Converge implementations on the draft-01 canonical encoding - #3
Merged
Conversation
The two reference implementations rejected each other's chained tokens in
both directions whenever a delegation block was present. Rust emitted
`check if tool($t), [...].contains($t)` and `check if budget($b), $b >= N`;
Python emitted `check if right("...")` per scope and `$b <= N`, then
compensated with a block-source scan. Each matched the spec on one axis and
neither on both.
Both now emit the canonical encoding from draft-prakash-aip-01 Section 3.4.1
and verify with the algorithm in Section 4:
- One self-contained `check if` per block. Scope narrowing follows from
conjunction, so widening authorizes nothing.
- Budget is a `budget_ceiling` fact, not a check. A Datalog check cannot
express "narrower than my parent": Python's verifier injected budget(0),
making every such check vacuous, while Rust injected nothing and evaluated
against the authority's ceiling, rejecting valid chains.
- V4 structural attenuation walk at verification, covering scope, budget,
expiry and principal invariance. Mint-time checks are a convenience for the
delegator; an attacker appending a block does not run them.
Wildcard scopes worked in neither implementation. `tool:*` compared literally,
so a wildcard authority did not authorize `tool:search`. The existing three-hop
test used `tool:*` but only asserted depth, never authorizing. Wildcards now
compile to `starts_with` clauses joined with `or`.
The check must stay self-contained. A rule-based encoding does not attenuate:
a delegation block declaring a narrower rule is unioned with the authority
block's broader rule of the same name, and the broader one then satisfies the
delegation's check. Verified against biscuit-auth before choosing the form.
Conformance tests now exercise delegation in both directions, and run in CI on
push and pull request. They previously lived outside CI's working-directory and
skipped silently when biscuit-python was absent, which is how this drifted.
Python 131 passed, Rust 72 passed, conformance 6 passed.
The markdown specs still described draft-00: a budget Datalog check, no verification algorithm, and canonical templates neither implementation followed. That drift is what let the two implementations diverge. - New spec/aip-verification.md carrying the V1 to V7 algorithm, the error code for each failure, and the reason the container format does not establish attenuation on its own. - aip-tokens.md: budget is a budget_ceiling fact, declared and verified structurally, never a Datalog check, and never an ambient fact the verifier supplies. Block fact tables gain principal and drop the budget check. - aip-tokens.md section 7: the Simple profile now carries the full canonical block encoding and matches scopes by exact string equality. Scope patterns move to the Standard profile, which is where curated Datalog is already permitted, so wildcard support contradicts nothing in the published draft. - Standard profile gains a normative requirement that a block's scope constraint be a single self-contained check. Rules shared across blocks do not attenuate: the authority block's rule stays in scope and is unioned with a delegation block's narrower rule of the same name, and the broader one then satisfies the delegation's own check. - aip-delegation.md: principal invariance, and mint-time checking is called out as insufficient on its own. - aip-bindings-mcp.md defers to the algorithm rather than restating it. - README points at draft-01. Python 131 passed, Rust 72 passed, conformance 6 passed.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 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 |
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.
The Rust and Python reference implementations rejected each other's chained tokens in both directions whenever a delegation block was present. Reproduced before the fix:
delegation block 1 does not grant right("tool:search")Check n°1 in block n°1: check if budget($b), $b <= 100Each implementation matched the spec on one axis and neither on both. Rust emitted the canonical tool check but an inverted budget check; Python emitted the canonical budget direction but
check if right(...)per scope, plus a block-source scan to compensate.What this changes
Both now emit the canonical encoding from draft-prakash-aip-01 §3.4.1 and verify with the algorithm in §4.
check ifper block. Scope narrowing follows from conjunction, so a widened block authorizes nothing.budget_ceilingfact, not a check. A Datalog check cannot express "narrower than my parent": Python's verifier injectedbudget(0)making every such check vacuous, and Rust injected nothing so the check bound to the authority's ceiling and rejected valid chains.Wildcards
tool:*worked in neither implementation. It compared literally, so a wildcard authority did not authorizetool:search.test_delegation_chain_three_hopsusedtool:*but only asserted depth and never authorized, so it never caught this.Patterns now compile to
starts_withclauses joined withor, under the Standard profile. The Simple profile keeps exact string equality exactly as draft-01 §3.4.1 prints it, so nothing here contradicts the published draft.Security note
A block's scope constraint must be a single self-contained check, never a shared rule name. Rules do not attenuate: the authority block's rule stays in scope and is unioned with a delegation block's narrower rule of the same name, and the broader one then satisfies the delegation's own check. Verified against biscuit-auth before choosing the encoding, and now normative in
spec/aip-tokens.md§7.2.Why nothing caught this
The cross-language conformance tests at
tests/conformance/were outside CI'sworking-directory: python, CI only fired onaip-core-v*tags, and the tests skipped silently without biscuit-python. The Python to Rust test also used an authority-only token, so it would not have exercised delegation even if it ran.All three now fixed: the tests exercise delegation in both directions, a new workflow runs them on push and pull request, and the job fails outright if biscuit-python is missing rather than skipping.
Verification