Manage credentials
Credentials are reusable authentication secrets. Inbound webhooks (Trigger a workflow with a webhook), exposed agents (Expose an agent as an API) and HTTP EAI connections reference them by id to check a caller; outbound connections reference them to authenticate to an external system. The secret itself never appears in a workflow YAML or an agent file, both of which are versioned in git.
Kinds
The kinds and their fields are defined once in
libs/shared/src/domain/credentials.ts (CREDENTIAL_KINDS).
| Kind | Fields (secret in bold) | Hint | Use |
|---|---|---|---|
basic |
username, password | username | inbound: Authorization: Basic …; outbound: Basic auth |
header |
header name, value | header name | inbound: the configured header (ex. X-Api-Key), or Authorization: Bearer <value> on the OpenAI surface; outbound: that header |
token |
token | — | outbound only: Authorization: Bearer <token> |
oauth2-client |
token URL, client id, client secret, scope (optional) | client id | outbound only: OAuth2 client-credentials grant |
Inbound auth only accepts basic and header; the console's inbound pickers
list only those.
The Credentials module
The console's Credentials rail module creates, renames, replaces and deletes them. Two properties are deliberate:
- Values never come back. The list and the edit form only show a non-sensitive hint (the username, the header name or the client id). Editing a secret means re-entering it entirely; leaving the secret fields empty on save keeps the stored one.
- Verification is server-side only — an inbound request is compared to the stored secret by the API, in constant time; the console never transports it.
The non-secret fields are part of the secret, so changing one of them means re-entering the whole secret — the form says so rather than saving a half-change.
Deleting a credential is allowed even while webhooks or agents reference it:
those endpoints then answer 503 (broken auth configuration — distinct from
a caller's 401) until reconfigured. Authentication is closed by default: an
unknown auth mode is a 503 too, never an open endpoint.
Storage
Credentials live in a credentials table in the same database as the run
index (RUN_DB_URL: SQLite locally, Postgres in deployment — see
Run store and
Use the Postgres run index). They follow that
database's backup policy: if you back up the run index, you back up the
credentials.
Encryption at rest
Set MERIDIAN_SECRET_KEYS on the proxy to encrypt the data column with
AES-256-GCM. Each secret is bound to its credential id: a value copied onto
another row does not decrypt.
echo "k1:$(openssl rand -base64 32)"
- Format:
kid:base64key[,kid:base64key…], each key 32 bytes. The first key encrypts; every listed key decrypts. - First boot with a key: cleartext secrets are encrypted in place.
- Rotation: put the new key first and keep the old one, restart the proxy (secrets are re-encrypted with the new key at boot), then remove the old key.
- Lost key: an encrypted secret cannot be recovered; re-enter it in the console after setting a new key ring.
- No key: secrets are stored in cleartext and the proxy logs a warning at boot. A store that already holds encrypted secrets refuses to open without its key, so the boot fails rather than the first authenticated call.
Without a key, the confidentiality boundary of every credential is that database: give it the access control, disk encryption and backup handling you would give a secret store, and treat a run-index dump as a secret dump. With a key, keep the key ring out of that database's backups.
What is redacted from run traces
An inbound webhook's headers are journalled with its event, so before writing
them the API drops Authorization, Proxy-Authorization, Cookie,
Set-Cookie and the header name of every header credential in the
store — not just the one that authenticated this call, because several
integrations often share a single URL, each with its own key.
The corollary: a key sent in a header that no credential registers is not recognised as a secret, and will appear in the run's trace.
Related
- Security posture — where inbound authentication sits in the overall model.
- Environment variables —
RUN_DB_URL,MERIDIAN_SECRET_KEYS.