Credentials
Centralised encrypted vault for all secrets. Apps reference
credentials by name in YAML; users own their secrets in the
vault; the runtime injects the right value at the right
scope, at the right moment. Replaces inline
{{secret.X}} / {{env.X}} templates (which still work as
a fallback).
Quick map
| Where | What |
|---|---|
auth.client_id / client_secret in the MCP server's YAML | OAuth client_id / secret for that server |
~/.digitorn/server.key | Local encryption key |
credentials table (SQLite / Postgres) | Encrypted vault rows |
Scopes
The schema accepts three scope values on credential.scope:
| Scope | Status |
|---|---|
per_user | Implemented - every vault entry is stored against the user who added it. |
per_app_shared | Accepted by the schema, no runtime behavior yet - resolves the same as per_user. |
system_wide | Accepted by the schema, no runtime behavior yet - resolves the same as per_user. |
There's no system-wide or app-shared credential store behind the
latter two today; every vault row belongs to one user, keyed by
(user_id, provider_name). ref isn't part of the lookup either -
at session start the daemon takes that user's most recently
updated vault entry for the brain's provider, not a specific
named ref. OAuth flows go through the same per-user path.
YAML reference
Two equivalent shapes:
# Compact form (defaults to scope: per_user)
agents:
- id: assistant
brain:
provider: openai
model: gpt-4o
backend: openai_compat
credential: openai_main
# Explicit form (recommended)
agents:
- id: assistant
brain:
provider: openai
model: gpt-4o
backend: openai_compat
credential:
ref: openai_main
scope: per_user
provider: openai
credential.ref and credential.scope parse and are shown back
in the UI, but the actual lookup at session start ignores both -
see Scopes above for what really drives resolution.
Modules expose slots
A consumer module declares one or more credential slot configurations at compile time, specifying what type of credential it accepts, which providers it works with, and how the fields map to the module's internal config paths.
The compiler walks slots + manifests every consumer block; the runtime injector reads the mapping to write decrypted fields at the right path.
Credential types
Six types, compile-time enum-checked wherever a module declares a credential slot:
| Type | Use case |
|---|---|
api_key | Single-field secret (most LLM providers). |
multi_field | Generic key / value bag (e.g. an AWS access key + secret pair). |
oauth2 | Authorization-code flow (Google, Slack, Notion, ...). |
connection_string | DB urls (Postgres, Mongo, Redis, ...). |
mcp_server | stdio/HTTP MCP server config. |
custom | Schemaless escape hatch. |
Provider catalog
Each entry in the built-in catalog is a small record - display
name, category, credential type, icon, its field list (name,
label, whether it's masked in the UI, a prefix to sanity-check like
sk_ for a Stripe key), and an optional verify call (an HTTP
endpoint, method, and the success codes that mean "this key
works"). 18 providers ship today: anthropic, deepseek,
discord, github, github_copilot, google, groq, mistral,
mongodb, mysql, ollama, openai, openrouter, postgres,
redis, telegram, webhook_secret, whatsapp.
The catalog is part of the daemon binary, not a directory of files it reads at startup - adding a provider means adding an entry and rebuilding.
Security architecture
- Key - a single local key file,
~/.digitorn/server.key(created with0600permissions the first time the daemon needs it, 32 random bytes, base64-encoded). There is no KMS backend or environment-variable override - the key lives on the machine running the daemon. - Cipher - NaCl
secretbox(XSalsa20-Poly1305), a random 24-byte nonce per value, stored asnonce || ciphertext, base64-encoded as one string per credential field.
OAuth flow
5 well-known OAuth providers: Google, GitHub, Slack, Microsoft,
Notion. (Discord uses a bot token instead - an api_key
credential, not OAuth.)
The token is refreshed lazily, the moment something needs a fresh one - not on a background timer. Revocation is only wired up for Google and Slack, the two providers with a revoke endpoint on file; revoking for the others just deletes the stored token locally.
MCP stdio token bridging
For stdio MCP servers, the OAuth
token is injected as an environment variable named in
auth.env_token_var, and the subprocess is restarted
when the token refreshes. SSE / HTTP MCP servers send the
token in Authorization: Bearer ... header on every request.
API surface
The credentials surface (catalog browsing, vault CRUD, OAuth start / refresh / status / callback, per-app manifest and schema, health check, and the admin endpoints) exists as HTTP routes on the daemon, but the full endpoint reference is not documented publicly. Public clients use the SDK or the CLI (next section). For direct integration outside of those, contact your daemon administrator.
CLI
digitorn secret list <app-id>
digitorn secret get <app-id> <key>
digitorn secret set <app-id> <key> [value]
digitorn secret delete <app-id> <key>
App credential schema (what keys an app expects) is declared in YAML
under security.credentials_schema. Values are stored with
digitorn secret, not inlined in the manifest.
Migration from {{secret.X}} / {{env.X}}
Old apps used inline templates:
brain:
provider: deepseek
config:
api_key: "{{env.DEEPSEEK_API_KEY}}"
New apps add a credential: block (the inline template can
stay as a dev fallback):
brain:
provider: deepseek
backend: openai_compat
credential:
ref: deepseek_main
scope: per_user
provider: deepseek
config:
api_key: "{{env.DEEPSEEK_API_KEY}}" # dev-only fallback
There's no automated migration command - add the credential:
block by hand and keep the template as the dev fallback shown
above.
Lifecycle states
| State | Meaning |
|---|---|
valid | Stored and, for most types, marked good on save. |
invalid | Verification against the remote provider failed. |
pending | OAuth flow in progress (Copilot device-code flow). |
expired | The flow timed out waiting for the user. |
Cross-references
- App-config block reference (
security.credentials_schema): App Configuration → security - API surface (per-app credential routes): API Integration → Credentials
- MCP OAuth flow (per-app, MCP server token injection): API Integration → OAuth
- Production deployment: Production Deployment