fix(security): require signed + matching digest on the AP inbox #66

Merged
claude-bot merged 1 commit from fix/v7-inbox-signature-hardening into main 2026-06-30 01:49:51 +00:00
Contributor

Follow-up to #10, flagged by an automated commit security review (HTTP signature parser confusion / actor spoofing). #65 merged moments before this fix was ready, so it ships as a fast-follow.

The gap

verifyInboxSignature treated date and digest as optional and never required them to be among the signed headers — it verified the digest only if (digestHeader) present. So a signature covering just (request-target) host date leaves the body unbound: a captured/replayed signature from an actor could be re-sent with a substituted body, forging an activity as that signer. My #10 signer === activity.actor binding doesn't catch this — the replayed keyId legitimately is the actor; the forgery is the swapped body. #10 raised the impact from comment injection to entry writes.

(The two parsers — verifyInboxSignature and parseSignatureKeyId — are byte-identical, so they can't disagree on keyId; the exploitable hole was the unbound body, not parser divergence.)

Fix

Require the signature to cover (request-target), date, and digest, and verify the body digest unconditionally. Standard AP POST (Mastodon, etc.) always signs these three, so legitimate federation is unaffected. This also hardens the pre-existing Follow/comment inbox paths.

Verification (dev server)

  • New regression test: a signature not covering digest401, no entry written.
  • remoteWritePath.test.ts 6/6 (all #10 cases still pass with the stricter check).
  • Full API suite 196 passed (after a test-Redis FLUSHALL to clear an unrelated 429 cascade).

🤖 Generated with Claude Code

Follow-up to #10, flagged by an automated commit security review (HTTP signature parser confusion / actor spoofing). #65 merged moments before this fix was ready, so it ships as a fast-follow. ## The gap `verifyInboxSignature` treated `date` and `digest` as **optional** and never required them to be among the **signed** headers — it verified the digest only `if (digestHeader)` present. So a signature covering just `(request-target) host date` leaves the **body unbound**: a captured/replayed signature from an actor could be re-sent with a **substituted body**, forging an activity as that signer. My #10 `signer === activity.actor` binding doesn't catch this — the replayed `keyId` legitimately *is* the actor; the forgery is the swapped body. #10 raised the impact from comment injection to **entry writes**. (The two parsers — `verifyInboxSignature` and `parseSignatureKeyId` — are byte-identical, so they can't disagree on `keyId`; the exploitable hole was the unbound body, not parser divergence.) ## Fix Require the signature to cover `(request-target)`, `date`, and `digest`, and verify the body digest **unconditionally**. Standard AP POST (Mastodon, etc.) always signs these three, so legitimate federation is unaffected. This also hardens the pre-existing Follow/comment inbox paths. ## Verification (dev server) - New regression test: a signature **not** covering `digest` → `401`, no entry written. - `remoteWritePath.test.ts` **6/6** (all #10 cases still pass with the stricter check). - Full API suite **196 passed** (after a test-Redis `FLUSHALL` to clear an unrelated 429 cascade). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(security): require signed, matching digest on AP inbox (#10)
All checks were successful
CI / API — typecheck & tests (push) Successful in 2m30s
CI / App — typecheck, test & build (push) Successful in 1m20s
CI / App — typecheck, test & build (pull_request) Successful in 1m24s
CI / API — typecheck & tests (pull_request) Successful in 2m21s
3ff3dbc311
verifyInboxSignature treated `date` and `digest` as optional and never
required them to be among the signed headers. A captured signature over
just (request-target)/host/date could be replayed with a substituted body,
forging an activity as the signer — actor spoofing — which #10 elevated from
comment injection to entry writes.

Now require (request-target), date, and digest to be signed, and verify the
body digest unconditionally. Standard AP POST (Mastodon et al.) already signs
all three, so legitimate federation is unaffected. Adds a regression test
(a signature not covering digest is rejected 401, no entry written).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude-bot scheduled this pull request to auto merge when all checks succeed 2026-06-30 01:47:33 +00:00
claude-bot deleted branch fix/v7-inbox-signature-hardening 2026-06-30 01:49:52 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
rbrooks/TeaLeaves!66
No description provided.