Skip to main content

Request binding (transcript-v1)

Concept

A passport says who you are. Request binding proves what you are doing. transcript-v1 is a canonical, signed transcript of the request, so a passport can only be used for the exact request it was bound to. This is what makes a stolen passport useless for anything but an identical, in-window replay (which the replay layer then blocks too).

Implementation

  • Lives in pkg/proof (transcript.go).
  • The transcript binds: method, authority, path, query (normalized), selected headers (allow list), nonce, body hash, audience, route_id, passport jti, issued-at bucket, and key binding.
  • The proof is verified by the adapter/middleware before replay state is consumed.
  • Deny reasons:
    • request_binding_mismatch — the request does not match the signed transcript (tampered path/nonce/route, etc.).
    • invalid_request_proof — the proof signature is malformed or does not verify.

Example

The clearest end-to-end demonstration is the provider/gateway flow, which signs real requests and shows both allow and tamper-deny:

make demo-provider-gateway

It proves, among other things:

  • a broker-issued, transcript-v1-bound request allows;
  • changing the path after signing → request_binding_mismatch;
  • a corrupted proof → invalid_request_proof.

You can also produce signed request headers yourself (no broker) with trustplane sign, which can print a ready-to-run curl via --curl:

# Conceptual shape — sign a request and emit the headers/curl the adapter accepts
trustplane sign \
--private-key "$CLIENT_KEY" \
--method GET \
--path /orders \
--audience acme.demo.orders \
--route-id acme.demo.orders.read \
--curl

Send the produced request to the adapter and it verifies; tamper with the path and it denies.

Cross-language conformance

transcript-v1 must produce the same canonical string and digest in every language, or proofs signed by a non-Go SDK would not verify. The conformance suite checks this:

make transcript-conformance

This runs Go, JavaScript (scripts/conformance/transcript_v1_conformance.js), and Python (scripts/conformance/transcript_v1_conformance.py) over shared vectors and asserts identical output. It is part of the v0.1 acceptance gate.

What to notice

  • The binding covers the body hash, so you cannot swap the payload after signing.
  • It covers the route_id, so a proof for acme.demo.orders.read cannot be reused for a different route.
  • It covers an issued-at bucket and nonce, which (together with replay consume) bound the reuse window tightly.

→ Next: Replay protection.