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):
- the leaf's trust-domain binding — its claimed
trust_domainequals the domain itskernel_key_thumbprintis bound to inkeys— and only then the leaf VAID's signature, against the key that thumbprint resolved to; - the leaf's own expiry;
- 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; - 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.expis short (5 minutes by default) and is never later than the leaf's ownexpires_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:subis the chain's root, and each further hop is one level ofact, 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 ownvaid_id,trust_domain,scope_boundary,capability_set, andchain_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'sKeyRingpublishes.
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;
RevocationCheckis required configuration, supplied by the deployment (vaid_mint.revocationships 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)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| vaid_jwt_bridge-0.2.1.tar.gz | 41.2 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.