> ## Documentation Index
> Fetch the complete documentation index at: https://docs.jacobpevans.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Secrets

> Dual backbone — OpenBao for dynamic and leased credentials, Infisical for static app secrets — plus the credential security ladder for agents.

<Note>
  Status: **planned** (phase 4). Infisical (LXC 108) is in-flight today; OpenBao is
  not deployed. The existing [OpenBao page](/security/tools/openbao) predates this
  design and sketched OpenBao as a cluster-local alternative; this design assigns it
  the dynamic-credential role described below.
</Note>

> An unattended agent cannot type a keychain password. Every secret it touches
> must be fetched, scoped, and expiring — or invisible to it entirely.

Today every secret is bound to the macOS keychain with a human present: AWS via
[aws-vault](/security/tools/aws-vault), UniFi creds, five GitHub PAT tiers. None
of that works inside an ephemeral container. The replacement is a **dual
backbone**, each tool doing what it is best at.

## The split

| Backbone                                           | Role                                                               | Examples                                                                       |
| -------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
| **OpenBao** (new LXC, Terraform + Ansible managed) | Dynamic and leased credentials; Terraform/Ansible-consumed secrets | AWS STS, UniFi KV, future PKI / SSH CA / database engines                      |
| **Infisical** (LXC 108, in-flight)                 | Static app secrets, env injection, sync                            | AI model API keys, `infisical run --` runtime env, GitHub Actions secrets sync |

### OpenBao: dynamic credentials and infra trust

* **AWS** — the AWS secrets engine issues **STS credentials** (AssumeRole) against
  narrow per-profile policies: the tf-state bucket and DynamoDB lock table, nothing
  else. This replaces aws-vault and the keychain entirely for Terraform state
  access. Minutes-lived, auto-expiring, no static IAM user anywhere.
* **Terraform-consumed secrets** — UniFi credentials for tofu-unifi move into
  KV-v2 and are read via Vault provider data sources. First-class Terraform and
  Ansible integration is exactly why OpenBao gets this role.
* **Bootstrap** — AppRole with **response wrapping**: the provisioner injects a
  single-use, short-TTL wrapped SecretID at container create (cloud-init for LXC
  consumers, launcher env for agent containers). The unwrap can happen exactly
  once; an intercepted bootstrap is self-evident.

### Infisical: static app secrets

Infisical keeps the Doppler-replacement role from the existing roadmap
([Doppler](/security/tools/doppler) is still decommissioned, phase 7): AI model
API keys, application runtime env via `infisical run`, and GitHub Actions secrets
sync. Machine identities use Universal Auth with TTL, usage-limit, and trusted-IP
constraints on client secrets.

## The credential security ladder

Not every upstream can mint short-lived credentials. The ladder, 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 OpenBao's secrets engine; GitHub App installation tokens (1h) via the
   [broker](/autonomous-agents/github-access). 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 phase 7 item.

<Note>
  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.
</Note>

## Migration: current → target per secret class

| Secret class           | Current                                      | Target                                                                             |
| ---------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------- |
| AWS tf-state access    | aws-vault + keychain, double password prompt | OpenBao AWS secrets engine → STS AssumeRole, narrow per-profile policy             |
| UniFi credentials      | keychain / env at apply time                 | OpenBao KV-v2, read by Vault provider; rotation job; injecting proxy later         |
| AI model API keys      | keychain / Doppler                           | Infisical, injected at container create                                            |
| App runtime env        | Doppler `doppler run`                        | Infisical `infisical run`                                                          |
| GitHub Actions secrets | Doppler sync / manual                        | Infisical sync                                                                     |
| GitHub API + push      | 5-tier PATs in keychain                      | GitHub App + broker tokens — see [GitHub access](/autonomous-agents/github-access) |
| Vault bootstrap        | n/a                                          | AppRole + response-wrapped single-use SecretID at container create                 |

## What stays

* **[SOPS/age](/infrastructure/secrets-sops)** keeps committed deployment config —
  encrypted-at-rest-in-repo is a different job than runtime-leased credentials,
  and both backbones leave it alone.
* **The [macOS keychain](/security/tools/macos-keychain)** shrinks to Mac login
  convenience and human break-glass material. It stops being load-bearing for any
  automated path.

## See also

<CardGroup cols={2}>
  <Card title="GitHub access" icon="github" href="/autonomous-agents/github-access">
    The App + broker design that retires the PAT tiers.
  </Card>

  <Card title="aws-vault" icon="vault" href="/security/tools/aws-vault">
    The tool the AWS STS engine replaces.
  </Card>

  <Card title="OpenBao (original sketch)" icon="lock" href="/security/tools/openbao">
    The earlier cluster-local design this supersedes in part.
  </Card>

  <Card title="SOPS in repos" icon="file-shield" href="/infrastructure/secrets-sops">
    The committed-config layer that stays.
  </Card>
</CardGroup>
