Skip to content

Add opt-in Receipt Required gate for create_watch (RR-1)#77

Open
FutureEnterprises wants to merge 2 commits into
RipperMercs:mainfrom
FutureEnterprises:receipt-required-watch
Open

Add opt-in Receipt Required gate for create_watch (RR-1)#77
FutureEnterprises wants to merge 2 commits into
RipperMercs:mainfrom
FutureEnterprises:receipt-required-watch

Conversation

@FutureEnterprises

Copy link
Copy Markdown

create_watch is the one tool in this MCP server that mutates the world and spends a paid credit: it registers a 90-day webhook watch that POSTs to a caller-supplied callback_url. Right now nothing requires that a human ever authorized that exact callback_url + type before an agent registers it and burns the credit.

This PR adds an opt-in authorization-receipt gate in front of create_watch so the maintainer can require a verifiable human authorization receipt for it. Merging earns RR-1.

What it does

  • Off by default, fully backward-compatible. With no extra config, create_watch behaves byte-identically to today. The gate only engages when the operator sets TENSORFEED_RECEIPT_REQUIRED=1.
  • When enabled, create_watch refuses to register the watch (and never spends the credit) unless the call carries an authorization_receipt bound to that exact type + callback_url: portable proof that a named human authorized this exact action.
Check Behavior
Missing receipt refused (428 Receipt Required), no credit spent
Valid receipt watch registers, receipt consumed one-time on success
Replayed receipt refused (one-time consumption)
Forged / expired receipt refused (signature / action-binding / freshness fails)

How it's built

One dependency: @emilia-protocol/require-receipt (Apache-2.0). Fully offline — no API key, no account, no EMILIA server trusted. The easy-to-get-wrong parts (target binding, consume-after-success so a transient failure never burns a valid approval, sanitized refusals) live in that reviewed gate; this PR just wires it to the create_watch seam. The original POST and output formatting are unchanged.

  • src/receipt-gate.ts — opt-in switch + gate wiring (binds the receipt to type + callback_url; trusted issuer keys via TENSORFEED_RECEIPT_TRUSTED_KEYS, with an inline-key fallback for setup-free demos)
  • src/index.ts — optional authorization_receipt arg + gate dispatch at the seam
  • src/types/require-receipt.d.ts — minimal ambient types (the package ships JS + JSDoc)
  • mcp-server/README.md — short opt-in usage note

Builds clean (tsc --noEmit) and the web test suite passes (83/83).

What this is / isn't

This is not auth ("who are you") or permissions ("are you allowed"). It's portable accountability evidence the service can keep for its own liability: proof a named human authorized an irreversible, paid action — a necessary, not sufficient condition. It does not prove the decision was wise or lawful. This is a reference implementation, not a vulnerability report — nothing here is exploitable today; it's an optional control you can switch on.

Spec: IETF Internet-Draft draft-schrock-ep-authorization-receipts (an I-D, not an RFC).


Happy to adjust the env var name, the assurance class, or which fields the receipt binds to before merge.

FutureEnterprises and others added 2 commits June 29, 2026 21:23
create_watch is the one tool in the MCP server that mutates the world and
spends a paid credit: it registers a 90-day webhook watch that POSTs to a
caller-supplied callback_url. Today nothing requires a human to have
authorized that exact callback_url + type.

This adds an OPT-IN authorization-receipt gate in front of it, built on
@emilia-protocol/require-receipt (Apache-2.0, fully offline, no API key or
account). It is OFF by default and byte-identical to current behavior unless
the operator sets TENSORFEED_RECEIPT_REQUIRED. When enabled, create_watch
refuses to register the watch (and never spends the credit) unless the call
carries an authorization_receipt bound to that exact type + callback_url; a
valid receipt is consumed one-time only after a successful registration.

- src/receipt-gate.ts: opt-in switch + gate wiring (binds receipt to
  type + callback_url; trusted-keys via env, inline-key fallback for demo)
- src/index.ts: optional authorization_receipt arg; gate dispatch at the
  create_watch seam (original POST + formatting unchanged)
- src/types/require-receipt.d.ts: minimal ambient types for the JS package
- README.md: opt-in usage note

Builds clean (tsc --noEmit), web test suite passes (83/83).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…keys; honest replay/manifest docs

Acts on external review (CodesWhat/portkey-admin-mcp#15). create_watch no longer falls back
to accepting self-signed (inline-key) receipts: with TENSORFEED_RECEIPT_REQUIRED=1 and no
TENSORFEED_RECEIPT_TRUSTED_KEYS, the gate fails closed (receipt_enforcement_misconfigured)
and never spends the credit; TENSORFEED_RECEIPT_ALLOW_INLINE_KEY=1 is an explicit non-prod
opt-in. Manifest only advertised when served (TENSORFEED_RECEIPT_MANIFEST_URL). Replay store
documented honestly (process-local default). tsc build clean.
@FutureEnterprises

Copy link
Copy Markdown
Author

Hardened based on external review feedback (cf. CodesWhat/portkey-admin-mcp#15): the gate now fails closed without a pinned trusted issuer key (no self-signed acceptance by default — set TENSORFEED_RECEIPT_TRUSTED_KEYS, or TENSORFEED_RECEIPT_ALLOW_INLINE_KEY=1 for non-prod demos), the manifest is only advertised when served, and replay-store durability is documented honestly (process-local by default). tsc build clean locally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant