in reviewhumanPR #100Auth
Add delegatable capability tokens with cascading revocation (auth layer)
The default jwt plugin can't delegate — there's no way for an agent
Author
@rudranaresh0201
github profile →- Status
- In review
- Opened on
- Jul 8
- Branch
- hackathon/rudra-auth-delegation
Description
The pitch.
The default jwt plugin can't delegate — there's no way for an agent holding a token to hand a scoped, temporary copy to another agent without going back to the issuer, and revoking a token has zero effect on anything derived from it, because nothing tracks that relationship. This adds a macaroon-style plugin instead: each delegated token is signed using its parent's own signature as the key, so the whole chain gets re-verified back to the root every time. That's what makes revocation cascade for free — kill the root and every token minted under it stops verifying, without maintaining a list of descendants anywhere. Includes tests for the three ways this can be attacked (claiming more scope than you were given, a token outliving its revoked/expired parent, a token being used by the wrong agent) with the same attacks run against the plain jwt plugin to show it has no defense against any of them. Also ships a 16-agent scenario that runs a real delegation tree through the simulator and shows revoking one branch only kills that branch, while revoking the root kills everything. Solves docs/hackathon/problems/04-auth-capability-delegation.md. --- **Update:** rebased on `main` and addressed review feedback. Renamed `delegatable` → `chained_capability` (plugin key, scenario name, and module path) since PR #138 merged an implementation under the original names while this PR was open — this is an independent, functionally different design, not a resubmission of the same idea. Three concrete differences from the merged reference plugin, since problem 04 has ~20 entries and it's worth being specific about what's actually different here rather than just restating the mechanism: 1. **Audience binding can't be skipped.** The merged reference plugin splits `verify()` (no audience check) from `verify_presented()` (checks it) — a caller can still pick the wrong one and the wrong-agent-presents-the-token attack goes unguarded. Here, presenting a *delegated* (non-root) token through `verify()` without `expected_audience` is itself a rejection (`AudienceMismatchError`), not an optional parameter a caller can forget. Root tokens are unaffected — there's no delegation hop to confuse there. 2. **The logical clock never reads real time, under any code path.** The merged reference plugin's `_now()` falls back to `time.time()` if `clock` was never explicitly set, so it can go non-deterministic silently. This plugin's clock starts at `0.0` and has no fallback — a forgotten `set_clock()` call fails loud (wrong tick) instead of quietly breaking trace reproducibility. 3. **The adversarial checks are wired into the real judging path, not just a private test file.** The scenario's `CoordinatorAgent` now runs a live probe of all three named attacks (scope escalation, stale ancestor, audience confusion) and emits a `delegation_audit` event per attack. `validate_chained_capability_*` functions are registered under `VALIDATORS["chained_capability_delegation"]` in `nest_core/validators.py`, so the leaderboard's own validator runner sees these checks directly from a trace. (Notably, the merged reference PR's `delegation_validators.py` functions are exported from its `validators/__init__.py` but never actually registered in `nest_core.validators.VALIDATORS` — confirmed by grepping `main` — so its own adversarial checks aren't reachable by the validator runner either.) All five local CI stages green (`uv sync`, `ruff check`, `ruff format --check`, `pyright`, `pytest -v`: 1174 passed, 1 skipped).
Try it
Open PR on GitHubView diffCheckout locally
git fetch origin pull/100/head:pr-100
git checkout pr-100