# Scope Validation Comparison: pds.js vs atproto PDS Comparison of OAuth scope validation between this implementation and the official AT Protocol PDS. --- ## Scope Types Supported | Scope Type | Format | pds.js | atproto PDS | |------------|--------|--------|-------------| | `atproto` | Static | Full access | Required for all OAuth | | `transition:generic` | Static | Full access | Full repo/blob bypass | | `transition:email` | Static | N/A | Read account email | | `transition:chat.bsky` | Static | N/A | Chat RPC access | | `repo:?action=` | Granular | Full parsing + enforcement | Full parsing + enforcement | | `blob:` | Granular | Full parsing + enforcement | Full parsing + enforcement | | `rpc::` | Granular | Not implemented | Full parsing + enforcement | --- ## Scope Enforcement by Endpoint ### com.atproto.repo.createRecord | Aspect | pds.js | atproto PDS | |--------|--------|-------------| | Scope check | `ScopePermissions.allowsRepo(collection, 'create')` | `permissions.assertRepo({ action: 'create', collection })` | | Required scope | `repo:?action=create` or `transition:generic` or `atproto` | `repo:?action=create` or `transition:generic` or `atproto` | | OAuth-only check | Yes (legacy tokens without scope bypass) | Yes (legacy Bearer bypasses) | | Error response | 403 "Missing required scope \"repo:...?action=...\"" | 403 "Missing required scope \"repo:...?action=...\"" | ### com.atproto.repo.putRecord | Aspect | pds.js | atproto PDS | |--------|--------|-------------| | Scope check | `allowsRepo(collection, 'create')` AND `allowsRepo(collection, 'update')` | `assertRepo({ action: 'create' })` AND `assertRepo({ action: 'update' })` | | Required scope | `repo:?action=create&action=update` | `repo:?action=create&action=update` | | Notes | Requires both since putRecord can create or update | Requires both since putRecord can create or update | ### com.atproto.repo.deleteRecord | Aspect | pds.js | atproto PDS | |--------|--------|-------------| | Scope check | `ScopePermissions.allowsRepo(collection, 'delete')` | `permissions.assertRepo({ action: 'delete', collection })` | | Required scope | `repo:?action=delete` | `repo:?action=delete` | ### com.atproto.repo.applyWrites | Aspect | pds.js | atproto PDS | |--------|--------|-------------| | Scope check | Iterates all writes, checks each unique action/collection pair | Iterates all writes, asserts each unique action/collection pair | | Required scope | All `repo:?action=` for each write | All `repo:?action=` for each write | | Per-write validation | Yes | Yes | ### com.atproto.repo.uploadBlob | Aspect | pds.js | atproto PDS | |--------|--------|-------------| | Scope check | `ScopePermissions.allowsBlob(contentType)` | `permissions.assertBlob({ mime: encoding })` | | Required scope | `blob:` (e.g., `blob:image/*`) | `blob:` (e.g., `blob:image/*`) | | MIME type awareness | Yes (validates against Content-Type) | Yes (validates against Content-Type) | ### app.bsky.actor.getPreferences | Aspect | pds.js | atproto PDS | |--------|--------|-------------| | Scope check | Requires auth only | `permissions.assertRpc({ aud, lxm })` | | Required scope | Any valid auth | `rpc:app.bsky.actor.getPreferences` | ### app.bsky.actor.putPreferences | Aspect | pds.js | atproto PDS | |--------|--------|-------------| | Scope check | Requires auth only | `permissions.assertRpc({ aud, lxm })` | | Required scope | Any valid auth | `rpc:app.bsky.actor.putPreferences` | --- ## Scope Parsing | Feature | pds.js | atproto PDS | |---------|--------|-------------| | Scope string splitting | `scope.split(' ')` | `ScopesSet` class | | Repo scope parsing | `parseRepoScope()` | `RepoPermission.fromString()` | | Repo scope format | `repo:collection?action=create&action=update` | `repo:collection?action=create&action=update` | | Blob scope parsing | `parseBlobScope()` | `BlobPermission.fromString()` | | RPC scope parsing | None | `RpcPermission.fromString()` | | Scope validation | Returns null for invalid | Validates syntax, ignores invalid | | Action deduplication | Yes (via Set) | Yes | | Default actions | All (create, update, delete) when no `?action=` | All (create, update, delete) when no `?action=` | --- ## Permission Checking | Feature | pds.js | atproto PDS | |---------|--------|-------------| | Permission class | `ScopePermissions` | `ScopePermissions` / `ScopePermissionsTransition` | | `allowsRepo(collection, action)` | Yes | Yes | | `allowsBlob(mime)` | Yes (with MIME wildcard matching) | Yes (with MIME wildcard matching) | | `allowsRpc(aud, lxm)` | N/A | Yes | | Transition scope handling | `transition:generic` bypasses repo/blob checks | `transition:generic` bypasses repo/blob checks | | Error messages | Specific missing scope in error | Specific missing scope in error | --- ## OAuth Flow | Feature | pds.js | atproto PDS | |---------|--------|-------------| | `scopes_supported` in metadata | `['atproto']` | `['atproto']` (but accepts granular) | | Scope validation at PAR | None | Validates syntax | | Scope stored in token | Yes | Yes | | Scope returned in token response | Yes | Yes | | `atproto` scope required | Checked at endpoints | Required at token verification | --- ## Transition Scope Behavior | Scope | pds.js | atproto PDS | |-------|--------|-------------| | `transition:generic` | Bypasses all repo/blob permission checks | Bypasses ALL repo/blob permission checks | | `transition:chat.bsky` | Not implemented | Allows `chat.bsky.*` RPC methods | | `transition:email` | Not implemented | Allows `account:email:read` | --- ## Summary | Category | pds.js | atproto PDS | |----------|--------|-------------| | Scope parsing | Full parser for repo/blob | Full parser per scope type | | Enforcement granularity | Per-collection, per-action | Per-collection, per-action | | Transition scope support | `transition:generic` only | Full | | MIME-aware blob scopes | Yes | Yes | | RPC scopes | No | Yes | | Error specificity | Names missing scope | Names missing scope | --- ## Remaining Gaps 1. **RPC scopes** — `rpc::` parsing and enforcement not implemented 2. **Additional transition scopes** — `transition:chat.bsky` and `transition:email` not implemented 3. **Scope validation at PAR** — Could validate scope syntax during authorization request