Codumentor logo Codumentor

User Secrets Plugin

Stores per-user API keys and tokens encrypted at rest and lets plugins and the shell tool consume them without exposing the value to the LLM. Use it for GitHub PATs, Redmine/Gitea keys, database passwords, Telegram bot tokens, and similar credentials in a multi-user deployment.

Manage secrets in Settings → Extensions → User Secrets. That requires the user_secrets:manage permission, which the built-in power_user and admin roles have.

How secrets are used

Three delivery modes, all independent of workspace isolation:

${VAR} env-var substitution in YAML continues to work; the syntax is disambiguated by the colon (${secret:…} vs ${VAR}).

ConsumerWhere the placeholder livesWhen it resolves
LLM profile api_keymodels.profiles.<name>.api_key (YAML) or a per-user profileEach LLM call
MCP remote headersMCP server headers in plugin argsEach outbound MCP request
Web-search provider api_keyWeb-search providers.<name>.api_keyEach search
Database passwordDatabase connection password: ${secret:…}Each SQL tool call
Shell tool env varA binding you create in SettingsEach shell command

Needed by plugins

Because secrets are referenced by name, a user otherwise has to know the exact name a plugin expects (e.g. redmine_api_key). Plugins that need a secret declare it, and the User Secrets pane shows a Needed by plugins section — each flagged set or not set, with Add this secret to pre-fill the create form.

Plugins that declare requirements today:

Host-only secrets

When creating a secret you can mark it Host-only (e.g. SCM push token). A host-only secret is never given to the agent and cannot be bound to the shell tool. Features such as SCM Push read it only on the host. Tick this for credentials that must never reach a sandbox.

Rows the assistant manages itself

Some host-only rows are not created by hand. An MCP server configured
with oauth: true keeps each user's authorization here, as a host-only row named
mcp_oauth_<host>_<digest> whose description says which server it belongs to.
There is nothing to edit — the value is a token Codumentor mints and refreshes —
but deleting the row disconnects you from that server, which is a supported
way to do it. The server's tools go quiet from your next turn.

Configuration

plugins:
  - module: codumentor.plugins.user_secrets
    class: UserSecretsPlugin
    priority: 3
    args:
      enabled: true
      max_secrets_per_user: 50          # default: 50
      previous_jwt_secrets: []          # for key-rotation grace period

Load this plugin before consumers that resolve ${secret:…} at startup (keep priority: 3 unless you have a reason to change it).

Parameters

ParameterTypeDefaultDescription
enabledbooltrueMaster on/off.
max_secrets_per_userint50Cap shown in the Settings pane (used / max).
previous_jwt_secretslist[]Previous auth.jwt_secret values, so secrets encrypted under an older key can still be decrypted after rotation.
data_dirstringagent.storage_dirOverride only if the store should not live under the agent storage directory.

Set auth.jwt_secret so secrets are encrypted at rest (keyed off that secret). Rotation: put the old value in this plugin's previous_jwt_secrets, switch auth.jwt_secret, restart; secrets are re-encrypted with the current key the next time they are read.

Using the Settings pane

Settings → Extensions → User Secrets:

  1. Needed by plugins (when any plugin declares requirements) — create the missing ones with the exact name shown.
  2. Secrets — add / edit / rename / delete. Values are write-only: they cannot be read back after save.
  3. Shell-tool env bindings — link a secret to an env var. Optionally mark it visible to the agent. Host-only secrets do not appear in the binding dropdown.

Renaming a secret updates its bindings. Deleting a secret deletes its bindings.

See also