Documentation

Gateway provider secrets

Store a third-party provider API key (OpenAI / Anthropic / custom) on the server, encrypted at rest, used only server-side via the gateway. A deployed agent routes its LLM calls through the AuditTrail gateway and references a stored secret by name — so the plaintext never leaves the box.

This is distinct from BYOK keys, which the Operations Assistant chat uses directly. A gateway secret exists so an agent running anywhere can keep its provider credential server-side.

Warning — secrets are tied to your instance secret key

Secrets are encrypted with a key derived from your instance's AUDITTRAIL_SECRET_KEY (HKDF-SHA256). Rotating AUDITTRAIL_SECRET_KEY invalidates every stored gateway secret — you will need to re-add them.

What is and isn't stored

  • The plaintext value is write-only: it is accepted on create / rotate, encrypted immediately, and never returned by any endpoint, never logged, and never persisted except as a Fernet token.
  • last4 (the last 4 characters of the plaintext) is the only plaintext-derived value persisted — it powers the ••••abcd display so you can tell which key is configured without exposing it.
  • Decryption happens only in the gateway forward path, in a local variable, at the moment the upstream Authorization header is set, then the value is discarded.

How a request selects a secret

Point any OpenAI-compatible client at the gateway proxy with your AuditTrail virtual key, and add the X-AuditTrail-Secret header naming a stored secret (by its name or id):

bash
curl https://your-instance/api/v1/gateway/proxy/v1/chat/completions \
  -H "Authorization: Bearer at-gw-…" \
  -H "X-AuditTrail-Secret: prod-openai" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"hi"}]}'

The gateway resolves prod-openai scoped to the owner of the virtual key, decrypts it in memory, and uses it as the upstream provider credential. The secret is never visible to the client, never echoed in the response, and never logged.

  • Header absent → the gateway uses the instance AUDITTRAIL_GATEWAY_* env key (existing behaviour, unchanged).
  • Header names a secret you don't own404 (existence is never leaked across tenants).

Endpoints

All routes are user-scoped; cross-tenant access returns 404.

MethodPathPurpose
GET/api/v1/agent-secretsList secrets (last4 only — never plaintext)
POST/api/v1/agent-secretsStore a new secret (409 on duplicate name)
PUT/api/v1/agent-secrets/{id}Rotate (re-encrypt with a new value)
DELETE/api/v1/agent-secrets/{id}Delete (204)

POST / PUT carry the plaintext value in the body. No response — list, create, rotate, or otherwise — ever returns it.

CLI — audittrail secrets

The Python SDK's console script (pip install audittrail) manages the same surface from a terminal. It reads AUDITTRAIL_API and AUDITTRAIL_API_KEY (an sk-at-… API key from Settings → API Keys) from the environment; --api / --api-key override per invocation.

bash
audittrail secrets list                            # metadata + last4 only
audittrail secrets add prod-openai --provider openai
audittrail secrets rotate prod-openai
audittrail secrets rm prod-openai                  # type the name to confirm
  • add and rotate read the value from a hidden interactive prompt. There is deliberately no --value flag — argv is visible to every other process on the machine. For scripts, pipe it instead: printf '%s' "$KEY" | audittrail secrets add prod-openai --provider openai --value-stdin
  • rm asks you to type the secret's name back before deleting (--yes skips the confirmation, for scripts).
  • Like the API itself, the CLI never prints a stored value — after add, the only recovery from a lost key is rotate.

UI — /settings?tab=keys

  • Add secret — name it, pick a provider, paste the value. It is shown once and never again.
  • Rotate — paste a new value; the stored ciphertext is replaced.
  • Delete — typed-confirm (tier 2) so a production secret can't be fat-fingered offline.

Supported providers

ProviderNotes
openaiUsed as the Authorization: Bearer … credential to api.openai.com
anthropicUsed as the x-api-key credential to api.anthropic.com
customAny OpenAI-compatible upstream you wire up