Manage tenant secrets

Configure plugin settings

Use settings for addresses, backend names and switches that administrators may read.

For: cluster and tenant administrators.

Use settings for addresses, backend names and switches that administrators may read.

Settings are not secrets

An address, a backend's name or a switch that a plugin reads is a setting, not a secret: the plugin declares it under requires.settings, and a tenant admin sets it with oz0 setting, or in the console on the plugin's Configure tab (Build, Plugins, the plugin, Configure). A setting no one plugin reads (several read it, or none any more) is set under Admin, Settings, Plugin settings, which lists every setting and links each one plugin reads to its Configure tab:

Terminal
oz0 setting set WEB_SEARCH_URL https://search.internal.example
oz0 setting set WEB_SEARCH_BACKEND searxng
oz0 setting list          # names, values and the plugins that read each
oz0 setting delete WEB_SEARCH_BACKEND
  • Stored and shown as they are. Settings are not sealed, and operators and admins of the tenant see their values; only admins set and delete them. Every change is audited (setting.set, setting.deleted) with the value.
  • Never scanned for. The secret scan's fingerprints hold no setting, so a plugin's fetch of a page on the same address as a setting is made. A tenant secret that a plugin's old version read as an optional secret, and whose value its setting of the same name now holds, is left out of them too (the value is shown as the setting already); no other setting takes a secret out of them. A setting never fills a credential binding or a {{secret:NAME}} placeholder.
  • No hiding place for a secret. setting set refuses a value that carries one of the tenant's secrets, the secret of the setting's own name included (an edge checks it: management holds no plaintext secret), a URL with a user name or password in it (https://user:pass@...), and a value in a known token's format, with the fix: keep it a secret.
    secret set of a name that an installed plugin reads only as a setting stores the secret all the same (a plugin source's auth_secret or a model provider's key may be read by that name) and notes that the plugin does not get it: set its value with setting set.
  • On the node: the edge sends a plugin the settings it declares apart from its secrets; its processes get each as an environment variable, its default when the tenant has not set it. A changed setting restarts the node's runtime, as a rotated secret does. The loader's and the node's variables (LD_*, DYLD_*, GLIBC_TUNABLES, GCONV_PATH, OZ0_*) are never settings.
  • Required settings. A setting the plugin marks required has no default: install names each one the tenant has not set, with the command that sets it, and nodes report the plugin failed until it is set (the tenant has not set WEB_SEARCH_URL, which the plugin requires; set it with: orchestrator-zero setting set WEB_SEARCH_URL VALUE), keeping a previous version running if they had one.
    Setting it sends the plugin again, and nodes start it. While a plugin that requires it is installed, setting set refuses an empty value and setting delete refuses to delete it, since either would stop the plugin on every node: give it another value, or remove the plugin first. A node from before required settings starts the plugin without the value.

Settings kept as secrets before

Verified plugins read their settings as optional secrets before settings were apart from secrets (ai.oz0/web 1.0.0's WEB_SEARCH_BACKEND and WEB_SEARCH_URL, ai.oz0/cve-triage 1.0.0's CVE_TRIAGE_OSV_*). A tenant that set them as secrets keeps working when it installs the versions that declare them as settings (web 1.1.0, cve-triage 1.1.0):

  1. Only the plugin that read the name as an optional secret takes it over: when a version of that plugin declares the name as a setting, an edge copies the secret's value to a setting of that name, once (setting.adopted in the audit log, with the value's length and never the value), unless the tenant has that setting already.
    Until then the edge sends the new version the secret's value as the setting, so a version installed under a quality gate or a rollout starts with it.

A value no setting may hold (a URL with a password or a credential in its query, a chat system's webhook URL, a connection string with a password, a token, a value that carries another of the tenant's secrets, one outside the setting's format) is not copied: it stays a secret (setting.not_adopted), and the new version goes without the setting until an admin sets one.

Nor is a value that may be its own credential, though a setting could hold it: a URL whose path names a hook (/hooks/, /catch/, /webhook/), a URL with a random-looking part of 16 or more letters and digits in its path, query or fragment (a private calendar feed, a shared link), or a value that is all base64 or hex of 32 characters or more (an encoded key).

Set the setting yourself when you know such a value is plain; while it holds the secret's value, the secret scan still looks for it.

Only names that read like configuration are taken over. They must meet all three conditions:

  • The last word describes the configuration, such as _URL, _HOST, _BACKEND, _REGION, _MODE, _TIMEOUT or _MODEL.
  • No word names something whose address is often its credential. Examples include CALLBACK, NOTIFY, INCOMING, INVITE, ICS, FEED, SHARE, TRIGGER, PUSH and ACCOUNT; X_NOTIFY_URL and X_SERVICE_ACCOUNT therefore do not qualify.
  • No word reads like a secret. This includes names holding SECRET, PWD, DSN, BEARER, COOKIE, JWT, PRIVATE, HMAC, SIGNING, SIGNATURE or WEBHOOK; starting or ending with KEY, TOKEN or PASS, or their plurals; and names such as AUTH, SAS, SIG, CREDS, SALT, CONNECTION_STRING or a final SESSION. WEB_APIKEYS, WEB_SLACK_WEBHOOK and X_DSN do not qualify.

Any other name is never taken over (setting.not_adopted says so), nor a secret some other plugin declares a setting of, nor one anything else reads as a secret: another plugin, a plugin source's auth_secret, a model provider's key, a routine or a connection.

Such a name stays a secret (setting.not_adopted, which names its readers); set the setting yourself if the plugin needs it. 2. While any installed version still reads the name as a secret (an older version during a rollout, say), that version gets the secret as before. While the setting holds the secret's value, the secret scan does not look for it, so the new version's requests to that address, and fetches of pages there, are made. 3. The secret stays until you delete it: other things read secrets by name (a plugin source's auth_secret, a model provider's key, connections, flows, routines), so no edge deletes one. oz0 setting list notes each setting that is a tenant secret too, and the Plugin settings tab marks it, for someone who may read the tenant's secrets (secrets.read); delete the old secret with oz0 secret delete NAME once nothing else reads it.

Copyright © 2026