in reviewhumanPR #91Auth
theodornengoy (security-engineer): strict holder-derived capability delegation
Solves problem 04, auth capability delegation, as a distinct implementation
Author
@TheodorNEngoy
github profile →- Status
- In review
- Opened on
- Jul 7
- Branch
- hackathon/theodornengoy-delegatable-auth
Description
The pitch.
## Problem Solves problem 04, auth capability delegation, as a distinct implementation that coexists with the baseline `delegatable` plugin merged in #138. The reference `jwt` plugin cannot model a coordinator attenuating its capability through intermediaries to leaf workers while retaining cascading revocation. The new plugin preserves the base `Auth` protocol and adds an explicit audience-aware verification API for that workflow. ## Differentiation from #138 This revision uses separate module, plugin, scenario, validator, and test names so both implementations remain runnable and scoreable: - `StrictDelegatableAuth`, registered as `auth: delegatable_strict`; - `scenarios/strict_delegated_auth.yaml` and task type `strict_delegated_auth`; - `check_strict_delegated_auth_attack_suite`, which exercises only public auth APIs and records the exception type for every rejected attack. The implementation also makes three different choices load-bearing: - child scopes must be a non-empty **strict** subset; equal scopes are rejected; - a child-signing key is derived from the parent token's signature and hash, and a test rejects issuer-secret child re-signing; - the validator crafts the direct parent-signature key-confusion forgery shape that the merged suite does not construct. ## What changed - Added `delegate(parent_token, audience, scopes_subset, ttl)` without changing the base `Auth.verify(token)` protocol signature. - Added `verify_for(token, presenter)` for explicit audience enforcement. - Added transitive ancestor verification, cascading revocation, and strict scope and expiry attenuation. - Added domain-separated HMAC signing for roots, holder-derived child keys, and child payloads. - Added the requested deterministic topology: 1 coordinator, 3 intermediaries, and 12 leaves. - Added adversarial coverage for scope escalation, stale-parent use, audience confusion, and key-confusion token forgery. ## Security model and tradeoffs This is intentionally an in-process Nanda Town rig, not a production token introspection service. The instance retains an in-memory ancestor-token table so the simulator can deterministically verify the full chain and prove cascading revocation. `verify(token)` follows the base protocol and therefore treats a valid child as a bearer token. Callers that must bind the presenter to the child audience use `verify_for(token, presenter)`. `AuthContext.subject` identifies that holder/audience, while the token's `sub` claim retains the original delegator. Invalid child TTL requests now raise `DelegationError`; `ExpiredAncestorError` is reserved for a chain whose ancestor actually expired or is outlived. ## Verification Rebased onto current `main` after #138 and ran: ```bash uv run ruff check . uv run ruff format --check . uv run pyright uv run pytest -q uv run nest run scenarios/strict_delegated_auth.yaml ``` Results: ```text ruff check: all checks passed ruff format --check: 215 files already formatted pyright: 0 errors, 0 warnings, 0 informations pytest: 1168 passed, 1 skipped, 1 deselected, 1 warning scenario: Running scenario: strict_delegated_auth scenario: agents: 16 seed: 4242 ticks: 3000 scenario determinism: two fresh runs produced byte-identical 122-line traces scenario SHA-256: fd9877021de5396c31a2de13f1f52231acc77f0254410571c29f633828443fce merge simulation against current main: clean ``` The scenario trace contains all three required attack outcomes: - `attack:scope_escalation:blocked` - `attack:stale_parent:blocked` - `attack:audience_confusion:blocked` The focused strict-delegation suite has 18 tests, including key-confusion forgery rejection, issuer-secret child-signature rejection, non-ASCII malformed signature handling, typed rejection evidence, and a Hypothesis strict-subset property.
Try it
Open PR on GitHubView diffCheckout locally
git fetch origin pull/91/head:pr-91
git checkout pr-91