Skip to content

[Hackathon] EFS Scribe delegated auth with offline write receipts - #111

Open
JamesCarnley wants to merge 1 commit into
projnanda:mainfrom
JamesCarnley:hackathon/james-efs-scribe-auth
Open

[Hackathon] EFS Scribe delegated auth with offline write receipts#111
JamesCarnley wants to merge 1 commit into
projnanda:mainfrom
JamesCarnley:hackathon/james-efs-scribe-auth

Conversation

@JamesCarnley

Copy link
Copy Markdown

What this adds

This PR adds auth: delegatable, an HMAC-chained capability-token plugin for Problem 04.

A coordinator can issue a broad root capability, intermediaries can mint narrower child capabilities, and verification walks the parent chain so revoking or expiring an ancestor invalidates every descendant.

Why this workload

The PR exercises delegated auth with a concrete offline workload: EFS Scribe write intents.

Twelve leaf agents submit path-scoped write intents and receive deterministic receipts that bind the agent, path, payload hash, auth context, signature, nonce, mode, and mock EFS UID. This keeps the scenario fully deterministic inside Nanda Town while testing a realistic delegated-write flow.

What changed

  • Adds the auth: delegatable plugin.
  • Adds scenarios/delegated_auth.yaml as the Problem 04 entry point.
  • Adds scenarios/efs_scribe_offline.yaml as the explicit EFS Scribe workload.
  • Adds validators for the delegation tree, receipt verification, and adversarial write rejection.
  • Adds tests for delegated auth, EFS Scribe receipts, scenario determinism, validator failures, and adversarial cases.
  • Updates the auth layer docs with the new plugin.

Attacks covered

  • Scope escalation
  • Revoked or stale parent token
  • Audience confusion
  • Cross-path writes
  • Nonce replay
  • Path traversal
  • Payload hash mismatch
  • Bad signatures
  • Mode confusion

Verification

Local verification passed:

ruff check .
ruff format --check .
pyright
pytest -q

Result:

839 passed, 1 skipped, 1 deselected

Scenario validation:

delegated_auth
PASS efs_scribe_delegation_tree
PASS efs_scribe_receipts_verified
PASS efs_scribe_rejects_adversarial_writes

efs_scribe_offline
PASS efs_scribe_delegation_tree
PASS efs_scribe_receipts_verified
PASS efs_scribe_rejects_adversarial_writes

Scope

This is the Nanda Town side: plugin, deterministic scenarios, validators, tests, and docs. It does not make network, Sepolia, Railway, or external service calls.

@dhve

dhve commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Thanks @JamesCarnley. This is one of the most complete Problem 04 submissions I've read. The plugin extends the auth surface the right way: verify(token) keeps its Protocol signature and delegate / verify_for are purely additive, so every existing caller and alternative auth implementation keeps working. The typed exception taxonomy (ScopeEscalationError, RevokedAncestorError, TtlEscalationError, AudienceMismatchError) makes the tests read like a threat model, the forged-segment tests in test_delegatable_auth.py hand-roll HMAC chains via _append_forged_child instead of only driving the public API, and test_jwt_baseline_runs_but_fails_validators plus the byte-identical test_scenario_is_deterministic hit the two hardest charter requirements head on. I also like that the trace validators re-derive DidKeyIdentity and re-verify every capability chain and signature independently rather than trusting the scribe's own checks block.

This is close. One blocker and three things to tighten:

1. The branch conflicts with main; rebase and get CI reporting green.

GitHub currently reports this PR as CONFLICTING. You append to the shared integration points (nest_core/validators.py, plugins.py, packages/nest-plugins-reference/pyproject.toml, tests/test_validators.py) that most other hackathon PRs also append to, so drift accumulates fast; rebase onto latest main and push. Separately, no CI checks have reported on this branch at all yet. The judge panel does not score broken PRs, so make sure all stages (uv sync, ruff check, ruff format --check, pyright, pytest) go green on the PR itself, not just in your local run.

2. Path scopes are never normalized at the token layer.

scope_covers treats efs.write: scopes as plain string prefixes, so delegate() will happily mint efs.write:/agents/../private/* as a "narrowing" of efs.write:/agents/*, and verify accepts the resulting chain. Your scenario stays safe because ScribeAgent rejects non-normalized request paths via _normalized_path, but that means the traversal invariant lives in the workload, not in the token. Any future consumer that resolves .. at enforcement time (a real filesystem would) or normalizes scope paths reopens the escalation. You already have the predicate in efs_scribe_offline.py; enforce it in _validate_child_scopes too (reject .., //, and empty segments in efs.write: scope paths) so the guarantee holds wherever tokens travel. Small diff, correctness-dimension win, and it deserves its own forged-token test.

3. delegated_auth.yaml and efs_scribe_offline.yaml are the same scenario twice.

Same 18 agents, same layer stack, same task.type: efs_scribe_offline, same three validators; only name, description, and trace path differ, and the test suite runs full simulations of both. Problem 04 names scenarios/delegated_auth.yaml as the required artifact, so keep that one canonical and either drop the alias or make it earn its place (a failures.message_drop > 0 variant would actually add coverage instead of runtime).

4. Question: is rejecting unchanged wildcard scopes intentional?

_validate_child_scopes rejects a child wildcard whose only covering parent scope is itself, so a root holding ["efs.write:/agents/*", "scribe:*"] cannot delegate just ["efs.write:/agents/*"] to a shorter TTL or a different audience, even though that is a strict subset. That forbids the classic macaroon move of attenuating on time or audience alone. Your coverage-based narrowing is genuinely more sensible than a naive set-subset check, but if the stricter wildcard rule is deliberate (the dedicated test suggests it is), state it in the DelegatableAuth docstring and in docs/layers/auth.md, because callers will hit ScopeEscalationError in a flow most delegation systems allow.

Two smaller notes. The charter branch pattern is hackathon/<your-handle>-<short-theme> and yours is hackathon/james-efs-scribe-auth rather than your GitHub handle; if you rename the branch in your fork through the GitHub UI, the open PR follows the rename, which is cheap insurance given the auto-close rule. And with roughly twenty open PRs on this problem, your differentiator (the offline EFS Scribe receipt workload with independent trace-level re-verification) is real; adding one hypothesis property test over scope narrowing (random scope sets and paths, assert a delegation chain never widens coverage) would make it load-bearing for test rigor as well.

Suggested path forward:

  1. Rebase onto main, resolve the append-point conflicts, push, and confirm CI reports green.
  2. Add scope-path normalization to _validate_child_scopes plus a forged-token test for traversal scopes.
  3. Collapse or differentiate the duplicate scenario YAML.
  4. Document (or relax) the unchanged-wildcard rule and consider one property-based test for narrowing.

Leaving this open. Ping me on the hackathon channel if anything here is unclear.

@JamesCarnley
JamesCarnley force-pushed the hackathon/james-efs-scribe-auth branch from 97a4048 to 9253969 Compare July 10, 2026 04:13
@JamesCarnley

JamesCarnley commented Jul 10, 2026

Copy link
Copy Markdown
Author

Thank you! Issues addressed in 9253969.

Changes made:

  • rebased onto current main (7106ca6), resolving the integration conflicts;
  • moved EFS path normalization into the token layer: malformed efs.write: scopes with .., ., //, empty path segments, missing leading /, or misplaced wildcards are rejected during mint and verify;
  • added forged-chain coverage for malformed EFS scopes, plus predicate coverage for scope_covers;
  • kept scenarios/delegated_auth.yaml as the canonical Problem 04 scenario and removed the duplicate visible efs_scribe_offline.yaml alias;
  • documented the intentional unchanged-wildcard rejection in DelegatableAuth and docs/layers/auth.md;
  • adjusted the Hypothesis property test to generate strictly attenuating chains.

Local validation:

  • .venv/bin/ruff check .
  • .venv/bin/ruff format --check .
  • .venv/bin/pyright
  • .venv/bin/python -m pytest -v -> 1197 passed, 1 skipped, 1 deselected

GitHub now reports the PR as mergeable. The latest CI run is action_required with zero jobs, so it looks like the fork PR workflow still needs maintainer approval before checks can execute.

@JamesCarnley
JamesCarnley deleted the hackathon/james-efs-scribe-auth branch July 10, 2026 04:42
@JamesCarnley
JamesCarnley restored the hackathon/james-efs-scribe-auth branch July 10, 2026 04:43
@JamesCarnley JamesCarnley reopened this Jul 10, 2026
@JamesCarnley

Copy link
Copy Markdown
Author

I tried renaming the branch but chaos ensued. Hopefully the branch name isn't an issue.

@JamesCarnley
JamesCarnley force-pushed the hackathon/james-efs-scribe-auth branch from 9253969 to 38532b9 Compare July 10, 2026 19:17
@JamesCarnley
JamesCarnley force-pushed the hackathon/james-efs-scribe-auth branch from 38532b9 to 650d59f Compare July 19, 2026 02:11
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