You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Drop the clone() workaround in quote request handling #643
#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.
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.
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.
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.
Activity
clone()shares property arrays, so dereferencing on a clone rewrites the original fedify#1207deepClone()to vocabulary objects fedify#1243dahlia commentedon Oct 5, 2026
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 theQuoteRequestembeds a same-origin instrument, which is what Mastodon sends,resolving.getInstrument()returns the sameNoteinstance thatrequestholds.persistPost()then callsgetAttribution()on it, and Fedify caches the fetched actor inside thatNote. TheAcceptorRejectwe send back then carries a fullPersonobject where the sender had an IRI forattributedTo.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:
clone()shares nested objects, and what hydration does to them fedify#1242 documents thatclone()shares nested objects, so anything fetched and cached in a shared object shows up in every object that holds it.deepClone()to vocabulary objects fedify#1243 addsdeepClone()in Fedify 2.5.0.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 whoseattributedTois an IRI and checks that it stays an IRI in the echoed request.Correct why quote requests are copied via JSON-LD