in reviewhumanPR #156Auth
hackathon/mwarsss-delegatable-auth
Implements Problem 04 → Auth Capability Delegation from the NandaHack charter. Adds a DelegatableAuth plugin that extends the existing Auth protocol surface with HMAC-chained capability tokens, cascading revocation, and audience-binding.
Author
@mwarsss
github profile →- Status
- In review
- Opened on
- Jul 10
- Branch
- hackathon/mwarsss-delegatable-auth
Description
The pitch.
## Summary Implements **Problem 04 → Auth Capability Delegation** from the NandaHack charter. Adds a `DelegatableAuth` plugin that extends the existing `Auth` protocol surface with HMAC-chained capability tokens, cascading revocation, and audience-binding. - **Plugin** → `DelegatableAuth` (auth layer, name `delegatable`). Extends the `Auth` protocol (`issue` / `verify` / `revoke`) with an additive `delegate(parent_token, audience, scopes, ttl, *, caller) → Token` method. No existing callers are modified. - **Four attacks blocked** → scope escalation, stale-parent revocation, audience confusion at `verify()`, and audience confusion at `delegate()`. Each attack raises a distinct typed exception (`ScopeEscalationError`, `RevokedAncestorError`, `AudienceConfusionError`), all `ValueError` subclasses for drop-in compatibility. - **HMAC chain** → based on Birgisson et al. 2014 (Macaroon construction). Each child token's signing key is derived from the parent's HMAC output, so revoking a parent transitively invalidates every descendant without a per-child revocation list. - **Audience binding** → both `verify(token, *, caller)` and `delegate(token, audience, scopes, ttl, *, caller)` enforce the audience check unconditionally on audience-bound tokens. Omitting `caller=` on a delegated token is rejected at both call sites — the same as passing a wrong caller. A gap where `verify()` silently accepted audience-bound tokens when `caller=` was omitted was found and closed in the same review cycle as the `delegate()` fix. - **Three adversarial validators** → `check_scope_escalation_blocked`, `check_stale_parent_blocked`, `check_audience_confusion_blocked` in `nest_plugins_reference/validators/auth_validators.py`, returning `ValidatorReport` (same type as `gossip_validators.py`). Each validator is proven against a JWT baseline that deliberately fails. - **Scenario** → 16-agent two-level tree: 1 coordinator → 3 intermediaries → 12 leaves. Scenario YAML at `scenarios/delegated_auth.yaml` with `layers.auth: delegatable`. - **Wiring** → `_BUILTINS` in `plugins.py`, `_try_load_builtin` in `scenarios.py`, and a `[project.entry-points."nest.plugins.auth"]` entry in `pyproject.toml`. ## What differentiates this from other Problem 04 submissions Most open PRs on Problem 04 implement a single signature scheme or a flat token list. This submission is distinct on three concrete axes: 1. **Key-chain revocation without a per-child revocation list.** Revoking the root invalidates every descendant transitively because each child's signing key is the HMAC of its parent's signature — there is no registry of child IDs to maintain. The mechanism is O(depth) to verify, O(1) to revoke. 2. **Typed, ValueError-compatible exceptions.** `ScopeEscalationError`, `RevokedAncestorError`, and `AudienceConfusionError` are all `ValueError` subclasses. Callers that catch `ValueError` get correct behaviour for free; callers that want to distinguish attack types can catch the specific subclass. 3. **Validators proven against a JWT baseline.** Each of the three adversarial validators (`check_scope_escalation_blocked`, `check_stale_parent_blocked`, `check_audience_confusion_blocked`) is run against both `DelegatableAuth` (must pass) and the reference `JwtAuth` (must fail). This dual-proof structure means the validator itself is tested, not just asserted. ## Fixes since the original submission (PR #103) Maintainer review raised four issues; all are resolved in this PR. A fifth gap was found and closed during the same review cycle: | # | Issue | Fix | |---|-------|-----| | 1 | Startup crash — factory passed plugin class, not instance | `delegated_auth_factory` now instantiates `DelegatableAuth(clock=0.0)` using the same `isinstance(cls, type)` guard as `marketplace.py` and `empic_payments.py` | | 2 | Non-deterministic token IDs (`uuid4()`) | Replaced with per-instance monotonic counter (`tok-0001`, `tok-0002`, …) | | 3 | Non-deterministic timestamps (`time.time( …
Try it
Open PR on GitHubView diffCheckout locally
git fetch origin pull/156/head:pr-156
git checkout pr-156