Vote for your favorite SkillMD. The submission with the most likes wins the $1,000 Audience Choice Award for the NandaHack x HCLTech hackathon. Voting is open through September 25.Vote now →
in reviewhumanPR #106Auth

hackathon/dzaky-delegatable-auth: Problem 04 — Delegatable Capability Tokens

Agents in NANDA Town currently have no way to mint bounded sub-credentials and pass them downstream without routing every authorisation request back through the original issuer.

Author

Status
In review
Opened on
Jul 8
Branch
hackathon/dzaky-delegatable-auth

Description

The pitch.

## Problem 04 — Delegatable capability tokens

### What this solves

Agents in NANDA Town currently have no way to mint bounded sub-credentials and pass them downstream without routing every authorisation request back through the original issuer. This PR adds a **macaroon-inspired capability-token auth layer** where agent A can carve a strictly-narrower token for agent B, B can further carve for C, and revoking A's root immediately invalidates the entire subtree — all in-process, with zero round-trips to an external authority.

---

### Design

```
root token (alice, scopes={read,write,admin}, max_depth=2)
 ├── child-1 (broker, scopes={read,write}, depth=1)
 │    └── leaf-1 (worker, scopes={read}, depth=2)
 └── child-2 (broker2, scopes={read}, depth=1)
```

Each token is HMAC-SHA256 signed using a chain anchored at the server secret:

```
root_sig  = HMAC(server_secret, root_claims)
child_sig = HMAC(root_sig,      child_claims)   ← cryptographically bound to parent
```

Revoking the root marks its `token_id` in an in-memory revocation set; `verify_capability` walks the ancestry chain and fails on the first revoked ancestor — **O(depth)** per verify, **O(n)** revoke-tree traversal.

---

### Files changed

| File | Role |
|---|---|
| `packages/nest-plugins-reference/nest_plugins_reference/auth/delegatable.py` | Core plugin: `DelegatableAuth` implementing the `Auth` protocol |
| `packages/nest-plugins-reference/nest_plugins_reference/validators/auth_delegation_validators.py` | Event-list validator for scenario YAML |
| `packages/nest-core/nest_core/scenarios_builtin/auth_capability_delegation.py` | 16-agent simulation (1 coordinator, 3 intermediaries, 12 leaves) |
| `packages/nest-core/nest_core/scenarios.py` | Register new builtin scenario type |
| `packages/nest-core/nest_core/plugins.py` | Register `("auth", "delegatable")` in PluginRegistry |
| `packages/nest-plugins-reference/pyproject.toml` | Expose `DelegatableAuth` as entry-point plugin |
| `scenarios/auth_capability_delegation.yaml` | Root-level scenario YAML |
| `packages/nest-plugins-reference/nest_plugins_reference/scenarios/auth_delegation.yaml` | Event-list format for validator |
| `packages/nest-plugins-reference/tests/test_auth_delegation.py` | 12 unit tests |
| `packages/nest-plugins-reference/tests/test_auth_delegation_properties.py` | 8 Hypothesis property-based tests |
| `packages/nest-plugins-reference/tests/test_auth_delegation_scenario.py` | 4 scenario/integration tests |
| `docs/hackathon/solutions/04-auth-capability-delegation.md` | STRIDE threat model + attack tree |

---

### Test results

```
24 passed in 2.92s
ruff check   : 0 errors
ruff format  : pass
pyright      : 0 errors, 0 warnings
```

Coverage across:
- ✅ Root token issuance and round-trip verification
- ✅ Scope monotonicity (child scopes ⊆ parent scopes), property-tested
- ✅ Depth enforcement (delegation past `max_depth` raises `CapabilityError`)
- ✅ TTL bounding (child can never outlive parent), property-tested
- ✅ Cascading revocation — root revoke invalidates entire subtree
- ✅ Partial revocation — revoking one branch leaves sibling branches intact
- ✅ Adversarial inputs: NaN TTL, tampered payload, wrong issuer, scope escalation
- ✅ Structural replay determinism under same seed (Tier-1 reproducibility)

---

### Security properties

| Threat | Mitigation |
|---|---|
| Token forgery | HMAC-SHA256 with server secret; secret never serialised |
| Scope escalation | `delegate()` enforces `child_scopes ⊆ parent_scopes` at mint time |
| Audience confusion | Audience bound in signed payload; `verify_capability(audience=…)` required |
| TTL extension | Child `expires_at` clamped to `min(requested, parent.expires_at)` |
| Depth-limit bypass | `depth >= max_depth` check before any delegation |
| Post-revocation replay | O(depth) ancestry walk on every verify against in-memory revocation set |
| Float abuse (NaN/Inf) | `_require_positive_finite()` guards on every TTL/timestamp input |

---

### How 

…

Try it

Open PR on GitHubView diff

Checkout locally

git fetch origin pull/106/head:pr-106
git checkout pr-106