Skip to content

Verify FEP-044f quotes with Fedify interaction controls - #641

Merged
dahlia merged 1 commit into
fedify-dev:mainfrom
dahlia:refactor/fep-044f
Oct 2, 2026
Merged

dahlia merged 1 commit into
fedify-dev:mainfrom
dahlia:refactor/fep-044f

Conversation

@dahlia

@dahlia dahlia commented Oct 2, 2026

Copy link
Copy Markdown
Member

Closes #635.

quoteInteraction from @fedify/interaction-controls replaces Hollo's own requester, target, and authorization checks, so fixes to them arrive with Fedify updates. Hollo still owns quote state, counters, notifications, and delivery. The integration is in src/federation/quote.ts.

Incoming quote requests

The target is loaded from the database and swapped into the request as a local Note carrying the canQuote rule built from stored settings. Hollo doesn't fetch its own post over HTTP, and the same object serves as the evaluatePolicy() subject.

Hollo resolves the instrument before calling the helper and lets fetch errors propagate, so transient failures trigger an inbox retry instead of dropping the request. Resolution runs on a copy re-parsed from JSON-LD, because clone() shares the instrument storage and would otherwise rewrite the request echoed back in the Accept.

Visibility and block checks still run before policy evaluation. The approved-follower check is a database lookup in matchesApprovalCollection; the helper swallows errors from that callback, so Hollo rethrows them rather than sending a permanent Reject over a database hiccup.

Authorizations

Authorizations from an Accept or a remote quote post are always verified by dereferencing their IRI. Embedded bodies are never trusted. Hollo omits verifyAuthenticity because matching fields don't establish authenticity.

Retry decisions look at what the document loader threw, not at the helper's failure type, since the helper reports a failed JSON-LD context fetch as invalidJsonLd. Network, DNS, 5xx, 408, and 429 errors are retried; other 4xx responses and URLs rejected by SSRF protection are not.

Verifying an Accept now waits on the network, so the pending-to-accepted transition is a conditional update: concurrent Accepts count once, and a Reject received during the fetch wins.

What stays local

The revocation Delete keeps its embedded authorization, since createRevocation() emits only the IRI. Mapping remote policies to Mastodon's enum is untouched, and so is the legacy path for remote posts without a policy, which the helper would deny. getRevocation isn't wired because Hollo doesn't keep revoked authorization IDs.

Behavior changes

Tests cover the stricter rules listed in CHANGES.md: requests need an id and an actor, quote and quoteUrl must agree, cross-origin embedded instruments are refetched, and authorizations must be fetchable from the quoted author's origin. Invalid requests no longer leave the instrument persisted. Accept and Reject responses now carry explicit IDs in Fedify's auto-ID shape and address the requester in to.

Not tested yet: live interop with Mastodon and GoToSocial. The peer fixtures are modeled on Mastodon's serializers, not captured traffic. Accepting impolite quotes from Misskey and similar software is tracked in #640.

Refactor the FEP-044f quote authorization flow to use quoteInteraction
from the new @fedify/interaction-controls dependency.  Hollo still owns
the database state, counters, notifications, and delivery.

 -  Add src/federation/quote.ts with shared helpers to build quote
    requests and authorizations, build the policy subject from Hollo's
    stored settings, and verify authorizations by dereferencing their
    IRIs.  A failed verification is marked retryable only when the
    document loader failed transiently.

 -  Incoming QuoteRequest activities go through verifyRequest() against
    a locally built target.  Their instrument is resolved on an
    independent copy, so the request echoed back keeps its original
    form.  The local canQuote policy is evaluated with evaluatePolicy()
    and an approved-follower collection check; visibility, block, and
    self-quote checks stay in Hollo.  Accept and Reject responses are
    built with the helper.

 -  Accept<QuoteRequest> now verifies the result authorization before
    approving.  The pending-state transition is a conditional update,
    so concurrent responses cannot double-count or overwrite a
    rejection.

 -  Remote quote posts verify quoteAuthorization by its IRI, and the
    object dispatcher and the outgoing request and revocation use the
    shared constructors.  The existing request IDs, authorization URLs,
    ordering keys, revocation wire format, and legacy no-policy path are
    preserved.

The stricter verification cases are listed in CHANGES.md and covered by
regression tests.

Closes fedify-dev#635

Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Codex:gpt-6-astra
Assisted-by: Codex:gpt-6.1-sol
@dahlia

dahlia commented Oct 2, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@dahlia
dahlia merged commit 863d14c into fedify-dev:main Oct 2, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor quote authorization to use Fedify interaction controls

1 participant