You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(pds): address getFeed service-auth JWT to the feed generator (#193)
* fix(pds): address getFeed service-auth JWT to the feed generator
getFeed is proxied to the AppView, but the service-auth JWT must be
addressed to the feed generator (aud = feedgen DID, lxm =
getFeedSkeleton) so the generator can authorize the user and record
per-user state. We were stamping aud = did:web:api.bsky.app, so
generators that validate the audience (e.g. Bluesky For You) rejected
the token and ran statelessly, leaving feeds stuck.
Add handleGetFeedProxy: resolve the feedgen DID from the feed record in
the creator's repo and pass a service-auth override into handleXrpcProxy.
Falls back to ordinary AppView proxying when the feed can't be resolved.
* fix(pds): assert getFeed scope at routing aud and require HTTPS for feed resolution
Address review feedback:
- The OAuth/DPoP scope check now asserts getFeed and getFeedSkeleton
against the routing audience (the AppView), not the overridden feedgen
aud on the outbound JWT. Asserting at the feedgen aud rejected
otherwise-valid tokens. Matches the reference PDS.
- resolveFeedGenDid only fetches the feed record over HTTPS, matching the
proxy-target restriction, so an attacker-controlled DID document can't
trigger plaintext requests from the PDS.
* fix(pds): assert proxy rpc scope at the full did#service audience
Granular rpc: scopes are granted against the full `did#service_id`
audience (a bare DID is not a valid rpc scope audience per
@atproto/oauth-scopes), and scope matching is exact. The proxy asserted
against the bare `audienceDid`, so granular OAuth scopes could only ever
match `aud=*`. Assert against the full proxy-header value (matching the
reference PDS's computeProxyTo) while keeping the bare DID for the
outbound service-auth JWT.
Add OAuth/DPoP tests covering the getFeed scope check: a token scoped for
the AppView audience is accepted and the JWT is addressed to the feedgen;
a token scoped for a different audience is rejected with InsufficientScope.
* docs(changeset): note the granular rpc scope audience fix
* docs(changeset): split proxy scope fix into its own changeset
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Address the service-auth JWT for `app.bsky.feed.getFeed` to the feed generator rather than the AppView. The token is now stamped with `aud` set to the generator's service DID (resolved from the feed record) and `lxm` set to `app.bsky.feed.getFeedSkeleton`, matching the reference PDS implementation. Previously the token carried `aud: did:web:api.bsky.app`, so generators that validate the audience (such as the Bluesky "For You" feed) rejected it and ran in a degraded, stateless mode — feeds appeared stuck because per-user "seen" state was never recorded. If the feed record can't be resolved, the request falls back to ordinary AppView proxying so the feed still loads.
Fix OAuth scope checking when proxying XRPC requests. Granular `rpc:` scopes are granted against the full `did#service_id` audience, but the proxy was checking them against the bare DID, so any granular (non-`aud=*`) scope was rejected. Proxied requests now check scope against the full service audience, while the outbound service-auth JWT continues to use the bare DID.
0 commit comments