Skip to content

feat(server): optional API-key authentication for the HTTP binding - #10

Closed
glatinone wants to merge 1 commit into
fix/enforce-retention-windowfrom
feat/api-key-auth
Closed

glatinone wants to merge 1 commit into
fix/enforce-retention-windowfrom
feat/api-key-auth

Conversation

@glatinone

Copy link
Copy Markdown
Owner

feat(server): optional API-key authentication for the HTTP binding

The spec carries agent identity in X-AMP-Agent-ID and defines no credential
(RFC-AMP-001 §6.1), so the header was an assertion rather than proof: anyone who
could reach the port could claim any agent id, and every access rule downstream -
readable_by, writable_by, the owner check - was decided from that claim. The
reference server was exactly as documented, and unsafe on any network an operator
does not fully control.

This adds proof without changing the binding. The agent id keeps travelling in the
header, where the spec puts it; with a key store configured, the caller must also
present a key that belongs to that id.

  • amp_server.auth loads a store from AMP_API_KEYS_FILE: a JSON object mapping
    agent id to a sha256: digest. Digests, not keys, so a leaked file does not
    hand over working credentials; python -m amp_server.auth hash <key> prints the
    value to paste.
  • A wrong key, a missing key and a key for an agent that does not exist answer the
    same 401 UNAUTHENTICATED, so the endpoints cannot be used to enumerate agent
    ids - the same reasoning as the uniform 403 on deleted cells in spec §8.4.
  • Off by default, so the spec's binding keeps working for anyone who has not
    opted in - and a store that cannot be read stops the server rather than
    falling back to trusting the header, which would silently remove the protection
    an operator asked for.
  • Identity resolution moved into one FastAPI dependency (verified_agent_id), so
    a route cannot resolve an agent without proving it, and a route added later
    fails the sweep test rather than shipping unverified.
  • POST /memories's identity.created_by fallback is closed whenever keys are
    configured. That fallback is the documented default without keys; with them it
    would have been an authentication bypass, letting any caller create a cell as
    any agent. The test for it was written after tracing what the fallback does, not
    after assuming it was inert.
  • GET /spec reports api_keys_required, so a client learns it needs a key
    before a call fails with 401. GET /health and /spec stay open.
  • Both SDKs take the key as a constructor argument. Their identity headers are now
    built in one method instead of at each call site - the async client was setting
    X-AMP-Agent-ID in four places, which is four places to forget a credential.

The spec carries agent identity in `X-AMP-Agent-ID` and defines no credential
(RFC-AMP-001 §6.1), so the header was an assertion rather than proof: anyone who
could reach the port could claim any agent id, and every access rule downstream -
`readable_by`, `writable_by`, the owner check - was decided from that claim. The
reference server was exactly as documented, and unsafe on any network an operator
does not fully control.

This adds proof without changing the binding. The agent id keeps travelling in the
header, where the spec puts it; with a key store configured, the caller must also
present a key that belongs to that id.

- `amp_server.auth` loads a store from `AMP_API_KEYS_FILE`: a JSON object mapping
  agent id to a `sha256:` digest. Digests, not keys, so a leaked file does not
  hand over working credentials; `python -m amp_server.auth hash <key>` prints the
  value to paste.
- A wrong key, a missing key and a key for an agent that does not exist answer the
  same `401 UNAUTHENTICATED`, so the endpoints cannot be used to enumerate agent
  ids - the same reasoning as the uniform 403 on deleted cells in spec §8.4.
- **Off by default**, so the spec's binding keeps working for anyone who has not
  opted in - and a store that cannot be read **stops the server** rather than
  falling back to trusting the header, which would silently remove the protection
  an operator asked for.
- Identity resolution moved into one FastAPI dependency (`verified_agent_id`), so
  a route cannot resolve an agent without proving it, and a route added later
  fails the sweep test rather than shipping unverified.
- `POST /memories`'s `identity.created_by` fallback is closed whenever keys are
  configured. That fallback is the documented default without keys; with them it
  would have been an authentication bypass, letting any caller create a cell as
  any agent. The test for it was written after tracing what the fallback does, not
  after assuming it was inert.
- `GET /spec` reports `api_keys_required`, so a client learns it needs a key
  before a call fails with 401. `GET /health` and `/spec` stay open.
- Both SDKs take the key as a constructor argument. Their identity headers are now
  built in one method instead of at each call site - the async client was setting
  `X-AMP-Agent-ID` in four places, which is four places to forget a credential.
@glatinone

Copy link
Copy Markdown
Owner Author

Landed on master in the v0.1.0 chain: the branch was fast-forward merged as part of b940905..92b88ee and released as v0.1.0. Closing so the open list matches reality - the commits are in master, and the tag points at them.

@glatinone glatinone closed this Oct 4, 2026
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.

1 participant