Repository navigation
Separate inbox authentication from the declared activity actor #1290
Description
Motivation
#1289 reports that Mastodon sends FEP-7aa9 FeatureRequest activities without an actor. Fedify rejects them before an inbox listener runs, even when their Linked Data Signatures have been verified. The planned patch for 2.4 will handle this case specifically.
The patch addresses FeatureRequest, but other requests without an actor would still need activity-specific exceptions. Fedify could identify authenticated principals independently of the payload's actor and expose them to applications for permission checks. This would avoid adding another core exception whenever an extension uses a request without an explicit actor.
Signature validity alone is not enough. An attacker can sign a request that names someone else's collection as its instrument. For FEP-7aa9, authorization also requires checking that the authenticated requester owns the collection. Inferring the requester from the collection's attributedTo without comparing it with an authenticated principal does not establish that link.
Proposed approach
Separate inbox authentication from the activity's declared actor and from application-level permission checks. The API is still undecided. It should:
- Retain the authenticated principals and authentication mechanisms, after verifying both the signatures and the keys' ownership or controller relationships. Distinguish the HTTP request signer from the signers of the activity document, since a server can forward a document signed by another actor. Handle multiple Object Integrity Proofs without arbitrarily choosing one signer.
- Preserve the existing authentication requirements for activities with an explicit
actor. Do not bypass a failed authentication check against the declaredactorby falling back to a different principal. - Allow requests without an explicit
actorto reach handlers when an authenticated requester can be identified under the activity's protocol. This must not waive actor requirements or permission checks imposed by the activity's protocol. - Make that authentication information available to inbox listeners and interaction helpers. For example,
featureInteractionshould be able to compare the authenticated requester with the owner of a reliably resolvedFeaturedCollection. - Preserve the original signed payload and retain the authenticated principals, verified key references, and authentication mechanisms through queued processing. Do not add an inferred
actorto the document as though it had been covered by the original signature.
Regression coverage should include direct delivery and forwarding, mismatched collection owners, multiple proofs, and queue retries. Successful signature verification with no usable authenticated principal must not be enough to dispatch a request to an inbox listener.
Release scope
Target the next minor release. Changes to the shared authentication flow and listener APIs need broader review than the focused maintenance fix for #1289. Keep the 2.4 patch focused on #1289 and reconcile it with the shared authentication changes when those are ready.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Effort
Activity
FeatureRequest(FEP-7aa9) is rejected with400 Missing actorbecause it has noactor#1289dahlia commentedon Oct 10, 2026
PR #1291 marks its temporary
FeatureRequestauthentication and queue handling withFIXME/TODOcomments. When implementing #1290, remove or replace those paths, including the signature-observation scan, inferredactorinjection andfeatureRequestActorqueue metadata/replay. Retain collection ownership validation and preserve the original signed payload.dahlia commentedon Oct 10, 2026
The review of #1291 exposed security requirements that the shared authentication design here should preserve:
objectorinstrumentvalues under the producer's authentication result. Comparing onlyinstrumentwas insufficient. This applies to HTTP-authenticated requests as well as signed activity documents.actormust not be presented as part of the signed payload.attributedTo; a cross-origin redirect must not let another host claim ownership. Portable collections need their existing proof-based identity checks. The resolved owner must still match an authenticated principal.The maintenance patch now queues a producer-frozen parsed view under built-in contexts for actorless
FeatureRequestactivities. When replacing the #1290-linkedFIXME/TODOpaths, reassess this snapshot together withfeatureRequestActormetadata and inferred-actor restoration as part of shared principal replay. The temporary implementation need not become the general API, but retain its regression coverage for changing contexts, retries, original payload/proof preservation, and collection redirects.dahlia commentedon Oct 10, 2026
Another regression from #1291 concerns property cardinality:
instrumentIdsomits embedded objects without an ID. A request can therefore have two instrument values but only one instrument ID. If the anonymous object comes first, the singularinstrumentIdis alsonull, even though the previous owner-resolution code authenticated the collection named by the second value.When implementing this issue, validate the full property's cardinality and the selected value before inferring a requester or checking permissions. ID accessors alone do not establish that the property contains exactly one value. Authentication and listeners must use the same instrument; retain the regressions covering anonymous values both before and after the collection reference.
dahlia commentedon Oct 10, 2026
Two further lessons from #1291 should carry into the shared authentication design.
The earlier cardinality issue also affects
attributedTo: typed parsing can discard untyped anonymous values, so even counting the parsed objects is insufficient. Validate the full expanded property before typed parsing, and use that same expanded view for parsing and ownership checks. Retain regression tests for anonymous values both before and after the owner reference.Keep authentication/authorization rejection separate from invalid remote data and transient lookup failures. Malformed documents, unusable redirect responses, redirect cycles, and exceeded redirect limits should not trigger retries; transport failures, timeouts, HTTP 408/429, and 5xx responses should remain retryable. Consider structured loader failure reasons when implementing the shared flow, rather than carrying the patch's message-based classifier into the general API. Retain #1291's regression tests for both built-in document loaders, including HTTP redirects and alternate document links.
dahlia commentedon Oct 10, 2026
Another #1291 finding affects the actorless exception's entry condition for
FeatureRequest: an emptyactorIdslist does not mean the activity has noactorvalues. Anonymous actors, blank-node references, and untyped embedded values can disappear from ID accessors or typed parsing. Inferring a collection owner in that case replaces an explicitly supplied actor in the internal view.When implementing this issue, distinguish an absent actor from an explicit actor that cannot be authenticated. The latter must not gain access through actorless principal fallback. Preserve enough of the expanded input to make that distinction, bound to the same context interpretation used for parsing and verification. #1291 records the contexts used by each parsing attempt to check actor presence without re-fetching mutable contexts; this temporary mechanism should be replaced with shared verified-view metadata. Retain the anonymous/blank-node actor regression tests across authentication methods and direct/queued delivery.
Allowing private network addresses must not disable HTTP(S) validation of document fetch targets; #1292 tracks that loader fix and its backports.
dahlia commentedon Oct 10, 2026
Two more parsing details from #1291 should inform this issue. Expanded property arrays can contain a single
@listnode that holds multiple values. Cardinality checks must use the generated decoder's list-container interpretation, so anonymous values are counted before typed decoding drops them. Retain tests for a list with an anonymous attribution before or after the owner, and for a valid single-owner list.Decoding a whole collection also decodes embedded objects unrelated to ownership. For example, a malformed
Link.hreflangin an attachment makes the parser throwRangeError, just as a malformed temporal literal does. #1291 identifies this case using the existing expanded document and the generated decoder's language-tag normalization;RangeErrorexceptions from document/context loaders remain retryable. The general design should distinguish document decoding failures from loader failures at their source, so new parser fields do not require more ad hoc exception checks in ownership resolution. Consider whether ownership resolution needs to decode unrelated attachments at all. Any change there must keep the existing validation and verification guarantees.dahlia commentedon Oct 11, 2026
The portable collection path in #1291 still exposes a source-data gap:
lookupObject()returns a typed object, so serializing that result cannot recover untyped attribution values omitted during decoding. A resolver-level test confirms that the current one-owner check accepts this lossy result; that test isolates the post-lookup boundary and does not exercise proof verification. The general design needs access to the expanded collection view used by portable verification, rather than a second expansion or a typed round-trip.Malformed multibase data also shows why enumerating decoder exceptions does not scale: a collection with an embedded
DataIntegrityProofand an unsupportedproofValueprefix raises plainError, whereas malformed dates and language tags raiseRangeError. The design should record which operation failed and keep loader failures separate from decoding failures. Diagnostics must inspect the same expanded view that failed; re-expanding through a changing remote context can conceal a malformed temporal literal.dahlia commentedon Oct 11, 2026
Release-plan update: the narrow maintenance fix in #1291 was closed without merging, and #1289 is now targeted for Fedify 2.5 alongside this issue. This supersedes the issue body's plan to keep a separate 2.4 patch. Earlier comments describing
FIXME/TODOcleanup refer to the unmerged PR branch; that code will not ship, so those cleanup instructions do not apply to released code.The review loop exposed a design problem with the bridge: it tried to reconstruct authorization data from typed objects after decoding had discarded source values, and to classify decoding failures by enumerating exception types/messages. Additional patches also had to bind each verification attempt to its parsed view and preserve that view across queues. A type-specific exception did not avoid the need for shared authentication foundations. The passing CI suite did not cover the remaining counterexamples.
Carry these requirements into the design:
actorIdsaccessor is not proof thatactoris absent: anonymous actors, blank nodes, and untyped values must not gain access through principal fallback. A successful parse with no proofs is not authentication.@listcontainers and anonymous values before or after the collection/owner reference. Portable collection lookup needs the source view used by proof verification, not a reconstruction fromlookupObject()'s typed result. The latest portable regression isolated the post-lookup boundary; it did not test an end-to-end proof bypass.actoras signed data.Use #1291's tests and review counterexamples as a regression inventory, reproducing each claim before accepting it. Retain valid-input tests as well as rejection tests, cover authentication fallbacks, forwarding, and direct and queued delivery, and test supported runtimes. Reimplement the flow around these requirements rather than porting the temporary bridge. #1292 tracks the independent HTTP(S) scheme-validation bug and its maintenance backports.