Skip to content

[Hackathon] Auth: manifest-bound delegatable capability tokens#37

Open
Charanarravindaa wants to merge 3 commits into
projnanda:mainfrom
Charanarravindaa:hackathon/charan-policy-enforcement
Open

[Hackathon] Auth: manifest-bound delegatable capability tokens#37
Charanarravindaa wants to merge 3 commits into
projnanda:mainfrom
Charanarravindaa:hackathon/charan-policy-enforcement

Conversation

@Charanarravindaa

@Charanarravindaa Charanarravindaa commented Jun 24, 2026

Copy link
Copy Markdown

[Hackathon] Auth: manifest-bound delegatable capability tokens

Problem Picked

Problem #4 — Delegatable capability tokens with cascading revocation.

Layer 5 — Auth.

This PR adds auth: delegatable, a reference Auth-layer plugin for manifest-bound delegatable capability tokens, plus the required adversarial validators and scenarios/delegated_auth.yaml proof scenario. It stays inside the existing 12-layer stack and does not introduce a new framework layer.

Why

The default jwt auth plugin can issue and verify bearer tokens, but it cannot model the delegation pattern called out in problem #4:

agent A holds a root token; agent A mints a short-lived, narrower token for agent B; revoking A's token invalidates B's token at the next verify.

That pattern matters for agent swarms because orchestrators routinely need constrained handoffs:

  • "You may call this tool, but not pass broader authority onward."
  • "You may buy this item, but only within my delegated capability."
  • "You may act for me until this parent token expires or is revoked."
  • "You may use this sub-capability, but only as the declared audience."

This PR makes that capability-delegation contract testable under attack in Nanda Town.

Core Idea

DelegatableAuth issues root tokens whose scopes are clamped to a signed manifest. A holder can mint a child token for another audience, but only under stricter caveats:

  • Manifest-bound roots: root scopes are clamped to tools allowed by the signed PolicyManifest.
  • Strict scope narrowing: child scopes must be a strict subset of parent scopes.
  • TTL containment: child expiry cannot exceed parent expiry.
  • Audience binding: a token minted for one agent cannot be presented by another.
  • Cascading revocation: revoking any token invalidates that token and all descendants.
  • Tamper resistance: manifest tampering and token payload/signature tampering are rejected.

The plugin is a deterministic, in-memory reference implementation for the Nanda Town test rig. Distributed revocation and cross-process token registry replication are intentionally out of scope.

Adversarial Contract

Problem #4 requires validators that catch three attacks. This PR ships all three:

Attack Baseline auth: jwt This PR auth: delegatable
Scope escalation FAIL: broader child authority can be accepted via baseline re-issuance PASS: child scopes must be a strict subset of parent scopes
Stale parent FAIL: revoking parent authority does not invalidate separately issued child token PASS: descendant verification checks revoked ancestors
Audience confusion FAIL: baseline verification does not bind delegated presentation to the child audience PASS: presenter must match token audience

The bundled scenario builds the required delegation tree: one coordinator, three intermediaries, and twelve leaves.

What Ships

  • nest_plugins_reference.auth.delegatable.DelegatableAuth
  • minimal signed-manifest primitives used to clamp Auth scopes
  • entry-point registration for auth: delegatable
  • scenarios/delegated_auth.yaml
  • delegated_auth built-in scenario with coordinator, 3 intermediaries, and 12 leaves
  • adversarial validators:
    • delegated_auth_scope_containment
    • delegated_auth_no_stale_parent
    • delegated_auth_audience_binding
  • unit tests for scope clamping, delegation, revocation, TTL, audience binding, tampering, and plugin resolution
  • property tests for strict scope containment, transitive revocation, and deterministic token generation
  • end-to-end tests showing auth: delegatable PASS and auth: jwt FAIL

Security Properties Covered By Tests

  • unsigned manifests are deny-all
  • signed manifests widened after signing do not grant new authority
  • duplicate root scopes are deduplicated
  • child scopes must be narrower than parent scopes
  • equal-scope delegation is rejected
  • out-of-parent scope escalation is rejected
  • child TTL beyond parent expiry is rejected
  • revoked ancestors invalidate descendants
  • child revocation does not invalidate the root
  • root and child tokens are audience-bound when a presenter is supplied
  • payload tampering of aud, exp, chain, or scopes is rejected
  • signature tampering is rejected
  • verifier re-checks delegation caveats even for registered forged children
  • the scenario trace is deterministic under repeated runs

How To Test

Run the charter CI gate:

uv sync
uv run ruff check .
uv run ruff format --check .
uv run pyright
uv run pytest -v

Equivalent local shortcut:

make ci-local

Focused Auth checks:

uv run pytest packages/nest-plugins-reference/tests/test_delegatable.py \
  packages/nest-plugins-reference/tests/test_delegatable_properties.py \
  packages/nest-core/tests/test_delegated_auth.py -q

Scenario proof:

uv run nest run scenarios/delegated_auth.yaml -o /tmp/delegated_auth.jsonl
uv run python -c "from pathlib import Path; from nest_core.validators import validate_trace; p=Path('/tmp/delegated_auth.jsonl'); [print('PASS' if r.passed else 'FAIL', r.name, '-', r.detail) for r in validate_trace(p, 'delegated_auth')]"

Expected under auth: delegatable: all three validators PASS.

Flip the YAML layer to auth: jwt, rerun, and the same three validators FAIL.

Determinism

The plugin uses deterministic JSON payloads, deterministic HMAC chaining, and the scenario uses the Nanda Town seed path. Re-running the same scenario with the same seed produces a byte-identical trace.

API Fit

  • Implements the existing Auth protocol surface: issue, verify, and revoke.
  • Adds the problem-required extension method: delegate(parent_token, audience, scopes_subset, ttl) -> Token.
  • Uses nest_core.types.AgentId, Token, and AuthContext.
  • Registers as auth: delegatable.
  • Public symbols follow the repo docstring style with Example:: blocks.

Threat Model And Limits

This is a reference plugin for deterministic Nanda Town simulations. It protects against scope widening, equal-authority delegation, parent TTL bypass, stale descendants after local revocation, audience confusion, manifest tampering, and token payload/signature tampering.

It does not attempt to solve distributed revocation, cross-process token registry replication, agent process sandboxing, hardware-backed key attestation, OAuth2 server endpoints, or network token introspection.

Persona

Security-minded agent-runtime engineer. The emphasis is on explicit authority boundaries, adversarial tests, deterministic replay, and making the Auth layer catch delegation bugs that the baseline jwt plugin cannot catch.

@dhve

dhve commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Thanks @Charanarravindaa. There's a lot of engineering here — the manifest_policy PDP, the PolicyEnforcer proxy, the four-dimension decision core, and the FAIL-vs-baseline / PASS-vs-control experiment design all read well. That said, this PR as submitted isn't mergeable as-is, and I want to explain why with a concrete path forward.

What this PR actually proposes

Reading through the diff carefully, this isn't a hackathon plugin submission for one of the 12 layers. It's a framework proposal to add a 13th layer to the stack, alongside:

  • Documentation rewrites across the repo (README, CONTRIBUTING, docs/quickstart, docs/concepts, docs/writing-a-plugin, docs/writing-a-scenario)
  • Next.js dashboard changes (7 files, not covered by make ci-local)
  • Changes to nest-marketplace/adapter.py, nest-sdk/__init__.py, and scripts/harness/agent_runner.py
  • Core-framework surgery (LayerConfig, layers/__init__.py, plugins.py, runner.py, cli.py, PolicyDecision in types.py)

Totalling 53 files and +3517 lines. For reference, the largest problem-specific hackathon PRs so far have been 10-15 files and ~1500-1700 lines (Ricardo #31, Simon #30, swamx #33).

Adding a layer to the 12-layer stack is a framework-level design change. It changes the README's headline claim, it changes the layer-count in the dashboard, it changes CONTRIBUTING's guidance, and it re-defines what a "complete Nanda Town test rig" is. That decision needs to happen in the open through a design discussion (an Issue, or a short RFC in docs/), not land as a single squash-merge in a hackathon PR.

Suggested path forward

Two moves, in this order:

1. Extract policy_auth as its own hackathon PR (small, mergeable now)

nest_plugins_reference/auth/policy_auth.py (163 lines) is a legit hackathon Auth-layer plugin. It implements the existing Auth protocol, registers via pyproject.toml entry point, clamps issued token scopes to a signed manifest, and doesn't require any framework changes. Open a focused PR that includes only:

  • nest_plugins_reference/auth/policy_auth.py
  • The manifest primitives it needs (probably a subset of policy/manifest.py)
  • nest_plugins_reference/tests/test_policy_auth.py + test_policy_auth_properties.py
  • One-line entry-point registration in pyproject.toml

That would be a fast-track review. It's the same shape as PR #25 (Ed25519 identity) or PR #28 (privacy plugin) — a plugin that improves the reference implementation for an existing layer via signed-manifest scope narrowing.

2. Open a design Issue for the "13th policy layer" proposal

If you believe the framework needs a first-class Policy layer (and there are good arguments), open an Issue with:

  • The gap in the current 12 layers that Policy fills that can't be handled at the Auth layer
  • The proposed Policy protocol (much shorter than the current policy.py)
  • A rough sketch of how existing scenarios would interact with the new layer
  • The "load-bearing" argument (why authorize needs to be a distinct decision point, not a policy check inside Auth)

Once there's design consensus (probably 3-5 people from the Nanda team weighing in), a follow-up PR can add just the layer protocol and one reference plugin (allow_all as the default). Then a separate PR adds manifest_policy. Then a separate PR adds the enforcer/scenario/validators. Each PR is scoped and reviewable.

That's how framework changes go through for this repo. Not because your idea is bad but because the blast radius makes squash-merge review dangerous.

Other issues, smaller but concrete

  • Attribution footer needs to go. The PR body ends with 🤖 Generated with [Claude Code](https://claude.com/claude-code). Nanda Town PRs strip that (it's your work regardless of tooling used).
  • Dashboard changes don't belong in hackathon PRs. The Next.js dashboard is a separate deliverable with its own review path; it's not covered by make ci-local. Please pull those changes into a separate PR to apps/nest-dashboard.
  • nest-marketplace, nest-sdk, and scripts/harness modifications are out of scope for a hackathon submission focused on the auth/policy layer. Either explain why each one is load-bearing for policy_auth, or drop them.
  • Doc changes across docs/ should be part of the layer-13 design discussion, not this PR.
  • test_validators.py +262 and validators.py +259 are large. Once the framework-scope changes come out, these should shrink dramatically. Any test file surgery that's not for a specific validator you're adding is worth dropping.

Timeline

If you want the policy_auth plugin merged in time for the Nanda Summit demo on July 11, opening the focused PR by July 5 gives comfortable runway. The Issue for the layer proposal can happen in parallel and doesn't need to land before the Summit.

Leaving this open for now. Not closing so you have a place to iterate. If you'd rather I close and you open fresh PRs from the descope, that's also fine — ping me either way.

@Charanarravindaa Charanarravindaa changed the title [Hackathon] Charanarravindaa Suriess — ships a real 13th policy layer (load-bearing, additive) [Hackathon][Reworking] Manifest-bound delegatable Auth tokens Jul 1, 2026
@Charanarravindaa Charanarravindaa changed the title [Hackathon][Reworking] Manifest-bound delegatable Auth tokens [Hackathon] Auth: manifest-bound delegatable capability tokens Jul 1, 2026
@Charanarravindaa Charanarravindaa force-pushed the hackathon/charan-policy-enforcement branch from cb4f48d to 19b99a7 Compare July 1, 2026 12:40
@Charanarravindaa

Copy link
Copy Markdown
Author

@dhve I’ve updated the PR to focus on Problem #4 as an Auth-layer submission. The diff now removes the broader policy-layer/framework scope and contains auth: delegatable, the delegated auth scenario, and the three adversarial validators required by the charter.
I also updated the PR body to match the hackathon submission format and reran the local gate: make ci-local passes, with 625 passed, 1 skipped, 1 deselected.
One remaining housekeeping item: GitHub is currently showing merge conflicts in the shared scenario/validator registry files after upstream main moved. I’ll resolve those next so the PR is mergeable.

@Charanarravindaa

Charanarravindaa commented Jul 1, 2026

Copy link
Copy Markdown
Author

@dhve I’ve pushed the requested updates to this branch.

Summary:

  • Reframed the PR as a charter-style submission for Problem [Hackathon] openai-llm: semantic memory plugin (recall + TTL + LRU) #4 — Delegatable capability tokens with cascading revocation / Layer 5 — Auth.
  • Kept the PR focused on auth: delegatable: manifest-bound root scope clamping, strict child scope narrowing, TTL containment, audience binding, and cascading revocation.
  • Included the required delegated_auth scenario with 1 coordinator, 3 intermediaries, and 12 leaves.
  • Included adversarial validators for scope escalation, stale-parent revocation, and audience confusion.
  • Resolved the latest upstream merge conflicts and pushed the merge commit.

Validation run locally:

  • ruff check . passed
  • ruff format --check . passed
  • pyright passed with 0 errors
  • pytest -q: 820 passed, 1 skipped, 1 deselected
  • make ci-local: all 5 checks passed

GitHub currently reports the PR as mergeable. It shows no branch checks reported, so the merge state is marked unstable only for missing checks, not for conflicts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants