Repository navigation
Serve only public portable objects from gateways by default - #1183
Conversation
✅ Deploy Preview for fedify-json-schema canceled.
|
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Warning Review limit reachedNext included review available in 27 minutes. View limit detailsLimit details: You’ve used all 4 included reviews currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (17)
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (17)
Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe change adds audience-signature checks to request and test contexts. Portable gateway responses now apply public-addressing, object-ID, proof, and authorization checks. The documentation and tests describe these rules and their response behavior. ChangesPortable gateway access
Document loader timeout test
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Bug fix · Severity of issue fixed: Medium Sequence Diagram(s)sequenceDiagram
participant Requester
participant FederationImpl
participant handlePortableObject
Requester->>FederationImpl: Request portable actor or object
FederationImpl->>handlePortableObject: Dispatch with actor or object kind
handlePortableObject->>handlePortableObject: Check expanded ID, addressing, and proof
handlePortableObject-->>FederationImpl: Return gateway response
FederationImpl-->>Requester: Return response
Merge Risk: ⚪ Minimal · up to Portable gateway objects are now served only when publicly addressed, unless an authorization predicate is configured. No blocking issue was found, so the change looks ready to merge. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The change substantially tightens default access to private portable objects and prevents shared caching of predicate-authorized responses. Private access still depends on application authorization and membership policy. No newly introduced disclosure path was established, but incomplete coverage prevents treating the change as risk-free. Retained concerns Security review detailsSecurity Blast Radius
Security Findings and Attack Paths
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 35.29% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 17 functions across 12 files. (5 skipped: 5 unsupported.) ✨ Finishing Touches 💡 1⚔️ Resolve merge conflicts 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is
🚀 New features to boost your workflow:
|
The "double-knocking shares the timeout" step of the getAuthenticatedDocumentLoader() timeout test gave each request 80 ms under a 120 ms timeout, which left 40 ms to sign and send the second knock. On a busy CI runner, signing alone took longer than that, so the timeout fired before the second request and the test saw only one. It failed twice in a row on Bun in fedify-dev#1183. Each knock now takes 200 ms under a 300 ms timeout. The test still tells a shared timeout from a per-request one, as the two knocks take 400 ms in total, but leaves about 100 ms on both sides. Changelog: none Assisted-by: Claude Code:claude-opus-5-5
FEP-ef61 forbids a gateway to serve a non-public object to anyone but its intended audience, and a gateway may store objects that other servers or clients created without the application ever deciding who may see them. Until now, an object dispatcher without an authorization predicate served any portable object to anyone once its proof verified. Through the gateway endpoint, an object dispatcher without a predicate now serves a portable object only if the served JSON-LD document is publicly addressed, i.e., its to, cc, bto, bcc, or audience has the public collection (the full IRI, as:Public, or Public). Any other object gets 404 Not Found, as if the gateway did not store it, and the decision is made before its proofs are verified, so that a requester who may not see the object can neither trigger the verification nor tell its result. Actors, whether from the actor dispatcher or an object dispatcher, are exempt as they have no audience. Ordinary object URLs are unchanged. With a predicate, the predicate alone decides as before, and allowed responses now carry Cache-Control: private, since other requesters may be denied the same object. The new RequestContext.isSignedByAudience() method helps such predicates: it checks whether the request is signed by an actor in an object's audience, comparing portable IDs canonically, and takes an isMember option for collections such as followers, which Fedify cannot resolve by itself. The mock contexts of @fedify/testing implement it as well. The visibility check and the proof verification share one memoized context loader, so that both interpret the document the same way. Closes fedify-dev#1153 fedify-dev#288 Assisted-by: Claude Code:claude-opus-5-5 Assisted-by: Claude Code:claude-fable-5-1 Assisted-by: Codex:gpt-6-astra Assisted-by: OpenCode:deepseek-flash
The "double-knocking shares the timeout" step of the getAuthenticatedDocumentLoader() timeout test gave each request 80 ms under a 120 ms timeout, which left 40 ms to sign and send the second knock. On a busy CI runner, signing alone took longer than that, so the timeout fired before the second request and the test saw only one. It failed twice in a row on Bun in fedify-dev#1183. Each knock now takes 200 ms under a 300 ms timeout. The test still tells a shared timeout from a per-request one, as the two knocks take 400 ms in total, but leaves about 100 ms on both sides. Changelog: none Assisted-by: Claude Code:claude-opus-5-5
177d4b0 to
5128454
Compare
Closes #1153.
FEP-ef61 says a gateway must not serve a non-public object except to a request signed by an actor in the object's audience. Until now, an object dispatcher without an
authorize()predicate served any portable object to anyone once its proof verified. Gateways store objects that other servers and clients created, so a private object could end up public without the application ever deciding it should be.How it works
Without a predicate, a gateway request is now served only if the object is publicly addressed; otherwise the response is
404 Not Found, exactly as if the gateway did not store the object. Fedify does not check the audience itself, since only the application knows who is in a followers collection; serving anything else takes a predicate, as for ordinary objects. Gateway serving is unreleased, so the new default breaks no one.Objectinstance. An object built byfromJsonLd()serializes its original JSON, which can disagree with a mutated instance.500) from a missing one (404).isActor()rather than FEP-2277's property-based classifier, under which a privateNotewithtotalItemswould pass as a collection. Collections and tombstones get no exemption.to,cc,bto,bcc, andaudienceall count, with the public collection written as the full IRI,as:Public, orPublic. BarePublicsurvives JSON-LD expansion as a relative@id, so Fedify treats that exact string as public.With a predicate, the predicate decides alone and a denial still gets
401 UnauthorizedthroughonUnauthorized, as for ordinary objects. Allowed responses now carryCache-Control: private, because HTTP Signatures do not get the caching rules thatAuthorizationhas, and a shared cache would otherwise hand one requester's response to the next.An addressee may be an
ap:URI while the signer is identified by a compatible identifier on another gateway, which is easy to get wrong by hand. The newRequestContext.isSignedByAudience()compares them canonically and takes anisMembercallback for collections. The mock contexts in@fedify/testingimplement it too.Out of scope
Ordinary object URLs and collection dispatchers served through the gateway behave as before. The stale sentence in docs/manual/object.md saying collection dispatchers are unreachable through the gateway is left for #1150.