feat: implement XRPC service proxying per spec#28
Merged
Conversation
Implement proper XRPC service proxying according to the atproto spec: https://atproto.com/specs/xrpc#service-proxying Changes: - Add DID resolution utilities for did:web and did:plc - Parse atproto-proxy header (format: "did:web:example.com#service_id") - Resolve DID documents and extract service endpoints - Route requests to the specified service endpoint - Maintain backward compatibility with hardcoded Bluesky routing The implementation: 1. Checks for atproto-proxy header 2. Resolves the DID document from the specified DID 3. Extracts the service endpoint for the specified service ID 4. Forwards the request to that endpoint with a service JWT 5. Falls back to api.bsky.app/api.bsky.chat if no header present Tests: - 11 unit tests for DID resolver utilities - 7 integration tests for proxy behavior - All existing 126 tests continue to pass (137 total)
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
atproto-pds | 4341334 | Dec 30 2025, 08:10 AM |
Update proxy tests to handle different network conditions: - Local test environment: DNS lookups fail, fetch returns 500 - GitHub Actions: DNS lookups succeed, may get 401 or other responses Tests now verify the core behavior (proxying works) rather than specific status codes that vary by environment.
Replace environment-dependent network tests with deterministic mocked tests: - Mock DID document resolution - Mock proxied service responses - Verify service JWT creation and forwarding - Add comprehensive test for successful proxy flow Benefits: - Tests work consistently in all environments (local, CI, etc.) - No network dependencies - Faster test execution - More thorough validation of proxy behavior Test count: 138 total (added 1 new test for valid proxy flow)
Security and performance improvements based on @bluesky-social/atproto patterns: **DID Document Caching** - Add 1-hour TTL cache for DID document lookups - Limit cache size to 1000 entries with LRU eviction - Reduces network overhead for repeated proxying to same services **URL Construction & Validation** - Use URL constructor for safe URL building (prevent injection) - Simplified URL validation matching @atproto/common-web approach - Allow both HTTP and HTTPS (for local development) - Validate URLs can be parsed **Service Endpoint Lookup** - Align extractServiceEndpoint with @atproto/common-web/did-doc.ts - Optimize service ID matching (hot path optimization) - Support both #fragment and did#fragment formats **Special-case Main Services** - Cache api.bsky.app and api.bsky.chat endpoints - Avoid DID lookups for known Bluesky services - Still validate service IDs exist **Header Security** - Strip sensitive headers before proxying: - authorization (replaced with service JWT) - cookie (privacy - don't leak cookies) - x-forwarded-for/x-real-ip (don't leak client IP) - atproto-proxy (internal routing) - connection, host (connection-specific) **Path Validation** - Prevent path traversal in XRPC method names - Reject .. and // in lexicon method paths **Tests** - Add 9 URL validation tests - Update existing tests for new return types - All 147 tests passing Based on patterns from: @bluesky-social/atproto/packages/pds/src/pipethrough.ts and @bluesky-social/atproto/packages/common-web/src/did-doc.ts
Replace custom extractServiceEndpoint and validateServiceUrl implementations with official @atproto/common-web getServiceEndpoint. - Import getServiceEndpoint from @atproto/common-web - Remove custom implementation from src/did-resolver.ts - Re-export official DidDocument type - Update all tests to use official API - All 147 tests passing This ensures we stay aligned with the official atproto implementation and benefit from their tested and proven code.
Replace custom DID resolution implementation with the official @atproto/identity package for better reliability and maintenance. Changes: - Add @atproto/identity dependency (^0.4.10) - Create WorkersDidCache using Cloudflare Cache API - Replace custom resolveDidDocument with DidResolver - Remove redundant DID resolution code from did-resolver.ts - Update tests to handle URL objects in fetch mocks - Import DidDocument type from @atproto/common-web Benefits: - Battle-tested official implementation - Proper caching with stale/fresh semantics - Timeout protection (3s default) - Aligned with Bluesky's proven patterns - Persistent caching via Cloudflare Cache API All 147 tests passing.
Extract proxy handling from index.ts into a clean, focused module. Changes: - Create src/xrpc-proxy.ts with handleXrpcProxy function - Move parseProxyHeader to xrpc-proxy.ts (better location) - Delete src/did-resolver.ts (no longer needed) - Update index.ts to use handleXrpcProxy handler - Update tests to import from xrpc-proxy.ts Benefits: - Cleaner separation of concerns - index.ts is now 265 lines (was 447) - Proxy logic is self-contained and testable - parseProxyHeader lives with proxy code where it belongs All 147 tests passing.
- Add null check for payload.sub in xrpc-proxy - Improve parseProxyHeader validation with optional chaining - Fix array destructuring type inference in service-auth
@atproto/identity uses `redirect: "error"` which Cloudflare Workers doesn't support (only "follow" or "manual"). Created custom DidResolver that uses `redirect: "manual"` and checks for redirect status codes. Uses official @atproto/common-web schema validation for DID documents. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Import waitUntil from cloudflare:workers for background cache refresh - Validate cached DID documents using schema check on retrieval - Clear invalid cache entries automatically 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implement proper XRPC service proxying according to the atproto spec:
https://atproto.com/specs/xrpc#service-proxying
Changes:
The implementation:
Tests: