in reviewhumanPR #52Auth
varuni7: delegatable capability tokens with cascading, partition-tolerant revocation
Problem 4 (auth). The default jwt plugin cannot delegate a subset of a
Description
The pitch.
# [Hackathon] varuni7: delegatable capability tokens with cascading, partition-tolerant revocation
**Problem 4 (auth).** The default `jwt` plugin cannot delegate a subset of a
capability, and revokes by exact token string with no parent/child relationship.
This ships `delegatable`: macaroon-style tokens where a holder mints a strictly
narrower, shorter-lived sub-token for another agent **offline** (no issuer
round-trip), and revoking a token severs its entire subtree.
## Design
- **Macaroon HMAC chain.** A child seal is `HMAC(parent_seal, caveat)`, so a
holder can only *add* restrictions (subset scopes, shorter TTL, bound
audience) — never widen them. `delegate()` also checks the scope subset and
clamps child TTL ≤ parent.
- **Cascade by construction.** Revocation is keyed on the seal, not the token
string. `verify()` recomputes the chain, and a revoked ancestor seal severs
every descendant — no per-child revocation list.
- **Partition-tolerant revocation.** Revocation state is a grow-only set
(G-Set CRDT). `merge` is the set union — commutative, associative,
idempotent — so replicas converge by gossip regardless of order, duplication,
or loss. Safety is monotone (a seal once observed revoked is never accepted
again), and a verifier that has not yet heard a revocation still accepts until
it merges one.
- **Determinism.** No `uuid`, no `time.time()`. Token identity is the seal (a
pure function of content + secret); all time comes from an injected clock.
Same seed → byte-identical trace.
## Attacks defeated (adversarial validator)
The `delegated_auth` scenario + validators PASS on `delegatable` and FAIL on
`jwt`. Three attack classes are blocked, each a typed error:
1. **Scope escalation** — child requests a scope the parent lacks → `ScopeEscalationError`.
2. **Stale / revoked parent** — delegate or verify through a revoked ancestor → `RevokedAncestorError`.
3. **Audience confusion** — token presented by an agent that is not its audience → `AudienceMismatchError`.
Plus a **convergence** check: after the revoked seal is gossiped, the revoked
intermediary can no longer mint tokens while its siblings still can — proof the
revocation propagated via CRDT merge, not just on the coordinator.
## Verify it
```bash
uv sync
nest run scenarios/delegated_auth.yaml -o traces/delegated_auth.jsonl
python -c "
from pathlib import Path
from nest_core.validators import validate_trace
for r in validate_trace(Path('traces/delegated_auth.jsonl'), 'delegated_auth'):
print(('PASS' if r.passed else 'FAIL'), r.name, '-', r.detail)
"
# Swap `auth: delegatable` -> `auth: jwt` in the YAML and the same four validators FAIL.
```
## Tests
- 51 new tests: example-based + `hypothesis` property tests (scope-subset,
cascade over random chains, CRDT merge laws, determinism) + the 3 attacks +
partition convergence, for both the plugin and the validators.
- Full suite: **592 passed, 1 skipped**. `make ci-local`: all 5 checks pass
(`ruff`, `ruff format`, `pyright` strict, `pytest`).
## Files
- `packages/nest-plugins-reference/nest_plugins_reference/auth/delegatable.py` — plugin
- registered in `plugins.py` `_BUILTINS` **and** `nest-plugins-reference/pyproject.toml` entry point
- `packages/nest-core/nest_core/scenarios_builtin/delegated_auth.py` + `scenarios/delegated_auth.yaml`
- 4 validators in `nest_core/validators.py`
- `docs/layers/auth.md`
- tests: `test_delegatable_auth.py`, `test_delegated_auth_scenario.py`
— with Claude Code
Try it
Open PR on GitHubView diffCheckout locally
git fetch origin pull/52/head:pr-52
git checkout pr-52