fix(ingest): bind the verified signer, never the claimed actor (SEC-1) master
CRITICAL. The inbox verified one actor's key and then let activity.actor name a DIFFERENT actor on the same authority, queueing that CLAIM as the event's ActorID. Every downstream rule that keys on identity was therefore asking about a forgeable id. Reachable consequences, both reproduced: - Moderation: an ordinary account (lemmy.world/u/attacker) signs an Announce claiming actor = lemmy.world/c/technology, and 17c-1's membership rule then compares the target's community_did against an ATTACKER-CHOSEN community. Any account on a Lemmy instance could remove or restore native content in any community co-hosted there — exactly what decision 18 exists to refuse. - Follow state: handleAccept sets communityID = signer and then checks `communityID != signer`, which under laundering compares the claim with itself and CANNOT fail. Any account could drive a pending follow to accepted and trigger a backfill, or Reject us out of a community. The same-authority tolerance was added for Mastodon secure-mode instance-actor signing. No traffic we receive needs it: Lemmy signs as the acting actor, and secure-mode uses the instance actor for signed FETCHES, not delivery POSTs. Binding the signer leaves ingest, votes and accept green, so the tolerance was protecting nothing and costing identity. A cross-authority claim is still refused at the door (malformed or hostile whichever id we key on), but the claim is never USED. The inbox establishes who spoke — which it can do with certainty — and leaves what they may do to the layers holding the state that decides it. orderingKeyFor follows, so an attacker can no longer serialize their events onto a community's key. RESTORE-1: announced Undo{Delete} became reachable for bridge-origin content when community_did landed, and had no origin guard — it fetched our own /ap/object id and re-materialized it, minting a bridged actor and PLC DID for a native persona, then failed committing into the author's un-hosted repo, and the compensation soft-deleted our own mapping and tombstoned our own id, leaving the post permanently unmoderatable. Native restores now branch on origin before the fetch: nothing fetched, nothing re-materialized, plus two idempotent clears for pre-17c deliveries that already fell into the v1 path. Also from the review: Readmit no longer reports accepted/enqueued=true for a call the terminality branch refused; announced deletes of native comments are taken and counted instead of falling into the destructive v1 path; inbound removals reach the admissions ledger through narrow UPDATEs (Record would blank evaluated_snapshot and make the post permanently unreadmittable); author_did gets the same COALESCE as community_did and is now populated at enqueue, since deleteIsByAuthor decides self-delete vs moderator removal from it. Found by one of three review streams. The other two traced the authorization conjunction and correctly reported it holds — it does; the input to it did not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>