PDS Implementation TODOs#
Lewis' corrected big boy todofile
Server Infrastructure & Proxying#
- Health Check
- Implement
GET /healthendpoint (returns "OK"). - Implement
GET /xrpc/_healthendpoint (returns "OK").
- Implement
- Server Description
- Implement
com.atproto.server.describeServer(returns available user domains).
- Implement
- XRPC Proxying
- Implement strict forwarding for all
app.bsky.*andchat.bsky.*requests to an appview. - Forward auth headers correctly.
- Handle appview errors/timeouts gracefully.
- Implement strict forwarding for all
Authentication & Account Management (com.atproto.server)#
- Account Creation
- Implement
com.atproto.server.createAccount. - Validate handle format (reject invalid characters).
- Create DID for new user (PLC directory).
- Initialize user repository (Root commit).
- Return access JWT and DID.
- Create DID for new user (did:web).
- Implement
- Session Management
- Implement
com.atproto.server.createSession(Login). - Implement
com.atproto.server.getSession. - Implement
com.atproto.server.refreshSession. - Implement
com.atproto.server.deleteSession(Logout). - Implement
com.atproto.server.activateAccount. - Implement
com.atproto.server.checkAccountStatus. - Implement
com.atproto.server.createAppPassword. - Implement
com.atproto.server.createInviteCode. - Implement
com.atproto.server.createInviteCodes. - Implement
com.atproto.server.deactivateAccount. - Implement
com.atproto.server.deleteAccount(user-initiated, requires password + email token). - Implement
com.atproto.server.getAccountInviteCodes. - Implement
com.atproto.server.getServiceAuth(Cross-service auth). - Implement
com.atproto.server.listAppPasswords. - Implement
com.atproto.server.requestAccountDelete. - Implement
com.atproto.server.requestEmailConfirmation/requestEmailUpdate. - Implement
com.atproto.server.requestPasswordReset/resetPassword. - Implement
com.atproto.server.reserveSigningKey. - Implement
com.atproto.server.revokeAppPassword. - Implement
com.atproto.server.updateEmail. - Implement
com.atproto.server.confirmEmail.
- Implement
Repository Operations (com.atproto.repo)#
- Record CRUD
- Implement
com.atproto.repo.createRecord.- Generate
rkey(TID) if not provided. - Handle MST (Merkle Search Tree) insertion.
- Trigger Firehose Event.
- Generate
- Implement
com.atproto.repo.putRecord. - Implement
com.atproto.repo.getRecord. - Implement
com.atproto.repo.deleteRecord. - Implement
com.atproto.repo.listRecords. - Implement
com.atproto.repo.describeRepo. - Implement
com.atproto.repo.applyWrites(Batch writes). - Implement
com.atproto.repo.importRepo(Migration). - Implement
com.atproto.repo.listMissingBlobs.
- Implement
- Blob Management
- Implement
com.atproto.repo.uploadBlob.- Store blob (S3).
- return
blobref (CID + MimeType).
- Implement
Sync & Federation (com.atproto.sync)#
- The Firehose (WebSocket)
- Implement
com.atproto.sync.subscribeRepos.- Broadcast real-time commit events.
- Handle cursor replay (backfill).
- Implement
- Bulk Export
- Implement
com.atproto.sync.getRepo(Return full CAR file of repo). - Implement
com.atproto.sync.getBlocks(Return specific blocks via CIDs). - Implement
com.atproto.sync.getLatestCommit. - Implement
com.atproto.sync.getRecord(Sync version, distinct from repo.getRecord). - Implement
com.atproto.sync.getRepoStatus. - Implement
com.atproto.sync.listRepos. - Implement
com.atproto.sync.notifyOfUpdate.
- Implement
- Blob Sync
- Implement
com.atproto.sync.getBlob. - Implement
com.atproto.sync.listBlobs.
- Implement
- Crawler Interaction
- Implement
com.atproto.sync.requestCrawl(Notify relays to index us).
- Implement
- Deprecated Sync Endpoints (for compatibility)
- Implement
com.atproto.sync.getCheckout(deprecated). - Implement
com.atproto.sync.getHead(deprecated).
- Implement
Identity (com.atproto.identity)#
- Resolution
- Implement
com.atproto.identity.resolveHandle(Can be internal or proxy to PLC). - Implement
com.atproto.identity.updateHandle. - Implement
com.atproto.identity.submitPlcOperation/signPlcOperation/requestPlcOperationSignature. - Implement
com.atproto.identity.getRecommendedDidCredentials. - Implement
/.well-known/did.json(Depends on supporting did:web).
- Implement
Admin Management (com.atproto.admin)#
- Implement
com.atproto.admin.deleteAccount. - Implement
com.atproto.admin.disableAccountInvites. - Implement
com.atproto.admin.disableInviteCodes. - Implement
com.atproto.admin.enableAccountInvites. - Implement
com.atproto.admin.getAccountInfo/getAccountInfos. - Implement
com.atproto.admin.getInviteCodes. - Implement
com.atproto.admin.getSubjectStatus. - Implement
com.atproto.admin.sendEmail. - Implement
com.atproto.admin.updateAccountEmail. - Implement
com.atproto.admin.updateAccountHandle. - Implement
com.atproto.admin.updateAccountPassword. - Implement
com.atproto.admin.updateSubjectStatus.
Moderation (com.atproto.moderation)#
- Implement
com.atproto.moderation.createReport.
Temp Namespace (com.atproto.temp)#
- Implement
com.atproto.temp.checkSignupQueue(signup queue status for gated signups).
Misc HTTP Endpoints#
- Implement
/robots.txtendpoint.
OAuth 2.1 Support#
Full OAuth 2.1 provider for ATProto native app authentication.
- OAuth Provider Core
- Implement
/.well-known/oauth-protected-resourcemetadata endpoint. - Implement
/.well-known/oauth-authorization-servermetadata endpoint. - Implement
/oauth/authorizeauthorization endpoint (with login UI). - Implement
/oauth/parPushed Authorization Request endpoint. - Implement
/oauth/tokentoken endpoint (authorization_code + refresh_token grants). - Implement
/oauth/jwksJSON Web Key Set endpoint. - Implement
/oauth/revoketoken revocation endpoint. - Implement
/oauth/introspecttoken introspection endpoint.
- Implement
- OAuth Database Tables
- Device table for tracking authorized devices.
- Authorization request table.
- Authorized client table.
- Token table for OAuth tokens.
- Used refresh token table (replay protection).
- DPoP JTI tracking table.
- DPoP (Demonstrating Proof-of-Possession) support.
- Client metadata fetching and validation.
- PKCE (S256) enforcement.
- OAuth token verification extractor for protected resources.
- Authorization UI templates (HTML login form).
- Implement
private_key_jwtsignature verification with async JWKS fetching. - HS256 JWT support (matches reference PDS).
OAuth Security Notes#
Security measures implemented:
- Constant-time comparison for signature verification (prevents timing attacks)
- HMAC-SHA256 for access token signing with configurable secret
- Production secrets require 32+ character minimum
- DPoP JTI replay protection via database
- DPoP nonce validation with HMAC-based timestamps (5 min validity)
- Refresh token rotation with reuse detection (revokes token family on reuse)
- PKCE S256 enforced (plain not allowed)
- Authorization code single-use enforcement
- URL encoding for redirect parameters (prevents injection)
- All database queries use parameterized statements (no SQL injection)
- Deactivated/taken-down accounts blocked from OAuth authorization
- Client ID validation on token exchange (defense-in-depth against cross-client attacks)
- HTML escaping in OAuth templates (XSS prevention)
Auth Notes#
- Dual algorithm support: ES256K (secp256k1 ECDSA) with per-user keys AND HS256 (HMAC) for compatibility with reference PDS.
- Token storage: Storing only token JTIs in session_tokens table (defense in depth against DB breaches). Refresh token family tracking enables detection of token reuse attacks.
- Key encryption: User signing keys encrypted at rest using AES-256-GCM with keys derived via HKDF from KEY_ENCRYPTION_KEY environment variable.
PDS-Level App Endpoints#
These endpoints need to be implemented at the PDS level (not just proxied to appview).
Actor (app.bsky.actor)#
- Implement
app.bsky.actor.getPreferences(user preferences storage). - Implement
app.bsky.actor.putPreferences(update user preferences). - Implement
app.bsky.actor.getProfile(PDS-level with proxy fallback). - Implement
app.bsky.actor.getProfiles(PDS-level with proxy fallback).
Feed (app.bsky.feed)#
These are implemented at PDS level to enable local-first reads (read-after-write pattern):
- Implement
app.bsky.feed.getTimeline(PDS-level with proxy + RAW). - Implement
app.bsky.feed.getAuthorFeed(PDS-level with proxy + RAW). - Implement
app.bsky.feed.getActorLikes(PDS-level with proxy + RAW). - Implement
app.bsky.feed.getPostThread(PDS-level with proxy + RAW + NotFound handling). - Implement
app.bsky.feed.getFeed(proxy to feed generator).
Notification (app.bsky.notification)#
- Implement
app.bsky.notification.registerPush(push notification registration, proxied).
Infrastructure & Core Components#
- Sequencer (Event Log)
- Implement a
Sequencer(backed byrepo_seqtable). - Implement event formatting (
commit,handle,identity,account). - Implement database polling / event emission mechanism.
- Implement cursor-based event replay (
requestSeqRange).
- Implement a
- Repo Storage & Consistency (in postgres)
- Implement
RepoStoragefor postgres (replaces per-user SQLite).- Read/Write IPLD blocks to
blockstable (global deduplication). - Manage Repo Root in
repostable.
- Read/Write IPLD blocks to
- Implement Atomic Repo Transactions.
- Ensure
blockswrite,repo_rootupdate,recordsindex update, andsequencerevent are committed in a single transaction.
- Ensure
- Implement concurrency control (row-level locking via FOR UPDATE).
- Implement
- DID Cache
- Implement caching layer for DID resolution (valkey).
- Handle cache invalidation/expiry.
- Graceful fallback to no-cache when Valkey unavailable.
- Crawlers Service
- Implement
Crawlersservice (debounce notifications to relays). - 20-minute notification debounce.
- Circuit breaker for relay failures.
- Implement
- Notification Service
- Queue-based notification system with database table
- Background worker polling for pending notifications
- Extensible sender trait for multiple channels
- Email sender via OS sendmail/msmtp
- Discord webhook sender
- Telegram bot sender
- Signal CLI sender
- Helper functions for common notification types (welcome, password reset, email verification, etc.)
- Respect user's
preferred_notification_channelsetting for non-email-specific notifications
- Image Processing
- Implement image resize/formatting pipeline (for blob uploads).
- WebP conversion for thumbnails.
- EXIF stripping.
- File size limits (10MB default).
- IPLD & MST
- Implement Merkle Search Tree logic for repo signing.
- Implement CAR (Content Addressable Archive) encoding/decoding.
- Cycle detection in CAR export.
- Rate Limiting
- Per-IP rate limiting on login (10/min).
- Per-IP rate limiting on OAuth token endpoint (30/min).
- Per-IP rate limiting on password reset (5/hour).
- Per-IP rate limiting on account creation (10/hour).
- Per-IP rate limiting on refreshSession (60/min).
- Per-IP rate limiting on OAuth authorize POST (10/min).
- Per-IP rate limiting on OAuth 2FA POST (10/min).
- Per-IP rate limiting on OAuth PAR (30/min).
- Per-IP rate limiting on OAuth revoke/introspect (30/min).
- Per-IP rate limiting on createAppPassword (10/min).
- Per-IP rate limiting on email endpoints (5/hour).
- Distributed rate limiting via Valkey/Redis (with in-memory fallback).
- Circuit Breakers
- PLC directory circuit breaker (5 failures → open, 60s timeout).
- Relay notification circuit breaker (10 failures → open, 30s timeout).
- Security Hardening
- Email header injection prevention (CRLF sanitization).
- Signal command injection prevention (phone number validation).
- Constant-time signature comparison.
- SSRF protection for outbound requests.
- Timing attack protection (dummy bcrypt on user-not-found prevents account enumeration).
Lewis' fabulous mini-list of remaining TODOs#
- The OAuth authorize POST endpoint has no rate limiting, allowing password brute-forcing. Fix this and audit all oauth and 2fa surface again.
- DID resolution caching (valkey).
- Record schema validation (generic validation framework).
- Fix any remaining TODOs in the code.
Future: Web Management UI#
A single-page web app for account management. The frontend (JS framework) calls existing ATProto XRPC endpoints - no server-side rendering or bespoke HTML form handlers.
Architecture#
- Static SPA served from PDS (or separate static host)
- Frontend authenticates via OAuth 2.1 flow (same as any ATProto client)
- All operations use standard XRPC endpoints (existing + new PDS-specific ones below)
- No server-side sessions or CSRF - pure API client
PDS-Specific XRPC Endpoints (new)#
Absolutely subject to change, "bspds" isn't even the real name of this pds thus far :D Anyway... endpoints for PDS settings not covered by standard ATProto:
-
com.bspds.account.getNotificationPrefs- get preferred channel, verified channels -
com.bspds.account.updateNotificationPrefs- set preferred channel -
com.bspds.account.getNotificationHistory- list past notifications -
com.bspds.account.verifyChannel- initiate verification for Discord/Telegram/Signal -
com.bspds.account.confirmChannelVerification- confirm with code -
com.bspds.admin.getServerStats- user count, storage usage, etc.
Frontend Views#
Uses existing ATProto endpoints where possible:
Authentication
- Login page (uses
com.atproto.server.createSession) - Registration page (uses
com.atproto.server.createAccount) - Signup verification flow (uses
com.atproto.server.confirmSignup,resendVerification) - Password reset flow (uses
com.atproto.server.requestPasswordReset,resetPassword)
User Dashboard
- Account overview (uses
com.atproto.server.getSession,com.atproto.admin.getAccountInfo) - Active sessions view (needs new endpoint or extend existing)
- App passwords (uses
com.atproto.server.listAppPasswords,createAppPassword,revokeAppPassword) - Invite codes (uses
com.atproto.server.getAccountInviteCodes,createInviteCode)
Notification Preferences
- Channel selector (uses
com.bspds.account.*endpoints above) - Verification flows for Discord/Telegram/Signal
- Notification history view
Account Settings
- Email change (uses
com.atproto.server.requestEmailUpdate,updateEmail) - Password change while logged in (needs new endpoint - change password with current password)
- Handle change (uses
com.atproto.identity.updateHandle) - Account deletion (uses
com.atproto.server.requestAccountDelete,deleteAccount)
Data Management
- Repo browser (browse collections, view/create/delete records via
com.atproto.repo.*) - Data export/download (CAR file download via
com.atproto.sync.getRepo)
Admin Dashboard (privileged users only)
- User list (uses
com.atproto.admin.getAccountInfoswith pagination) - User detail/actions (uses
com.atproto.admin.*endpoints) - Invite management (uses
com.atproto.admin.getInviteCodes,disableInviteCodes) - Server stats (uses
com.bspds.admin.getServerStats)
Future: private data#
I will see where the discourse about encrypted/privileged private data is at the current moment, and make an implementation that matches what the bsky team will likely do in their pds whenever they get around to it. Then when they come out with theirs, I can make adjustments to mine and be ready on day 1. Or 2.
We want records that only authorized parties can see and decrypt. This requires some sort of federation of keys and communication between PDSes? Gotta figure all of this out as a first step.