Claude: changes for issue #52 #53
No reviewers
Labels
No labels
breaking-change
bug
documentation
duplicate
enhancement
good first issue
help wanted
performance
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/ATProto.NET!53
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "claude/issue-52"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Requested in #52.
@claude-bot Please review.
🤖 On it — follow along in the action run; the reply will link the full session transcript.
Reviewed the branch, built it, and ran the full unit suite.
✅ Verified
dotnet build -p:EnableSourceControlManagerQueries=false— clean; no new warnings on any of the touched files (the CS1591s are pre-existingLexicon/Tools/Ozonenoise).dotnet test tests/ATProtoNet.Tests/— 934 passed, 0 failed.OAuthClient.Dispose()doesn't touch theHttpClient, andAtProtoOAuthService._httpClientstaysnullwhen a client is supplied, so there's no double-dispose or dispose-the-caller's-client path. The test asserting the caller's client survivesservice.Dispose()covers exactly that.RaceAuthoritativeLookupsAsyncis correct:budget.Cancel()runs before theusingdisposes the source, so the loser's registrations are already fired and theContinueWithobservation prevents a late fault surfacing unobserved.Overall this is solid — the diagnosis in the commit message matches the code, and the tests exercise the actual failure mode (a handler that hangs on
:443) rather than just the plumbing.🔧 Fixed on the branch (
claude/issue-52, commitf7ac34e)CHANGELOG.md— the new### Securityheading swallowed an unrelated entry. It was inserted directly above the pre-existingDirectory.Build.props/RepositoryUrlbullet, which was under### FixedinHEAD~1. That NuGet-registry build fix was silently reclassified as a security item. Moved the heading below it soSecuritystays last.I also added a Security bullet for a behaviour change the original entry didn't mention — see below.
💬 Review comments (your call, not changed)
1. DNS-over-HTTPS is now on the hot path for every resolution. Racing means
dns.googleis contacted on everyResolveHandleToDidAsynccall, where previously it was only reached after the HTTPS well-known lookup failed. For*.bsky.socialhandles — which do serve the well-known endpoint — that used to be never. So every handle a user types now goes to a hardcoded third-party resolver. It's a reasonable trade for the latency win, but it deserved to be stated; I added a CHANGELOG bullet for it. Worth considering whether the DoH endpoint should be configurable.2.
AtProtoOAuthService.cs:115mutates a caller-suppliedHttpClient. The comment two lines above says "A caller-supplied client is theirs: don't touch itsTimeout", and then the code writes to itsDefaultRequestHeaders.UserAgent. For the headline use case in the docs — anIHttpClientFactoryclient — that instance may be handed out and in flight elsewhere;DefaultRequestHeadersmutation isn't thread-safe, and the UA then rides on all the caller's other requests through that client. Setting the UA per-request, or only on the SDK-created client, would match the stated contract.3.
ResolveHandleAuthoritativeAsync's fail-closed guarantee is now budget-dependent. With both lookups sharing one 5 s budget, an honest-but-slow HTTPS authority times out tonulland the method accepts the DNS answer alone, logging "resolved via DNS only" — the conflict check never runs. I don't think it's exploitable in practice (an attacker who can stall one authority generally already controls it, and can't stalldns.google), but on slow networks it means the authoritative path can quietly degrade to single-source. There's no test for "both answer, one exceeds budget"; that case is worth pinning down.4. Same-host redirect check may break apex→www setups. A handle domain that 301s
example.com/.well-known/atproto-did→www.example.com/...previously resolved and now returnsnull. The DNS/appview fallbacks cover it, so the failure is a slower resolution rather than a broken one — just flagging that it's a real-world config, not only an attack shape.5. Minor: the resolution order in
ResolveHandleToDidAsyncis now nondeterministic — with a stale DNS TXT record and a corrected well-known, which one you get varies per call. Fine for the documented convenience path, but a short HTTPS head start (or preferring HTTPS when both land inside a grace window) would keep it stable. Also, the commit message attributesAtProtoOAuthServerOptionstoATProtoNet.Server; it lives inATProtoNet.Blazor.⏱ 5m 25s · 28 turns · tokens: 74.6k in / 19.6k out (+1471.7k cache-read)
📜 Full session transcript