Skip to main content

vaid-jwt-bridge

A token bridge for the VAID standard (https://github.com/solara-associates/vaid): it verifies a full VAID delegation chain and, only if verification fully passes, issues a short-lived standard JWT that a gateway can verify against a published JWKS using any ordinary JWT library — no VAID code in the gateway's request path.

This is deliberately not "VAID as a JWT end to end". A gateway holding one of these JWTs is trusting the bridge's verification, not any individual hop of the original chain. The normative profile is docs/jwt/v1/profile.md at the repository root; this package implements it.

What it checks before it issues anything

Reusing only vaid_mint primitives (same verification order as vaid_adk/vaid_a2a, re-derived rather than imported — see vaid_jwt_bridge/exchange.py):

  1. the leaf's trust-domain binding — its claimed trust_domain equals the domain its kernel_key_thumbprint is bound to in keys — and only then the leaf VAID's signature, against the key that thumbprint resolved to;
  2. the leaf's own expiry;
  3. each presented ancestor's own trust-domain binding, by the same rule, and then full chain integrity and attenuation back to a trusted root (vaid_mint.chain.verify_chain_at), which additionally requires consent on any cross-key hop;
  4. revocation of every VAID in the assembled lineage.

The order is load-bearing: step 1 reaches its verdict on the leaf alone before step 3 examines any presented document, so a leaf whose own signature does not verify is inauthentic regardless of what else was presented alongside it.

Any failure issues nothing. There is no partial or degraded JWT, and no fail-open default anywhere in this package — a deployment with no RevocationCheck configured cannot construct a working bridge at all.

Issued JWT shape

  • iss, aud, iat, exp, jti — ordinary JWT claims. exp is short (5 minutes by default) and is never later than the leaf's own expires_at — a leaf closer to expiry than the default TTL gets a JWT that expires exactly when the leaf does.
  • sub / act — an RFC 8693 actor-delegation chain built directly from the verified VAID chain: sub is the chain's root, and each further hop is one level of act, nested so the current (leaf) actor is outermost and the earliest delegate is most deeply nested — see profile §4 for why this mapping is total and unambiguous.
  • vaid — the leaf's own vaid_id, trust_domain, scope_boundary, capability_set, and chain_sha256: a hash over the canonical verified chain so an auditor holding the same documents can independently confirm what was verified.

Keys: EdDSA and ES256, selectable per deployment

Both Ed25519 (alg: EdDSA) and NIST P-256 (alg: ES256) signing keys are supported; a deployment configures whichever it wants (or both, for a rotation), and the JWKS at /.well-known/jwks.json publishes exactly the public keys configured. vaid_jwt_bridge.keys.Signer is a small seam specifically so a signer backed by a cloud KMS (which never puts the private key in this process at all) can be dropped in later without changing exchange() or the service.

from vaid_jwt_bridge import load_signer_from_file, KeyRing

signer = load_signer_from_file("signing-key.pem")  # Ed25519 or P-256 PEM
keyring = KeyRing([signer])

Trust anchor: a key is trusted for one domain

keys is a TrustedIssuers, and each kernel key in it is bound to the one trust domain it is trusted for (profile section 1). A document signed by a trusted key is accepted only if it also claims that key's domain.

Binding matters because without it a trusted key vouches for any domain a document cares to claim. Given two legitimately trusted issuers, A and B, a document signed by B's key while claiming A's trust_domain verifies perfectly against B's key, and the bridge signed a JWT whose sub/act/vaid claims named A's identity — which a downstream gateway then honoured on the bridge's word. No forged signature and no compromised key anywhere in the attack (GHSA-hfjh-v2x2-r58v).

Two spellings of the same configuration:

# From config: the VAID A2A extension's `trustedIssuers` shape
# (docs/a2a/v1/extension.md section 2.1), so one operator's trust
# configuration reads the same across A2A, ADK and this bridge.
keys = TrustedIssuers([
    {"trustDomain": "vaid.example",  "kernelPublicKey": "IVL40Zt5HSRF..."},
    {"trustDomain": "other.example", "kernelPublicKey": "My6-jSfLcyOz..."},
])

# From key bytes already held in the process.
keys = TrustedIssuers.from_raw_keys([("vaid.example", kernel_public_key)])

kernelPublicKey is decoded as strict unpadded base64url to exactly 32 bytes. A padded, non-canonical, out-of-alphabet, empty or wrong-length value raises TrustedIssuersConfigError, as do a non-object entry, an entry with no trustDomain, and the same key listed under two domains. One domain may hold several keys (an issuer rotating); one key may not hold two domains.

A bare vaid_mint.chain.KernelKeyMap or SingleKernelKey is not accepted as keys. Those resolve a key by thumbprint and cannot say which domain it is trusted for, so the binding could not be checked at all; BridgeConfig refuses one when the deployment is configured, so the gap cannot be left open by omission. Any resolver answering both resolve_key and trust_domain_for is accepted, so a KMS-backed one remains possible.

Library usage

from vaid_jwt_bridge import (
    ExchangeRequest,
    TrustedIssuers,
    exchange,
    load_signer_from_file,
)
from vaid_mint.revocation import InMemoryRevocationList

signer = load_signer_from_file("signing-key.pem")
result = exchange(
    ExchangeRequest(leaf=leaf_vaid, chain=(), audience="https://api.example.com"),
    keys=TrustedIssuers.from_raw_keys([("vaid.example", kernel_public_key)]),
    revocation=InMemoryRevocationList.assume_nothing_revoked(),
    issuer="https://bridge.example.com",
    signer=signer,
)

result is either Issued (access_token, expires_in, ...) or Denied (reason, detail) — never an exception for an ordinary verification failure. See examples/quickstart.py for a full 3-hop chain exchanged end to end and verified independently with PyJWT against the served JWKS.

HTTP service (optional, pip install vaid-jwt-bridge[service])

vaid_jwt_bridge.service.build_app builds a Starlette ASGI app exposing:

  • POST /token-exchange — RFC 8693 token exchange (grant_type=urn:ietf:params:oauth:grant-type:token-exchange, subject_token_type=urn:vaid:params:oauth:token-exchange:token-type:vaid-chain-v1).
  • GET /.well-known/jwks.json — the JWKS this deployment's KeyRing publishes.

The library (vaid_jwt_bridge.exchange) has no web-framework dependency; the service extra is only for running this bridge as its own process. Gateway-side configuration examples (agentgateway, Envoy AI Gateway) are in docs/jwt/v1/gateways.md.

Packaged conformance firewall

An installed copy can be checked without this repository:

vaid-jwt-bridge-conformance           # exit 0 = PASS, 1 = BLOCKER
python -m vaid_jwt_bridge.conformance  # same

It exchanges the frozen three-hop chain bundled in vaid-mint (chain_v1.json) at the instant that vector names, then verifies the issued JWT against the bridge's own published JWKS - the route a gateway uses - and asserts sub, the nested act, iss/aud, that exp never outlives the leaf's own expiry, and that vaid.chain_sha256 equals the frozen digest

daa5c6cf4b3d2d3692c53d3f781a9fc19c6ca1f011149c49639b6d5a865bdd8b

That digest is a byte claim, frozen as a literal: recomputing it with this package's own chain_sha256() would agree with the token even if both moved, which is a check that cannot fail. It then asserts seven denial paths, each with a distinct reason, and that an unrelated key does not verify the token. The issuing case is load-bearing: a bridge that refused every exchange would otherwise satisfy every denial check.

What this package does not do

  • It does not mint VAIDs. It only verifies a chain someone else minted.
  • It does not implement its own revocation backend; RevocationCheck is required configuration, supplied by the deployment (vaid_mint.revocation ships an in-memory one for tests and local development).
  • It does not cache issued tokens, and re-checks revocation on every exchange — see profile §7.

Metadata

Release files for vaid-jwt-bridge 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for vaid-jwt-bridge 0.2.1
File Size Uploaded Provenance
vaid_jwt_bridge-0.2.1.tar.gz 41.2 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for vaid-jwt-bridge 0.2.1
File Interpreter ABI Platform
vaid_jwt_bridge-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 73.1 kB

Release files / vaid_jwt_bridge-0.2.1.tar.gz

Download URL vaid_jwt_bridge-0.2.1.tar.gz
Size 41.2 kB
Tags Source
SHA-256 checksum
How to use checksums
54c79b112af57504f0a11fdb0917607c992b466b52da270ffc0d0b396fa694f0
BLAKE2b-256 checksum
How to use checksums
96235791f32acf3711539d34484f0e58bb3f9fb4bdfa4ec22598397324fceb7b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.12.15

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release files / vaid_jwt_bridge-0.2.1-py3-none-any.whl

Download URL vaid_jwt_bridge-0.2.1-py3-none-any.whl
Size 31.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
16e71a9f5248a344594da446d4d13d2355296a9b483a3f89e62798afcbaee7aa
BLAKE2b-256 checksum
How to use checksums
d3f45612ffd3f91d95ea5ddfadfb0dcfaaba99a67c931fa6b0c554d55da0e600
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.12.15

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Security for vaid-jwt-bridge 0.2.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for vaid_jwt_bridge-0.2.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page