Skip to content

Drop the clone() workaround in quote request handling #643

Description

@dahlia

#641 added a workaround for a Fedify bug: clone() on a vocabulary object shares its property arrays with the original, so dereferencing through the clone rewrites the original too (fedify-dev/fedify#1207). Once a Fedify patch release fixes that bug, the workaround can go.

Workaround

onQuoteRequested() resolves the QuoteRequest instrument before verifying the request. It needs a copy of the request for that, so the request it echoes back in the Accept or Reject keeps the sender's original form. Because clone() can't be trusted to give an independent copy, Hollo serializes the request to JSON-LD and parses it back instead (src/federation/inbox.ts L770–L778). That costs an extra serialization and parse for every incoming quote request.

What to do

  • Upgrade the @fedify/* packages to the patch release that fixes clone() shares property arrays, so dereferencing on a clone rewrites the original fedify#1207.
  • Replace the QuoteRequest.fromJsonLd(await request.toJsonLd(...)) round trip with request.clone(), and remove the comment explaining the workaround.
  • Keep the existing test echoes the sender's request after refetching a cross-origin instrument in src/federation/inbox.test.ts. It fails if the clone still shares storage, so it shows whether the upgrade actually fixed the bug.

No CHANGES.md entry is needed, since users won't see any difference.

Activity

  1. added this to the Hollo 0.10 milestone on Oct 2, 2026
  2. self-assigned this
    on Oct 2, 2026
  3. dahlia commented on Oct 5, 2026

    @dahlia
    MemberAuthor

    Putting this on hold until Fedify 2.5.0.

    Fedify 2.4.1 fixed fedify-dev/fedify#1207, but swapping the JSON-LD round trip for request.clone() still isn't safe. clone() copies only the request's own property arrays; nested objects stay shared. When the QuoteRequest embeds a same-origin instrument, which is what Mastodon sends, resolving.getInstrument() returns the same Note instance that request holds. persistPost() then calls getAttribution() on it, and Fedify caches the fetched actor inside that Note. The Accept or Reject we send back then carries a full Person object where the sender had an IRI for attributedTo.

    This isn't new in 2.4.x. Fedify 2.3.11 shares nested objects the same way, and fedify-dev/fedify#1207 was a separate regression in 2.4.0 where even the top-level arrays were shared. The round trip has been doing a deep copy all along, and we still need one.

    The existing test (echoes the sender's request after refetching a cross-origin instrument) passes with clone(), because a cross-origin instrument is refetched into the clone's own array. It doesn't cover the embedded same-origin case.

    Two Fedify issues came out of this:

    Once Fedify 2.5.0 is out, we can upgrade and replace the round trip with request.deepClone(). That change also needs a test that embeds a same-origin instrument whose attributedTo is an IRI and checks that it stays an IRI in the echoed request.

  4. removed this from the Hollo 0.10 milestone on Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Fields

Priority

None yet

Effort

None yet

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions