Add the actor-request credential, and two fixes it needs first #643

Merged ngerakines.me
targeting main
Merged opened by and assigned to ngerakines.me
targeting main

Implements com.atproto.server.createActorAuth — both halves. The PDS mints request-bound tokens for identities whose keys it holds, and accepts them on the methods they name. Off by default behind PDS_ACTOR_AUTH_ENABLED.

This is built against four amendments to the proposal, not against the published draft. Read the divergences first or the implementation will look wrong:

  • atproto-identity and atproto-aud are required headers; the represented identity is no longer derived from request content. uploadBlob declares no parameters and its body is opaque, so content cannot name a subject in every case — and at mint time the credential that would name it is the thing being made.
  • A mty claim binds the request's media type, compared after normalization. The draft flags this gap in its own security considerations.
  • The arq presentation binding is normative, not a postscript. A token is never presented bare, which is what makes a stolen one worth nothing.
  • qry and bdy are unpadded base64url, not the standard base64 the draft's example shows. The worked example needs re-signing; the fixture here carries the re-signed version.

Two prerequisite fixes, in the first commit #

Both are defects in the existing inter-service auth verifier and are worth landing on their own terms.

verify_service_auth deserialized the header's typ and compared it to nothing, so a token typed as anything verified as service auth. And signatures were held to low-S, so roughly half of a conforming ES256K peer's tokens were refused intermittently — the second of the two founding bugs tools/conformance/README.md records, in a new place.

Reviewing #

  • crates/atproto-xrpcs/src/actor_auth.rs — the token type and the two-stage verifier. The binding rule that refuses content no claim covers is the one worth reading closely; without it the token degrades into an lxm-scoped bearer for its window.
  • crates/atproto-pds/src/http/actor_auth.rs — assert_locally_hosted is load-bearing. Without it any did:web can self-issue a token and have the server create a repository for it.
  • The three refusals — not hosted, not a delegate, out of scope — are deliberately byte-identical. A test compares full response bodies; making any of them more specific is an account-existence oracle.

This pull request has no versions submitted yet.