Small surface.
Explicit limits.
Security invariants
- GET preparation is side-effect-free with respect to the action and performs no outbound fetch.
- POST commit is the only dispatch route. GET, HEAD, OPTIONS, and declared prefetch do not dispatch.
- Hosted dispatch is compile-time limited to the same-project harmless demo target.
- Every DNS answer must be global. The selected address is pinned while Host, SNI, and certificate checks retain the hostname.
- No caller-controlled headers, redirects, retries, compression, protocol upgrades, response streaming, or copied upstream headers.
- Application code never logs request URLs, bodies, intents, digests, or response payloads.
Privacy boundary
Prepare URL input is public and may be retained by browsers, networks, CDN infrastructure, or Vercel even though application payload logging is absent. Never submit secrets or personal data. Key-name blocking is hygiene, not reliable secret detection.
Impossibility and residual risk
There is no generally safe, public, anonymous, arbitrary side-effecting GET relay. A scanner can fetch an ordinary GET, and no header reliably proves human intent. IntentLatch resolves this by making GET inert and requiring POST to commit.
Hosted v1 does not claim to be a human approval interface. Its JSON is inspectable protocol data, not proof of what a person saw or authorized. A future confirmer must render the canonical destination, method, expiry, request ID, digest, and body from the exact commit envelope it sends; an unbound display label creates a substituted-context approval bug.
Hosted v1's deterministic digest confirms bytes only; it does not authenticate or authorize the caller. Anyone who obtains the commit envelope can submit it during its validity window.
Without an atomic external store, hosted v1 cannot guarantee one-use, durable replay prevention, rate accounting, or exactly-once execution. The stable idempotency key is the caller's request_id; it only helps if the destination atomically binds that ID to the first accepted body/digest and rejects conflicting reuse. A timeout means the outcome is unknown. The strongest remaining failure mode is duplicate downstream execution if a caller repeats a valid POST and a future destination fails to enforce the idempotency key. Reusing one request ID with conflicting bodies must be rejected, not resolved by choosing a winner.
A real endpoint requires atomic first-prepare binding, an unguessable one-time capability returned only in that first response, expiry/replay state, and conflicting reuse rejection.
Production scope
The only destination is a same-project demo that echoes a bounded message and deterministic receipt. It never messages, posts, emails, deletes, spends, uploads, or changes accounts. Arbitrary targets fail closed.
Report a vulnerability
Open a GitHub security advisory. Do not include secrets, private data, exploit payloads aimed at third parties, or tests against arbitrary destinations.