Pds: PdsSessionService DI registration uses an ephemeral signing key — all sessions invalidate on restart #37
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#37
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
AddAtProtoPds()registersPdsSessionServicevia plain DI:Its constructor is
PdsSessionService(PdsOptions options, byte[]? signingKey = null)— DI can't supplybyte[], so every process start generates a random HMAC signing key, which invalidates all access/refresh tokens on every restart/redeploy. For a production PDS that means all clients are silently logged out whenever the container restarts.Workaround (what Updraft does): re-register after
AddAtProtoPdsso the last registration wins:Suggestion: add
PdsOptions.SessionSigningKey(base64) and have the default registration use it when set, with a startup warning when falling back to an ephemeral key.🤖 On it — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
Fixed on branch
claude/issue-37.What changed
PdsOptions.SessionSigningKey(base64,string?) — the persisted HMAC-SHA256 key for session tokens.AddAtProtoPds()(both overloads) now buildsPdsSessionServicethrough a factory instead of registering it by type, so the key actually reaches the constructor that DI could never fill:The Updraft workaround (re-registering the singleton after
AddAtProtoPds) still works and still wins, since an explicitbyte[]takes precedence over the option.Fallback behaviour when unset — still an ephemeral random key, but now warned about, and the warning fires at host startup, not on the first login: a small internal
IHostedService(registered viaTryAddEnumerable, so repeatedAddAtProtoPdscalls don't stack) forces the singleton to be constructed duringStartAsync. That also makes a malformed key fail fast — an unparsable base64 value throwsInvalidOperationExceptionnaming the option rather than a bareFormatExceptionat token-issue time. A key that decodes to fewer than 32 bytes is accepted (existing PDS JWT secrets may be shorter) but warned about.New public API on
PdsSessionService:GenerateSigningKey()(32 random bytes, base64),ResolveSigningKey(PdsOptions)(decode/validate,nullwhen unset — an unset env var binding to""counts as unset),UsesEphemeralSigningKey,SigningKeySize.Also updated
docs/pds.md(new Session signing key section under Configuration), thePdsSampleprogram, andCHANGELOG.mdunder[Unreleased] → Added.Verified
dotnet build— 0 errors, no new warnings inATProtoNet.Pds.dotnet test tests/ATProtoNet.Tests/— 1048 passed, 0 failed (up from 1035; 14 new tests, 1 removed).New tests cover: a configured key surviving a simulated restart (two service instances / two DI containers sharing the config validate each other's tokens), the ephemeral key not surviving, explicit
byte[]precedence over the option, base64 validation, the startup warning being emitted by a realHostBuilderbefore any request, the short-key warning, no warning when configured, and single startup-check registration across duplicateAddAtProtoPdscalls.→ Opened PR: #61
⏱ 7m 12s · 46 turns · 📜 Full session transcript
(reply recovered from the transcript — the posting step in runs before v12.3 was broken by a shell-quoting bug)