Skip to main content
Status: live. OpenBao is the T2 runtime manager and the primary machine interface. The AWS engine brokers automated and AI-agent AWS access, replacing the static aws-vault base key. The GitHub service engine mints every GitHub credential on demand; its cutover is complete.
An unattended agent cannot type a keychain password. Every secret it touches must be fetched, scoped, and expiring, or invisible to it entirely.
The interactive paths this replaced bound AWS access and GitHub tokens to the macOS keychain with a human present. That does not work inside an ephemeral container or on an always-on agent host. The replacement is the four-tier secrets model, whose T2 runtime manager is built for exactly this read path.

The hard requirement: zero interactive prompts

An always-on agent host has no human at the keyboard when a job fires at 3 AM. A GUI unlock, a sudo Touch ID prompt, an MFA challenge: each is a wall the agent cannot climb. So the hard requirement for any secret an autonomous agent consumes is that the read is non-interactive. It needs an AppRole login or a token file, never a prompt. This is the single property that disqualifies the keychain for automated flows and makes the self-hosted runtime manager the primary interface.

The identity that runs unattended

Zero prompts is not the same as zero gates. The gate moved from the moment of the read to the identity doing the reading. A dedicated automation account on each host is the only identity permitted to escalate privilege without a human present. That grant is scoped to one exact command rather than a general one. Agents run as that account. The interactive human account keeps a biometric gate on every escalation. So a harness that finds its way into a human session still cannot converge a machine. Which harnesses run under the automation account is set out in harness trust tiers. The run-wrapper that launches an agent supplies secret-zero only: the bounded role credentials that let the process authenticate to the runtime manager. Every other value the agent needs resolves from T2 at run time. An ambient environment holding app secrets is the pattern this replaced, not a shortcut still available.

OpenBao: the headless read path

OpenBao (T2) is the primary AI/machine interface. An agent reaches it three ways, all promptless:
  • AppRole login. The agent presents a role ID (durable) plus a secret ID delivered as a response-wrapped, single-use, short-TTL token at container create (cloud-init for a host consumer, launcher env for an agent container). The unwrap happens exactly once; an intercepted bootstrap is self-evident.
  • Rendered token / env file. A lightweight agent renews the lease and renders the secrets the service needs into a tmpfs file the binary reads. Memory-only, destroyed on reboot, never on disk.
  • Process supervision. On a pooled agent guest, that same lightweight agent is the job unit’s entry point rather than a sidecar: it authenticates, renders the job’s credentials straight into the child environment, and execs the job. Credentials exist for the life of one job and never reach a shared file, so no job can start with credentials that were not scoped and leased for it.
  • Signed SSH certificate. For SSH-based automation, OpenBao’s SSH CA signs a short-TTL certificate for the automation principal (ai-agent, ansible, ci). Humans keep their static keys; machines carry expiring certs. No long-lived automation key waits on disk to be stolen.
Two independent dynamic secrets engines back the highest rung of the ladder: The engines have separate root credentials, roles, policies, leases, and consumer endpoints. They share the OpenBao cluster, root-and-admin control plane, Raft storage boundary, hosts, plugin runtime, and outbound HTTPS path. Normal AWS-engine access does not authorize GitHub-engine access, or vice versa. The AWS engine now replaces aws-vault and the keychain for automated state access. A dedicated broker identity assumes narrow purpose roles. That means a role per OpenTofu root, plus a permissions-boundary-capped admin-purpose role for AI and general IaC use. Its own base key is seeded into OpenBao once, write-once, with rotation as a deliberate, separate operator action. So no long-lived AWS key sits anywhere routine automation can reach it. The GitHub engine has likewise replaced stored GitHub tokens: every GitHub credential is now minted on demand and nothing is written to disk. UniFi and similar static-credential upstreams move into KV v2, read by the provider at plan time.

The credential security ladder

Not every upstream can mint short-lived credentials. The ladder runs from best achievable down to the floor. Always climb as high as the upstream allows:
  1. Dynamic, short-lived credentials where the upstream supports it. AWS STS via the OpenBao AWS engine; GitHub App installation tokens via the separate OpenBao GitHub engine. The credential the agent holds expires on its own; exfiltration has a deadline measured in minutes.
  2. Short-lived access to a static credential where the upstream cannot. UniFi is the canonical case: its API keys and local admin passwords are static. There is no rotation API, no STS equivalent. So the short-lived thing becomes the vault token that fetches it: minutes-to-1h lease, IP-bound, single-use bootstrap. Pair it with a dedicated least-privilege account (read-only local admin for exporters) and a scheduled rotation job so the static secret itself has a bounded lifetime.
  3. Credential-injecting egress proxy: the ceiling, and the only way to get “safe even if the agent sees its own token” for backends stuck on static creds. mitmproxy or Envoy’s credential_injector sits outside the boundary and adds the auth header in flight. The agent never sees the credential at all; there is nothing inside the container to exfiltrate. This matches Anthropic’s Agent SDK secure-deployment guidance and is an optional later item.
Rung 2 is honest about its limits: rotating a UniFi password is semi-manual and the secret is still static between rotations. The protection you actually rely on is the dying vault token plus the account’s narrow privileges, not the secrecy half-life of the password.

Migration: current → target per secret class

When a migration is finished

A value has moved out of the bootstrap tier only when all four of these hold. Three of four is not a migration; it is two copies of a secret.
  1. The value was rotated at its source, so the old one is dead.
  2. The new value is written to the central store, at the path its consumer reads.
  3. A live consumer was proven against the central store: a real run, not a configuration read.
  4. The copy in the bootstrap store was deleted, and the consumer proven again with it gone.
The split that decides T2 vs T3 is self-hosted vs SaaS. Credentials for self-hosted services (object storage, the IaC control plane, config-management runners, local databases) move to OpenBao (T2). Third-party SaaS logins (external cloud APIs, package-registry accounts) stay in Doppler (T3). But that is only where an external service consumes the value directly. Think of a hosted CI runner or a vendor integration that reads it without going through T2. SOPS (T1) is correspondingly narrowed to values that are non-critical, front no live or public service, and matter to a single repo.

What stays

  • SOPS/age (T1) keeps committed deployment config. Encrypted-at-rest-in-repo is a different job than runtime-leased credentials, and the runtime manager leaves it alone.
  • The macOS keychain keeps only human break-glass material and login convenience. It is not load-bearing for any automated path, and there is no AI-readable keychain tier. On an always-on Mac agent host the run-wrapper publishes each per-domain AppRole role_id/secret_id into the agent process’s environment, so agent reads stay promptless, and it publishes nothing else.

See also

Tools comparison

The four-tier model, the tool matrix, and the read-path diagram.

OpenBao

The T2 runtime manager this page’s headless path talks to.

GitHub access

The separate GitHub service engine that mints every GitHub credential.

SOPS in repos

The T1 committed-config layer that stays.