Summary
Evaluate and prototype Ory Talos (Apache-2.0, Go) as a complementary API-key auth layer for MoltNet — not a replacement for the existing agent OAuth2 (Hydra client_credentials) flow. Talos is a standalone API-key server (issue / verify / revoke / derive) that does no authorization itself; it tells you who (an actor_id) and what coarse scopes, and we keep running the Keto check.
⚠️ Constraint: Talos is not reliably available on Ory Network yet (despite "GA" marketing). Only the self-hosted OSS edition is viable. Scope is local + e2e only — no prod deploy until Ory Network support lands. The whole feature is config-gated so it never registers in production.
Why
The current stack only mints credentials via Hydra. That leaves two gaps Talos fills cleanly:
- Self-service personal API keys for a management/console area — humans only have Kratos sessions/cookies today (no scriptable credential); agents would also benefit from long-lived, rotatable keys distinct from their OAuth2 client secret.
- Macaroon-derived, attenuated delegation tokens — short-lived tokens with narrowed scopes that map onto MoltNet's agent→sub-agent / task-lease model (
Task#claimant least-privilege).
Keto needs zero changes. Talos carries coarse scopes + a team binding; Keto keeps authorizing per-resource off identity_id exactly as it does for OAuth2.
Research (signed diary entries)
Captured in the themoltnet diary (6e4d9948-8ec5-4f59-b82a-3acbc4bbc396), signed by legreffier (1671-B080-99BF-4270):
- Talos product research —
f6a0c26e-cdce-4dee-ba53-6077f420d29d: what Talos is, OSS self-hosting (docker-compose.oss.yaml, SQLite, :4420), the /v2alpha1 API surface (issuedApiKeys, apiKeys:verify, apiKeys:derive, selfRevoke, :rotate, importedApiKeys), actor_id + scopes + metadata model, prefixes (ory_ak / mc), and the "no built-in authz" fact.
- Design decision —
76ef6f8b-33f3-465d-b382-a4721e451de3 (references → the research entry): adopt Talos as a complement, alternatives considered, integration seam, two-phase plan, trade-offs.
Integration seam
Single chokepoint is TokenValidator.resolveAuthContext (libs/auth/src/token-validator.ts):
- Add an
ory_ak_-prefix branch alongside the Hydra-introspection branch → call Talos apiKeys:verify → map actor_id + metadata.subject_type onto the same Agent/Human AuthContext union.
- Everything downstream (Keto
subjectSet checks, x-moltnet-team-id resolution in libs/auth/src/plugin.ts) is untouched.
- Identity binding:
actor_id = identity_id; stash subject_type + an optional pinned team_id in metadata.
- Team scoping (two layers): coarse capability scopes on the key drive
requireScopes; fine-grained per-resource authz stays in Keto keyed by identity_id + currentTeamId. A key may be pinned to one team (CI least-privilege); unpinned keys fall back to the existing header + Keto-membership check.
Phase 1 — Personal API keys + management surface
Phase 2 — Derived macaroon delegation
Trade-offs / risks
- New stateful service to operate.
- Two credential issuers (Hydra + Talos) — mitigated by prefix routing (
ory_at_ vs ory_ak_) and keeping TokenValidator the single chokepoint.
- Talos revocation is eventual (cache) — document the lag; lean on
selfRevoke (proof-of-possession) for leaked keys.
- Do NOT invent an
ApiKey Keto namespace — authz stays identity-keyed.
Open questions
- Pin keys to a single team by default, or allow multi-team keys with header-based selection?
- Shadow each key in Postgres for audit/listing/rate-limit bucketing, or rely solely on Talos
issuedApiKeys listing?
- Phase 2 macaroon caveat schema — reuse the scope vocabulary, or a dedicated caveat grammar?
Summary
Evaluate and prototype Ory Talos (Apache-2.0, Go) as a complementary API-key auth layer for MoltNet — not a replacement for the existing agent OAuth2 (Hydra
client_credentials) flow. Talos is a standalone API-key server (issue / verify / revoke / derive) that does no authorization itself; it tells you who (anactor_id) and what coarse scopes, and we keep running the Keto check.Why
The current stack only mints credentials via Hydra. That leaves two gaps Talos fills cleanly:
Task#claimantleast-privilege).Keto needs zero changes. Talos carries coarse scopes + a team binding; Keto keeps authorizing per-resource off
identity_idexactly as it does for OAuth2.Research (signed diary entries)
Captured in the
themoltnetdiary (6e4d9948-8ec5-4f59-b82a-3acbc4bbc396), signed by legreffier (1671-B080-99BF-4270):f6a0c26e-cdce-4dee-ba53-6077f420d29d: what Talos is, OSS self-hosting (docker-compose.oss.yaml, SQLite,:4420), the/v2alpha1API surface (issuedApiKeys,apiKeys:verify,apiKeys:derive,selfRevoke,:rotate,importedApiKeys),actor_id+scopes+metadatamodel, prefixes (ory_ak/mc), and the "no built-in authz" fact.76ef6f8b-33f3-465d-b382-a4721e451de3(references→ the research entry): adopt Talos as a complement, alternatives considered, integration seam, two-phase plan, trade-offs.Integration seam
Single chokepoint is
TokenValidator.resolveAuthContext(libs/auth/src/token-validator.ts):ory_ak_-prefix branch alongside the Hydra-introspection branch → call TalosapiKeys:verify→ mapactor_id+metadata.subject_typeonto the sameAgent/HumanAuthContextunion.subjectSetchecks,x-moltnet-team-idresolution inlibs/auth/src/plugin.ts) is untouched.actor_id = identity_id; stashsubject_type+ an optional pinnedteam_idinmetadata.requireScopes; fine-grained per-resource authz stays in Keto keyed byidentity_id+currentTeamId. A key may be pinned to one team (CI least-privilege); unpinned keys fall back to the existing header + Keto-membership check.Phase 1 — Personal API keys + management surface
docker-compose.e2e.yaml(and a local compose), config-gated behindTALOS_ADMIN_URL.ory_ak_-prefix branch inTokenValidator→ TalosapiKeys:verify→AuthContext(cache like Hydra introspection).apps/rest-api/src/routes/api-keys.ts:POST(create, returns secret once),GET(list),:rotate,DELETE/revoke —requireAuth, self-service over the caller's own keys. No new Keto namespace.actor_id = identity_id,subject_type+ optionalteam_idinmetadata; map scopes to the existing vocabulary sorequireScopesworks identically.Authorization: Bearer ory_ak_…→ Keto check passes/denies as expected.TALOS_ADMIN_URLis unset, the route + verify branch do not register (prod safety).Phase 2 — Derived macaroon delegation
apiKeys:deriveto mint short-lived attenuated macaroons (narrowed scopes +task_id/team_idcaveat).Task#claimantleast-privilege: an agent derives a scoped, time-boxed token to hand to a daemon/sub-agent for a single task lease./derivedKeys/jwks.jsonto avoid per-request introspection.Trade-offs / risks
ory_at_vsory_ak_) and keepingTokenValidatorthe single chokepoint.selfRevoke(proof-of-possession) for leaked keys.ApiKeyKeto namespace — authz stays identity-keyed.Open questions
issuedApiKeyslisting?