authViewAll aud inconsistency#
Minimal reproduction of a bug where scope
enforcement for RPCs in the app.bsky.authViewAll permission set is
inconsistent: some RPCs are authorized against the service-fragmented aud
(did:web:api.bsky.app#bsky_appview), others against the bare aud
(did:web:api.bsky.app). The client has no way to satisfy both from one
include: scope.
Setup#
pnpm install
pnpm dev
Visit http://127.0.0.1:3000/, enter your atproto handle, approve the
consent screen, and you'll land on /test. The page calls six RPCs with
an identical agent and identical scope and reports which ones succeed.
Observed#
atproto include:app.bsky.authViewAll?aud=did:web:api.bsky.app%23bsky_appview
produces:
| Lexicon | Status | Detail |
|---|---|---|
app.bsky.actor.getProfile |
OK | |
app.bsky.feed.getTimeline |
OK | |
app.bsky.feed.getAuthorFeed |
OK | |
app.bsky.graph.getLists |
FAIL | Missing required scope "rpc:app.bsky.graph.getLists?aud=did:web:api.bsky.app" |
app.bsky.notification.listNotifications |
FAIL | Missing required scope "rpc:app.bsky.notification.listNotifications?aud=did:web:api.bsky.app" |
app.bsky.feed.getFeedGenerator |
FAIL | Missing required scope "rpc:app.bsky.feed.getFeedGenerator?aud=did:web:api.bsky.app" |
Every RPC in the list above is declared in the
app.bsky.authViewAll permission set,
which uses inheritAud: true — so all of them should be granted at the
aud we passed to include:. The agent also sends
atproto-proxy: did:web:api.bsky.app#bsky_appview on every call, so the
service endpoint is unambiguously specified.
Why we can't work around it with rpc scopes#
Explicit per-RPC rpc: scopes would satisfy enforcement but display to
users as broad permission requests on the consent screen, which is poor
UX for an otherwise-narrow read-only app.
Files#
src/server.ts— the entire OAuth client + test harness: in-memory stores, ephemeral ES256 keypair per run, routes/,/login,/callback,/test,/jwks.json,/client-metadata.json.