Skip to content

Registry change feed: webhook anti-replay (timestamp + delivery sequence in HMAC) #4772

@bokelley

Description

@bokelley

Problem

The webhook envelope spec in `specs/registry-change-feed.md` signs only the request body with HMAC. There's no timestamp and no delivery sequence in the signed material. A captured webhook can be replayed indefinitely (same body, same signature, same `latest_event_id`).

Surfaced during security review of the catalog change feed — same gap exists upstream. The catalog feed spec already adopts the fix (see commit 3f32784); this issue tracks porting it to the registry feed.

Proposed fix

Mirror the catalog-feed shape from PR #4767:

```
POST https://buyer.example.com/hooks/registry
X-Registry-Signature: sha256={hmac}
X-Registry-Event: property.created
X-Registry-Timestamp: 2026-05-19T11:00:00Z
X-Registry-Delivery-Seq: 4421
```

  • HMAC is computed over the canonical string `{timestamp}\n{delivery_seq}\n{body}` (newline-separated).
  • Receivers MUST reject signatures whose timestamp is more than 5 minutes skewed from receive time.
  • `delivery_seq` is strictly monotonic per subscription. Receivers de-dupe on `(subscription_id, delivery_seq)`.
  • Retried deliveries reuse the original `delivery_seq` — receivers dedupe on the sequence, not the signature.

Scope

  • Update `specs/registry-change-feed.md` §Webhook Delivery with the new envelope
  • Update the receiver-side validation guidance
  • Open a follow-up to land the matching signer changes in the central registry implementation
  • Cross-reference the catalog feed's anti-replay text once landed

Compatibility

Adding new signed headers is breaking for existing subscribers (the signature they verify is over a different string). Two paths:

  1. Versioned signer: ship a new `X-Registry-Signer-Version: 2` header; v1 signers continue to compute over body-only for a deprecation window. Subscribers honor whichever they receive.
  2. Coordinated cut: announce date, switch all subscribers in one window. Easier for the central registry where there's a single operator.

Lean toward (1) for safety. The registry feed has external subscribers (TMP routers, RegistrySync clients) that can't all be coordinated.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions