Skip to content

Instantly share code, notes, and snippets.

@dickhardt
Created April 27, 2026 04:39
Show Gist options
  • Select an option

  • Save dickhardt/79c25747464d28bb6d7a4652f0f07129 to your computer and use it in GitHub Desktop.

Select an option

Save dickhardt/79c25747464d28bb6d7a4652f0f07129 to your computer and use it in GitHub Desktop.

AAuth Implementation Assessment: @aauth packages vs. Neotoma Sandbox

Date: 2026-04-26
Tested against: sandbox.neotoma.io (v0.7.1)
Agent: aauth:local@dickhardt.github.io (Secure Enclave ES256 key)
Packages: @aauth/fetch 0.8.1, @aauth/local-keys 0.8.0, @hellocoop/httpsig 1.5.0


What Works

Test Result
Discovery (/.well-known/aauth-resource.json) ✅ Live, returns valid JSON
Unsigned request → AAUTH_REQUIRED (401) ✅ Correct
Signed write to /sandbox/aauth-only/store ✅ 200 — entity created, attributed
Agent identity (agent_sub, agent_iss) ✅ Correctly resolved from agent token
Signature verification ✅ Neotoma extracts ephemeral key from cnf.jwk
Tier detection "software" (correct — no hardware attestation)

The core protocol chain — key → agent token → HTTP signature → verification → attribution — is fully operational end-to-end.


Issues Found

1. Critical: Ephemeral Key Rotation Breaks Partition Identity

Every aauth-fetch CLI invocation generates a new ephemeral keypair (per the spec — agents rotate ephemeral keys). The agent token wraps the new ephemeral public key in cnf.jwk, signed by the durable root key. This is correct per spec.

The problem: Neotoma partitions user identity by sha256("aauth:<thumbprint>") where <thumbprint> is the ephemeral key's JWK Thumbprint. Since each invocation has a new ephemeral key:

  • Write succeeds → entity stored under user_id derived from thumbprint A
  • Read (next invocation) → new ephemeral key → thumbprint B → different user_id �� 404

I confirmed this with three successive /session calls — each returned a different agent_thumbprint and user_id:

Call 1: agent_thumbprint: "Rx1E5ie_...", user_id: "aeb71ce5-..."
Call 2: agent_thumbprint: "F30g7nHx...", user_id: "5f719d59-..."
Call 3: agent_thumbprint: "IUJ_gqnj...", user_id: "a0f24e8b-..."

What the spec says (Section 5, "Agent Identifiers"):

sub: Agent identifier (stable across key rotations)

The agent identifier aauth:local@dickhardt.github.io is the stable identity anchor. Ephemeral keys are proof-of-possession for a single session — they are designed to rotate frequently.

Fix: Partition by agent_sub (from the verified agent token), or by the root key kid resolved via the published JWKS at {iss}/.well-known/jwks.json. The ephemeral thumbprint should NOT be used as a stable identity anchor.

Verification: Neotoma already extracts agent_sub correctly (it appears in the /session response). It also has agent_iss which can be used to fetch the JWKS and confirm the root key.


2. Resource Metadata Schema Mismatch

Neotoma returns:

{
  "issuer": "https://sandbox.neotoma.io",
  "client_name": "Neotoma",
  "signature_window": 60,
  "supported_algs": ["ES256", "EdDSA"],
  "supported_typ": ["aa-agent+jwt"],
  "jwks_uri": null,
  "jwks_uri_reason": "Neotoma is verifier-only; agent JWKs are conveyed per-request..."
}

Spec defines (Section "Resource Metadata"):

Field Spec Status Neotoma
issuer REQUIRED ✅ Present
jwks_uri REQUIRED ⚠️ null (spec says required URL)
client_name OPTIONAL ✅ Present
signature_window OPTIONAL ✅ Present, correct
supported_algs Not in spec ❓ Non-standard addition
supported_typ Not in spec ❓ Non-standard addition
jwks_uri_reason Not in spec ❓ Non-standard addition
authorization_endpoint OPTIONAL Not present (fine)
scope_descriptions OPTIONAL Not present (fine)

Suggestions:

  • jwks_uri: For verifier-only resources, either omit entirely or publish an empty JWKS ({"keys":[]}). Setting it to null violates the REQUIRED constraint. We could also discuss updating the spec to make it OPTIONAL for resources that don't sign resource tokens.
  • supported_algs / supported_typ: These are potentially useful for agents. If we want them in the spec, let's discuss adding them. For now they're non-standard extensions (which is fine — the spec doesn't forbid additional fields).

3. Minor: CLI Ephemeral Key Not Persisted Across Invocations

@aauth/local-keys caches the agent token + ephemeral key in an in-memory Map. For long-running processes (MCP agents, servers), this works perfectly — same ephemeral key for the cache TTL. For CLI usage, every invocation = new process = new ephemeral key.

This isn't a bug — it's spec-correct behavior (agents SHOULD rotate ephemeral keys). But combined with issue #1, it means the CLI can never read back what it wrote on Neotoma's sandbox.

Potential CLI enhancement: Persist ephemeral key + agent token to ~/.aauth/sessions/<agent-url-hash>.json with TTL, so multiple CLI calls within the token lifetime share the same signing key. This is optional — the real fix is #1.


Demo Recipe That Works Today

# Confirm discovery
curl -s https://sandbox.neotoma.io/.well-known/aauth-resource.json | jq

# Signed session — shows your agent identity
aauth-fetch --agent-only https://sandbox.neotoma.io/session

# Unsigned write to AAuth-only route — correctly rejects
curl -s -X POST https://sandbox.neotoma.io/sandbox/aauth-only/store \
  -H 'content-type: application/json' \
  -d '{"entities":[{"entity_type":"note","content":"unsigned"}]}'
# → {"error_code":"AAUTH_REQUIRED",...}

# Signed write — succeeds
aauth-fetch --agent-only -X POST \
  -d '{"entities":[{"entity_type":"note","canonical_name":"AAuth test","content":"signed"}],"idempotency_key":"test-'$(date +%s)'"}' \
  https://sandbox.neotoma.io/sandbox/aauth-only/store
# → 200, entity created

Note: Reading back the entity will 404 due to issue #1 (new ephemeral key = new partition). Once Neotoma partitions by agent_sub, the full write→read round-trip will work.


Summary

The AAuth protocol implementation is solid. The agent bootstrap, key management, token signing, HTTP Message Signatures, and verification all work correctly. The one blocking issue is Neotoma's identity partitioning using ephemeral key thumbprints instead of the stable agent identifier — a straightforward fix on Neotoma's side.

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