Trust Envelope — v1
Normative schema for BrainstormRouter's EdDSA-signed agent identity JWT. Carries principal, budget, scope, trust, observability, and test/sandbox markers in a single signed payload.
Status: Draft 1 · published 2026-05-16 · canonical URL https://docs.brainstormrouter.com/specs/trust-envelope-v1
Authors: BrainstormRouter License: CC BY 4.0 (specification) — reference implementations under their respective package licenses
This specification is normative. The keywords MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are used per RFC 2119.
---
1. Abstract
The Trust Envelope is a JSON Web Signature (RFC 7515) that BrainstormRouter mints on every authenticated request. It bundles the principal identity, budget posture, allowed scope, trust signals, observability requirements, and test/sandbox markers into a single signed payload that downstream gates (routing, budget, guardrails) read instead of querying the auth row, budget store, reputation engine, and anomaly engine independently.
The envelope is the single decision contract for every downstream gate. It is sourced once, signed once, and every gate reads the same view — eliminating the class of bugs where two gates disagree because one read fresh state and one read cached state.
The envelope is JOSE-aligned (RFC 7519 / RFC 8725) so future IETF agent-JWT drafts and W3C VC 2.0 issuance are additive, not breaking.
2. Token format
A Trust Envelope is a JWS with:
2.1 Header
{
"alg": "EdDSA",
"typ": "JWT",
"kid": "<envelope-key-id>"
}
alg: MUST beEdDSA(Ed25519). Other algorithms are out of scope for v1.typ: MUST beJWT.kid: MUST identify the active signing key from the issuer's JWKS.
2.2 Payload — top-level claims
| Claim | Type | Required | Notes |
|---|---|---|---|
iss | string | yes | MUST be the literal "brainstormrouter" for envelopes minted by BR |
sub | string | yes | SPIFFE URI for agents/mTLS, user:{userId} for humans, tenant:{tenantId} fallback |
iat | number | yes | Issued-at, seconds since epoch |
exp | number | yes | Expiry, seconds since epoch. exp - iat SHOULD be ≤ 300 (5 minutes) |
jti | string | yes | Request UUID, unique per envelope |
2.3 Payload — br_principal
Identity, organization, and delegation lineage.
br_principal: {
agent_id: string | null; // null when caller is human
user_id: string | null; // null when caller is an agent
org_id: string; // always present
parent_chain: Array<{
type: "agent" | "user" | "system";
id: string;
ts: number; // ms epoch when delegation occurred
}>;
auth_method: "api_key" | "agent_jwt" | "mtls" | "supabase_jwt";
}
Exactly one of agent_id or user_id MAY be null. The other MUST be set. org_id is always non-empty. parent_chain MAY be empty in v1.0 (delegation lineage lands in a later draft).
2.4 Payload — br_budget
Authoritative spend posture for the request.
br_budget: {
period: "request" | "session" | "day" | "month";
cap_usd: number; // non-negative
spent_usd: number; // non-negative; MUST be ≤ cap_usd
hard_stop_at: number; // ms epoch; request MUST be rejected after this instant
}
hard_stop_at is the deadline for the entire request, not just the budget window. Implementations MUST reject any operation that would extend the request past this timestamp.
2.5 Payload — br_scope
Allowlists. Wildcard semantics are explicit; empty arrays are NOT wildcards.
br_scope: {
providers: string[]; // [] = no provider restriction (v1.0 transitional)
models: string[] | "*"; // "*" = wildcard; [] = DENY ALL
tools: string[] | "*"; // "*" = wildcard; [] = DENY ALL
regions: string[] | "*"; // "*" = wildcard; [] = DENY ALL
}
Empty array vs wildcard (locked 2026-05-13, issue #296):
- For
models,tools, andregions: an empty array[]MUST be treated as deny all. Wildcard is the literal string"*". - For
providers: an empty array[]MAY be treated as "not yet load-bearing" in v1.0 (transitional semantics for envelopes minted before provider scope was enforced). The bridged token form (see MCP-OAuth Bridge §4.3) uses stricter deny-all semantics.
2.6 Payload — br_trust
Trust posture and risk signals.
br_trust: {
tier: "platinum" | "gold" | "silver" | "bronze" | "restricted";
mtls_fingerprint: string | null;
attestation_hash: string | null; // TEE attestation; reserved
anomaly_score: number; // 0..1, in-process anomaly engine
reputation: {
successful_calls: number;
failed_calls: number;
last_anomaly_at: number | null;
};
xdr_risk?: number; // 0..1, optional external XDR signal
}
tier is the canonical 5-tier reputation enum. Implementations MUST NOT introduce additional tier values.
anomaly_score is computed by the issuer's in-process anomaly engine and reflects per-request signals. xdr_risk is sourced from external XDR systems and is optional; consumers SHOULD treat its absence as 0.0.
2.7 Payload — br_observability
Logging and audit requirements that downstream consumers MUST honor.
br_observability: {
trace_required: boolean;
fields_to_capture: string[]; // e.g., ["model", "tool", "cost"]
retention_days: number; // non-negative integer
redaction_policy: "none" | "pii-redacted" | "full-redacted";
}
Consumers MUST apply redaction_policy BEFORE writing to storage. Logging fields outside fields_to_capture is permitted (e.g., for debugging) but those fields MUST NOT be retained past retention_days.
2.8 Payload — br_test
Sandbox isolation markers.
br_test: {
tier: "production" | "sandbox";
isolation_marker: string | null; // tag for filtering sandbox traffic from dashboards
}
Envelopes with tier: "sandbox" MUST NOT be charged against production budgets or counted in production observability metrics. Implementations SHOULD honor isolation_marker as an opaque filter tag.
3. Signing
Envelopes MUST be signed with EdDSA using an Ed25519 keypair. The issuer publishes its public key set via JWKS at:
GET https://api.brainstormrouter.com/.well-known/jwks.json
Consumers MUST refresh the JWKS at least every 24 hours and SHOULD cache it with a TTL of ≤ 1 hour.
Key rotation follows the standard JWKS rollover pattern: the issuer publishes both old and new keys for at least one cache cycle before retiring the old key. Consumers MUST verify against any key in the current JWKS.
4. Verification
A consumer verifying a Trust Envelope MUST perform, in order:
- Header check:
alg = "EdDSA",typ = "JWT",kidpresent in current JWKS. - Signature verification: against the public key identified by
kid. - Temporal check:
iat ≤ now < exp. Clock skew tolerance SHOULD be ≤ 30 seconds. - Issuer check:
iss = "brainstormrouter"(for envelopes minted by BR). - Schema check: the payload conforms to the structure in §2.
If any check fails, the consumer MUST reject the request. Logging "verified but treating as unverified" is non-conforming — there is no partial trust.
5. Lifetime and replay
exp - iat SHOULD be ≤ 300 seconds. Consumers MUST reject envelopes whose declared lifetime exceeds this bound, EVEN IF the envelope is still within its own exp.
Consumers SHOULD maintain a jti cache with TTL ≥ envelope max lifetime to reject replays. The cache MAY be in-process; a distributed cache is required only for multi-instance consumers that share state.
6. Versioning
This specification follows semantic versioning:
- Major (v2, v3, …) — breaking claim renames, format changes, or removal
- Minor (v1.1, v1.2) — new optional claims (no required claims added)
- Patch (v1.0.1) — clarifications, typo fixes, additional test vectors
New required top-level claims trigger a major version bump. New optional claims may be added in a minor version; consumers MUST tolerate unknown claims without error.
The current version is v1.0.0-draft-1. v1.0.0 final ships once:
- At least two independent implementations pass the conformance vectors
- One third-party agent runtime has validated against the spec
7. Reference implementations
| Language | Repository | License |
|---|---|---|
| TypeScript | @brainstormrouter/trust-envelope on npm | MIT |
| Python | brainstormrouter-trust-envelope on PyPI | MIT |
| Go | github.com/brainstormrouter/trust-envelope-go | MIT |
Each reference implementation MUST pass the conformance test vectors at specs/trust-envelope-v1-test-vectors.json (forthcoming).
The canonical TypeScript schema (Zod) is at src/security/trust-envelope/schema.ts in the BrainstormRouter source tree. Reference implementations in other languages MUST produce envelopes that round-trip through this schema.
8. Security considerations
- Token theft: an envelope is a bearer token for its 5-minute lifetime. Transport MUST be TLS 1.3+. Consumers SHOULD bind envelopes to a session fingerprint (mTLS cert hash, client IP / ASN, or similar) and reject mismatches.
- Budget inflation: a malicious issuer could mint envelopes with inflated
br_budget.cap_usd. Consumers MUST treat the envelope as advisory and verify spend against an authoritative ledger (e.g., a Redis atomic reserve) before charging. - Tier downgrade attacks: an attacker who compromises the issuer's signing key could issue envelopes with elevated
br_trust.tier. Mitigation: short key lifetimes + hardware-backed key storage (HSM/KMS) + rapid rotation on compromise. - Anomaly score gaming:
anomaly_scoreis issuer-controlled. Consumers SHOULD treat it as one input among several, not the sole decision input. - Sandbox contamination: envelopes with
br_test.tier: "sandbox"MUST be filtered from production metrics. Failing to filter creates a contamination class where sandbox traffic skews bandits, budgets, and dashboards.
9. References
- RFC 2119 — Key words for use in RFCs to Indicate Requirement Levels
- RFC 7515 — JSON Web Signature (JWS)
- RFC 7519 — JSON Web Token (JWT)
- RFC 8725 — JSON Web Token Best Current Practices
- RFC 9068 — JWT Profile for OAuth 2.0 Access Tokens
- SPIFFE Identity Specification
- Trust Envelope concept guide — non-normative narrative
- MCP-OAuth Bridge v1 — companion spec
Changelog
- 2026-05-16 — Draft 1 published. Defines the 6-claim payload structure (
br_principal,br_budget,br_scope,br_trust,br_observability,br_test), the 5-tier reputation enum, and the deny-by-default semantics for empty-array scope fields.