Repository navigation
Implement draft-02: JWT chain encoding, shared chain model, 0.6.0 prep - #8
Merged
Merged
Conversation
V3, V4 and V5 now have one implementation that works on structured link values. Both encodings will parse into it. Also adds TokenError.wire_code, which always returns the aip_-prefixed code the draft uses.
Each link is a JWS signed by the key the previous link named in cnf, and carries the SHA-256 of the previous link. Delegator identity is therefore authenticated, unlike the Biscuit encoding. Verification runs V1 here and V3 to V5 on the shared chain model.
Adds detect_format so bindings can pick a verifier. CompactToken is kept and marked deprecated.
Any prefix of a valid chain also verifies and carries wider authority, so a relying party that needs attenuation to hold against downstream holders must check proof of possession against the leaf link's cnf key. leaf_holder_key() exposes it. verify now raises only TokenError for non-string tokens and bad root keys, and cnf jwk x must be strict base64url.
The regex attenuation walk is gone; V3, V4 and V5 now run in chain_model.verify_links for both encodings. Domains are encoded as facts only, since a Datalog check would reject every request that names no domain, and are checked structurally.
An empty tools or domains list meant different things by encoding: Biscuit wrote no facts, so it inherited the parent value, while JWT denied every domain. It is now malformed in both encodings, at verify_links and at mint time. Biscuit links() also rejects repeated single-valued facts, and forged block tests cover domain widening, extended expiry and max_depth in a delegation block.
The spec says base64url; Python decoded standard base64, so roughly half of spec-conformant signatures failed. Standard base64 is still accepted for documents written by earlier releases. Rust (hex) is unchanged and tracked in the draft's implementation status section.
Bindings now pick a verifier by token format. The proxy applies V2 root binding and the deployment audit to JWT chains the same way it does to Biscuit chains.
The same forged-delegation cases run through Biscuit and JWT, so the two encodings cannot drift apart while their own tests keep passing.
A review of draft-prakash-aip-02 found that CompactToken.verify never read the JWT header, so a plain EdDSA JWT with typ JWT or no typ, signed by the root key, was accepted by the MCP middleware. Section 5.1.4 limits legacy acceptance to typ aip+jwt. Verify now rejects any other typ as token_malformed before decoding.
A review of draft-prakash-aip-02 found that links() turned a missing identity fact on block 0 into an empty issuer, and a missing delegator or delegate on a delegation block into an empty or null party, with nothing rejecting it. Table 1 makes issuer required for all links and delegate required for delegation links, so a block missing a required fact is now token_malformed, checked next to the duplicate-fact rejection.
Bump version to 0.6.0 (pyproject and aip_core.__version__), add the 0.6.0 changelog entry, add a JWT chain README example with the truncation and proof-of-possession warning, and refresh the test count.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
sunilp
added a commit
that referenced
this pull request
Oct 10, 2026
Bring draft-02 implementation (PR #8) onto master
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implementation work behind draft-prakash-aip-02. Stacked on #7; retarget to master once that merges.
What changed
aip_token/chain_model.py). V3, V4 and V5 now have one implementation, working on structured link values instead of regexes over Datalog text. Both encodings parse into it.aip_token/jwt_chain.py). Each link is an EdDSA JWS signed by the key the previous link named incnf, joined with~. Media typeapplication/aip-chain+jwt, typaip-link+jwt. A single -01 compact token verifies as a one-link chain.typ aip+jwt.cnfkeys must be strict base64url.TokenErrorinstead of escaping as a 500.Known limitations (stated in the CHANGELOG, README and docstrings)
cnfkey. The proxy, middleware and A2A verifier do not check it yet. Biscuit chains are not affected.cnfkey belongs to itssubidentifier.Tests
CI cannot run until the Actions billing lock on the account is cleared. Do not tag
aip-core-v0.6.0until -02 is posted.