secrets
v0.1.4Local protected secret store. Provider keys and other credentials are encrypted under a master key kept outside the project (secret://NAME) or kept as variables in the project's .env (env://NAME), referenced from versioned configuration and resolved only by the workers each secret allowlists; metadata reads never return a value, and the secrets::changed trigger reports rotations and .env edits.
- macOS: arm64 · x64
- Linux: arm64 · armv7 · x64
- Windows: arm64 · x64
exact versions are immutable; binary and bundle artifacts are digest-pinned.
full markdown
/workers/secrets.md. paste it into an llm prompt or pipe it through curl from a worker.install
configuration
- data_dir: data/secrets
env_file: .env
key_file: nulldependencies
readme
secrets
secrets keeps credentials such as provider API keys out of the files you
commit. Configuration stores a reference, which is safe to version, and the
value lives in one of two stores:
- vault —
secret://ANTHROPIC_API_KEY: encrypted in a local vault. A pasted key goes here by default. - env —
env://ANTHROPIC_API_KEY: an environment variable, read from the project's env file —.envunlessenv_filenames another, which this worker can also write — or, when it is not there, from this worker's own environment. For people who prefer keeping keys in.env, and for deploys that inject them as variables.
Either way the value is handed only to the workers you allow, such as
llm-router.
Listing, inspecting and detecting secrets returns masked hints and
fingerprints, never a value.
Install
iii trigger compose::add worker=secretsCompose starts the worker with its configuration dependency. llm-router
depends on secrets, so adding the router from the registry adds it too.
Wait for secrets::status to register before calling it.
Quickstart
Find a key you already have. secrets::detect looks in the worker's own
environment, the project's .env and your login shell, and reports where it
found each name:
iii trigger secrets::detect --json '{"names":["ANTHROPIC_API_KEY"]}'{"results": [{"name": "ANTHROPIC_API_KEY", "stored": false, "sources": [
{"kind": "dotenv", "location": "/home/me/project/.env",
"hint": "sk-ant…9f2c", "matches_stored": false}
]}]}Import it and allow the router to read it. The worker re-reads the value from the source itself; it never passes through the caller or your shell history:
iii trigger secrets::import --json '{
"name": "ANTHROPIC_API_KEY", "source": "dotenv", "consumers": ["llm-router"]
}'{"name": "ANTHROPIC_API_KEY", "ref": "secret://ANTHROPIC_API_KEY",
"consumers": ["llm-router"], "hint": "sk-ant…9f2c",
"fingerprint": "3f9a0c1d2e4b5a67",
"created_at": "2026-10-02T18:04:11.532Z", "updated_at": "2026-10-02T18:04:11.532Z"}Then put the reference, not the key, in the provider's configuration
(Settings → Workers → llm-router, stored in ./config):
providers:
anthropic:
api_key: secret://ANTHROPIC_API_KEYTo keep the key in .env instead, pass "store": "env". With a value,
secrets::set writes ANTHROPIC_API_KEY=… to the project's .env, replacing
an existing definition in place (or an empty # ANTHROPIC_API_KEY=
placeholder) and leaving every other line as it was. A variable already in
.env is shared as it is with secrets::access:
iii trigger secrets::access --json '{
"name": "ANTHROPIC_API_KEY", "consumers": ["llm-router"], "store": "env"
}'{"name": "ANTHROPIC_API_KEY", "ref": "env://ANTHROPIC_API_KEY", "store": "env",
"consumers": ["llm-router"], "hint": "sk-ant…9f2c",
"location": "/home/me/project/.env",
"created_at": "2026-10-06T12:04:11.532Z", "updated_at": "2026-10-06T12:04:11.532Z"}and the provider's configuration gets api_key: env://ANTHROPIC_API_KEY.
The Console's onboarding wizard and every key field are built on the same
calls: pasting a key calls secrets::set, picking a detected one calls
secrets::import, and keeping a variable as it is calls secrets::access
with store: "env". Fingerprints and timestamps above are illustrative.
References
A reference is secret://NAME (the vault) or env://NAME (the env store),
where NAME matches ^[A-Za-z_][A-Za-z0-9_.-]{0,127}$; the scheme is
case-insensitive. Provider keys reuse their environment variable name:
secret://OPENAI_API_KEY, env://TYPESAFE_API_KEY. secrets::resolve also
accepts the bare NAME, as a vault secret. The two stores are separate: the
same NAME can be in the vault and in .env, and each reference reads its
own. III_SECRETS_KEY is reserved and cannot be stored.
Where values and the key live
| What | Where | Why it is not committed |
|---|---|---|
| Encrypted values | , default data/secrets/vault.json in the project |
data/ is gitignored by the project template; even if copied, every value is XChaCha20-Poly1305 ciphertext |
| Master key | III_SECRETS_KEY (base64, 32 bytes) when set; otherwise ${XDG_CONFIG_HOME:-~/.config}/iii/secrets/ |
It never lives in the project. A key_file inside the project directory is refused |
| Env store values | The configured env_file (default ), else this worker's environment |
.env is gitignored by the project template. It is plain text: the vault records only who may read each variable |
| References and settings | ./config/ |
Paths and secret:// / env:// references only |
The vault and the key file are written atomically, owner-only (files 0600,
directories 0700). The key file is created the first time a value is stored.
Each value is sealed with a fresh nonce and bound to its name, so a ciphertext
moved to another record fails to open. The vault header records a check of the
key that sealed it: a different key is refused with KEY_MISMATCH, and a
missing key file is reported, never silently replaced.
Back up the key file, or use III_SECRETS_KEY from your CI or container
secret store. Without the key, stored values cannot be recovered and must be
entered again.
The env store reads .env each time a variable is resolved, so an edit made
by hand applies at once. It never polls the file: the operating system
reports changes to the project directory (inotify, FSEvents or
ReadDirectoryChangesW), and an event naming .env re-reads the shared
variables and reports what changed on secrets::changed. Where the platform
cannot watch, an edit made by hand still applies on the next resolution but
is not announced. A value it writes must fit on one line; .env is written
atomically and left owner-only (0600).
Functions
| Function | Purpose | Agents |
|---|---|---|
secrets::set |
Create or rotate {name, value, consumers?, description?, store?}; omitted consumers keep the current list. store: "env" writes the value to .env |
denied |
secrets::import |
Store a value from process_env, dotenv or login_shell, re-read by the worker. With store: "env", a value already in .env or the worker's environment is shared as it is, and one from the login shell is copied into .env |
denied |
secrets::access |
Replace a secret's consumers; [] revokes everyone. With store: "env", shares the variable, whether or not it is set yet |
denied |
secrets::delete |
Delete a secret and revoke all access. With store: "env", stops sharing the variable; it stays in .env |
denied |
secrets::resolve |
Return {name, value} for secret://NAME or env://NAME to a listed consumer |
denied |
secrets::get / secrets::list |
Metadata: ref, store, masked hint, fingerprint (vault), location (env), consumers, timestamps, last resolver. get takes store too |
allowed |
secrets::status |
Vault path, key source (env or file) and key path, count, vault format version, the .env the env store uses and how many variables it shares |
allowed |
secrets::detect |
Where existing values of the given names are, with masked hints and whether each equals the stored value | approval |
secrets::resolve errors with INVALID_REFERENCE, SECRET_NOT_FOUND or
SECRET_FORBIDDEN. For env://NAME, a variable shared with nobody (or not
with the caller) is SECRET_FORBIDDEN, and one that is shared but not set is
SECRET_NOT_FOUND. Other codes are INVALID_REQUEST, SOURCE_NOT_FOUND
(import), KEY_UNAVAILABLE, KEY_MISMATCH, VAULT_ERROR, DECRYPT_FAILED and
CALLER_LOOKUP_FAILED. No error message contains a value. Schemas are
available through iii trigger .
The "Agents" column is the default in
iii-permissions.yaml.
The Console calls these functions on the person's behalf.
Access scope
consumers lists the worker names allowed to resolve a secret. An empty list
admits nobody, which is the default for a new secret. The same holds for the
env store: an env://NAME reference resolves only for the workers its grant
lists, never for every worker that asks. The caller is the
connection the engine stamps on the invocation (_caller_worker_id, which the
engine overwrites on every call), mapped to its worker name through
engine::workers::list. A worker registered in another namespace never
matches, even with the same name. Each successful resolution records
last_resolved_at and last_resolved_by.
Detection sources
kind |
Reads |
|---|---|
process_env |
The secrets worker's own environment |
dotenv |
(KEY=VALUE, comments, export, single or double quotes, inline # comments) |
login_shell |
$SHELL -ilc printing a marker and env -0, with stdin closed, stderr discarded and a 4 s limit. The shell runs in its own session, with III_RESOLVING_ENVIRONMENT=1 set so a shell profile can skip slow setup |
A source that is missing, broken or slow contributes nothing; it never fails the call. Only the requested names are kept from a source, and only masked hints leave the worker. Login-shell detection is Unix-only.
Custom trigger types
| Trigger type | Fires when | Payload to subscribers |
|---|---|---|
secrets::changed |
A secret is created, rotated, deleted or its consumers change; for the env store, also when an edit to .env sets, changes or removes a shared variable |
{name, ref, action: "created" | "rotated" | "deleted" | "access_changed", fingerprint?, updated_at} |
ref names the store (secret://NAME or env://NAME); env store events
carry no fingerprint. The binding config {names?: string[]} narrows
delivery to some secrets: a bare name for both stores, a secret:// or
env:// reference for one; omit it for every secret. Delivery is
fire-and-forget and never carries a value: a subscriber that needs the new
value calls secrets::resolve.
How llm-router uses it
llm-router depends on this worker. A provider slice whose api_key is
secret://NAME or env://NAME makes the router resolve it through
secrets::resolve instead of reading a literal key. Add llm-router to that
secret's consumers. The router caches the value, binds secrets::changed to
refetch it and refresh that provider's model list (an edit to .env included),
and reports credential_source: "secret" with credential_ref (and
credential_error when it cannot resolve) in router::provider::list.
Other workers can adopt the same pattern, for example judge-typesafe with
secret://TYPESAFE_API_KEY (it reads secret:// references only for now).
Configuration
data_dir: data/secrets # vault folder; relative paths resolve against the project
key_file: null # master key file; absolute or ~/, outside the project
env_file: .env # the env file env:// references read and the console writesEvery Compose namespace has its own secrets entry (),
so each environment or namespace can keep its keys in its own env file. Set it
in that entry's file, ./config/ (an edit applies
without a restart), with iii trigger configuration::set, or per container in
worker-compose.yaml (the ADE has no settings form for this worker yet):
containers:
secrets:
worker: package://secrets
config_override:
env_file: .env.stagingenv_file is relative to the project unless it is absolute or starts with
~/. A missing file is not an error: references then read this worker's
environment, and the first key saved through the console creates the file
(owner-only). Pointing env_file at another file takes effect at once: the
worker follows the new file, and every shared variable whose value differs
there is reported on secrets::changed, so consumers stop using the old
file's values. Keep every env file out of Git (the project template ignores
.env and .env.*).
III_SECRETS_KEY overrides key_file. The worker reads it once at startup
and removes it from its own environment, so shells started by secrets::detect
do not inherit it.
Limits
This first version is a local protected store, not a hardware-backed vault.
The env store is as protected as
.envis: plain text, readable by your user account. Its grants decide who may resolve a variable over the engine, not who can read the file. Use the vault for keys that should be encrypted at rest.Anyone who can read both the vault and the key file can decrypt every value. By default that is your own user account and root. File permissions are the only barrier, and they are not narrowed on Windows.
Consumer identity is a worker name. Any process that can connect to the engine and register as
llm-routerin this namespace while the real router is not connected is treated as the router.III_SECRETS_KEYstays readable in the process table entry for the worker (/proc/) by the same user, even after the worker removes it from its environment./environ A resolved value is plaintext in the consumer's memory and crosses the local engine connection. This worker turns off the SDK's payload capture in traces for its own handlers. Each consumer is responsible for not logging the value it receives.
There is no master-key rotation or OS keychain integration yet. Rotate an individual secret with
secrets::set.
api reference (json)
{
"functions": [
{
"description": "Replace the list of worker names allowed to resolve a secret; [] revokes everyone. With store \"env\", shares the environment variable NAME (env://NAME), whether or not it is set yet.",
"metadata": {},
"name": "secrets::access",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"StoreKind": {
"description": "Where a secret's value lives.",
"oneOf": [
{
"description": "Encrypted in `vault.json`; referenced as `secret://NAME`.",
"enum": [
"vault"
],
"type": "string"
},
{
"description": "An environment variable: the project's `.env`, else this worker's environment; referenced as `env://NAME`. Only who may read it is recorded here.",
"enum": [
"env"
],
"type": "string"
}
]
}
},
"properties": {
"consumers": {
"description": "The complete new allowlist of worker names; `[]` revokes everyone.",
"items": {
"type": "string"
},
"type": "array"
},
"name": {
"type": "string"
},
"store": {
"allOf": [
{
"$ref": "#/definitions/StoreKind"
}
],
"default": "vault",
"description": "`vault` (default; the secret must exist) or `env` (shares the variable, whether or not it is set yet)."
}
},
"required": [
"consumers",
"name"
],
"title": "AccessRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"StoreKind": {
"description": "Where a secret's value lives.",
"oneOf": [
{
"description": "Encrypted in `vault.json`; referenced as `secret://NAME`.",
"enum": [
"vault"
],
"type": "string"
},
{
"description": "An environment variable: the project's `.env`, else this worker's environment; referenced as `env://NAME`. Only who may read it is recorded here.",
"enum": [
"env"
],
"type": "string"
}
]
}
},
"description": "Everything about a secret except its value.",
"properties": {
"consumers": {
"description": "Worker names allowed to resolve the value. Empty: nobody.",
"items": {
"type": "string"
},
"type": "array"
},
"created_at": {
"type": "string"
},
"description": {
"type": [
"string",
"null"
]
},
"fingerprint": {
"description": "First 16 hex characters of HMAC-SHA256(master key, value). Changes on rotation. Vault only: an environment value can change outside this worker.",
"type": [
"string",
"null"
]
},
"hint": {
"description": "Masked value, e.g. `sk-ant…9f2c`; `••••` under 12 characters. Empty for an environment variable that is not set.",
"type": "string"
},
"last_resolved_at": {
"type": [
"string",
"null"
]
},
"last_resolved_by": {
"description": "Worker name of the last successful resolver.",
"type": [
"string",
"null"
]
},
"location": {
"description": "Env store: where the variable's value is read from now (the `.env` path, or this worker's environment); absent when it is not set.",
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"ref": {
"description": "`secret://NAME` or `env://NAME`, the string to put in versioned configuration.",
"type": "string"
},
"store": {
"$ref": "#/definitions/StoreKind"
},
"updated_at": {
"type": "string"
}
},
"required": [
"consumers",
"created_at",
"hint",
"name",
"ref",
"store",
"updated_at"
],
"title": "SecretMeta",
"type": "object"
}
},
{
"description": "Delete a secret and revoke every consumer's access to it. With store \"env\", stops sharing the variable; its value stays in .env.",
"metadata": {},
"name": "secrets::delete",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"StoreKind": {
"description": "Where a secret's value lives.",
"oneOf": [
{
"description": "Encrypted in `vault.json`; referenced as `secret://NAME`.",
"enum": [
"vault"
],
"type": "string"
},
{
"description": "An environment variable: the project's `.env`, else this worker's environment; referenced as `env://NAME`. Only who may read it is recorded here.",
"enum": [
"env"
],
"type": "string"
}
]
}
},
"properties": {
"name": {
"type": "string"
},
"store": {
"allOf": [
{
"$ref": "#/definitions/StoreKind"
}
],
"default": "vault",
"description": "`vault` (default) or `env`."
}
},
"required": [
"name"
],
"title": "NameRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"deleted": {
"type": "boolean"
}
},
"required": [
"deleted"
],
"title": "DeleteResponse",
"type": "object"
}
},
{
"description": "Look for existing values of the given env var names in this worker's environment, the project's .env and the user's login shell. Reports masked hints and whether each equals the stored value; never a value.",
"metadata": {},
"name": "secrets::detect",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"names": {
"description": "Env var names to look for (also the secret names they would be stored under).",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"names"
],
"title": "DetectRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"DetectResult": {
"properties": {
"name": {
"type": "string"
},
"sources": {
"items": {
"$ref": "#/definitions/DetectedSource"
},
"type": "array"
},
"stored": {
"type": "boolean"
},
"stored_hint": {
"type": [
"string",
"null"
]
}
},
"required": [
"name",
"sources",
"stored"
],
"type": "object"
},
"DetectedSource": {
"properties": {
"hint": {
"description": "Masked value found there.",
"type": "string"
},
"kind": {
"$ref": "#/definitions/SourceKind"
},
"location": {
"description": "The secrets worker's environment, the `.env` path, or the login shell path.",
"type": "string"
},
"matches_stored": {
"description": "The value equals the stored one (same fingerprint).",
"type": "boolean"
}
},
"required": [
"hint",
"kind",
"location",
"matches_stored"
],
"type": "object"
},
"SourceKind": {
"enum": [
"process_env",
"dotenv",
"login_shell"
],
"type": "string"
}
},
"properties": {
"results": {
"items": {
"$ref": "#/definitions/DetectResult"
},
"type": "array"
}
},
"required": [
"results"
],
"title": "DetectResponse",
"type": "object"
}
},
{
"description": "One secret's metadata (masked hint, fingerprint, consumers, timestamps; for store \"env\", where the variable is read from), or null. Never the value.",
"metadata": {},
"name": "secrets::get",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"StoreKind": {
"description": "Where a secret's value lives.",
"oneOf": [
{
"description": "Encrypted in `vault.json`; referenced as `secret://NAME`.",
"enum": [
"vault"
],
"type": "string"
},
{
"description": "An environment variable: the project's `.env`, else this worker's environment; referenced as `env://NAME`. Only who may read it is recorded here.",
"enum": [
"env"
],
"type": "string"
}
]
}
},
"properties": {
"name": {
"type": "string"
},
"store": {
"allOf": [
{
"$ref": "#/definitions/StoreKind"
}
],
"default": "vault",
"description": "`vault` (default) or `env`."
}
},
"required": [
"name"
],
"title": "NameRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"anyOf": [
{
"$ref": "#/definitions/SecretMeta"
},
{
"type": "null"
}
],
"definitions": {
"SecretMeta": {
"description": "Everything about a secret except its value.",
"properties": {
"consumers": {
"description": "Worker names allowed to resolve the value. Empty: nobody.",
"items": {
"type": "string"
},
"type": "array"
},
"created_at": {
"type": "string"
},
"description": {
"type": [
"string",
"null"
]
},
"fingerprint": {
"description": "First 16 hex characters of HMAC-SHA256(master key, value). Changes on rotation. Vault only: an environment value can change outside this worker.",
"type": [
"string",
"null"
]
},
"hint": {
"description": "Masked value, e.g. `sk-ant…9f2c`; `••••` under 12 characters. Empty for an environment variable that is not set.",
"type": "string"
},
"last_resolved_at": {
"type": [
"string",
"null"
]
},
"last_resolved_by": {
"description": "Worker name of the last successful resolver.",
"type": [
"string",
"null"
]
},
"location": {
"description": "Env store: where the variable's value is read from now (the `.env` path, or this worker's environment); absent when it is not set.",
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"ref": {
"description": "`secret://NAME` or `env://NAME`, the string to put in versioned configuration.",
"type": "string"
},
"store": {
"$ref": "#/definitions/StoreKind"
},
"updated_at": {
"type": "string"
}
},
"required": [
"consumers",
"created_at",
"hint",
"name",
"ref",
"store",
"updated_at"
],
"type": "object"
},
"StoreKind": {
"description": "Where a secret's value lives.",
"oneOf": [
{
"description": "Encrypted in `vault.json`; referenced as `secret://NAME`.",
"enum": [
"vault"
],
"type": "string"
},
{
"description": "An environment variable: the project's `.env`, else this worker's environment; referenced as `env://NAME`. Only who may read it is recorded here.",
"enum": [
"env"
],
"type": "string"
}
]
}
},
"title": "Nullable_SecretMeta"
}
},
{
"description": "Store a value found by secrets::detect: the worker re-reads it from the named source itself, so it never passes through the caller. With store \"env\", a value found in .env or this worker's environment is shared as it is, and one from the login shell is copied into .env.",
"metadata": {},
"name": "secrets::import",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"SourceKind": {
"enum": [
"process_env",
"dotenv",
"login_shell"
],
"type": "string"
},
"StoreKind": {
"description": "Where a secret's value lives.",
"oneOf": [
{
"description": "Encrypted in `vault.json`; referenced as `secret://NAME`.",
"enum": [
"vault"
],
"type": "string"
},
{
"description": "An environment variable: the project's `.env`, else this worker's environment; referenced as `env://NAME`. Only who may read it is recorded here.",
"enum": [
"env"
],
"type": "string"
}
]
}
},
"properties": {
"consumers": {
"default": null,
"description": "Omit to keep the current list (`[]` for a new secret).",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"description": {
"default": null,
"description": "Omit to keep the current description; an empty string clears it.",
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"source": {
"allOf": [
{
"$ref": "#/definitions/SourceKind"
}
],
"description": "Where the worker re-reads the value; it never travels through the caller."
},
"store": {
"allOf": [
{
"$ref": "#/definitions/StoreKind"
}
],
"default": "vault",
"description": "`vault` (default) or `env`: copied into the project's `.env` (a `dotenv` source is only shared, it is already there)."
}
},
"required": [
"name",
"source"
],
"title": "ImportRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"StoreKind": {
"description": "Where a secret's value lives.",
"oneOf": [
{
"description": "Encrypted in `vault.json`; referenced as `secret://NAME`.",
"enum": [
"vault"
],
"type": "string"
},
{
"description": "An environment variable: the project's `.env`, else this worker's environment; referenced as `env://NAME`. Only who may read it is recorded here.",
"enum": [
"env"
],
"type": "string"
}
]
}
},
"description": "Everything about a secret except its value.",
"properties": {
"consumers": {
"description": "Worker names allowed to resolve the value. Empty: nobody.",
"items": {
"type": "string"
},
"type": "array"
},
"created_at": {
"type": "string"
},
"description": {
"type": [
"string",
"null"
]
},
"fingerprint": {
"description": "First 16 hex characters of HMAC-SHA256(master key, value). Changes on rotation. Vault only: an environment value can change outside this worker.",
"type": [
"string",
"null"
]
},
"hint": {
"description": "Masked value, e.g. `sk-ant…9f2c`; `••••` under 12 characters. Empty for an environment variable that is not set.",
"type": "string"
},
"last_resolved_at": {
"type": [
"string",
"null"
]
},
"last_resolved_by": {
"description": "Worker name of the last successful resolver.",
"type": [
"string",
"null"
]
},
"location": {
"description": "Env store: where the variable's value is read from now (the `.env` path, or this worker's environment); absent when it is not set.",
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"ref": {
"description": "`secret://NAME` or `env://NAME`, the string to put in versioned configuration.",
"type": "string"
},
"store": {
"$ref": "#/definitions/StoreKind"
},
"updated_at": {
"type": "string"
}
},
"required": [
"consumers",
"created_at",
"hint",
"name",
"ref",
"store",
"updated_at"
],
"title": "SecretMeta",
"type": "object"
}
},
{
"description": "Every secret's metadata: vault secrets, then shared environment variables (masked hint, consumers, timestamps). Never a value.",
"metadata": {},
"name": "secrets::list",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "EmptyRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"SecretMeta": {
"description": "Everything about a secret except its value.",
"properties": {
"consumers": {
"description": "Worker names allowed to resolve the value. Empty: nobody.",
"items": {
"type": "string"
},
"type": "array"
},
"created_at": {
"type": "string"
},
"description": {
"type": [
"string",
"null"
]
},
"fingerprint": {
"description": "First 16 hex characters of HMAC-SHA256(master key, value). Changes on rotation. Vault only: an environment value can change outside this worker.",
"type": [
"string",
"null"
]
},
"hint": {
"description": "Masked value, e.g. `sk-ant…9f2c`; `••••` under 12 characters. Empty for an environment variable that is not set.",
"type": "string"
},
"last_resolved_at": {
"type": [
"string",
"null"
]
},
"last_resolved_by": {
"description": "Worker name of the last successful resolver.",
"type": [
"string",
"null"
]
},
"location": {
"description": "Env store: where the variable's value is read from now (the `.env` path, or this worker's environment); absent when it is not set.",
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"ref": {
"description": "`secret://NAME` or `env://NAME`, the string to put in versioned configuration.",
"type": "string"
},
"store": {
"$ref": "#/definitions/StoreKind"
},
"updated_at": {
"type": "string"
}
},
"required": [
"consumers",
"created_at",
"hint",
"name",
"ref",
"store",
"updated_at"
],
"type": "object"
},
"StoreKind": {
"description": "Where a secret's value lives.",
"oneOf": [
{
"description": "Encrypted in `vault.json`; referenced as `secret://NAME`.",
"enum": [
"vault"
],
"type": "string"
},
{
"description": "An environment variable: the project's `.env`, else this worker's environment; referenced as `env://NAME`. Only who may read it is recorded here.",
"enum": [
"env"
],
"type": "string"
}
]
}
},
"properties": {
"secrets": {
"items": {
"$ref": "#/definitions/SecretMeta"
},
"type": "array"
}
},
"required": [
"secrets"
],
"title": "ListResponse",
"type": "object"
}
},
{
"description": "Internal: re-read the secrets configuration after an operator change.",
"metadata": {
"internal": true
},
"name": "secrets::on-config-change",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Trigger payload for `<worker>::on-config-change`. Advisory only: handlers re-fetch the authoritative value and ignore it, so a direct call can never inject config.",
"properties": {
"id": {
"default": null,
"description": "Configuration id that changed.",
"type": [
"string",
"null"
]
}
},
"title": "OnConfigChangeEvent",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Ack returned by the internal `<worker>::on-config-change` handler.",
"properties": {
"ok": {
"type": "boolean"
}
},
"required": [
"ok"
],
"title": "OnConfigChangeResponse",
"type": "object"
}
},
{
"description": "Return the value behind secret://NAME (the vault) or env://NAME (the project's .env, else this worker's environment) to a worker listed in its consumers; anyone else gets SECRET_FORBIDDEN. The caller is identified by the engine, not by the request.",
"metadata": {},
"name": "secrets::resolve",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"ref": {
"description": "`secret://NAME`, `env://NAME`, or a bare `NAME` (the vault).",
"type": "string"
}
},
"required": [
"ref"
],
"title": "ResolveRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"name": {
"type": "string"
},
"value": {
"type": "string"
}
},
"required": [
"name",
"value"
],
"title": "ResolveResponse",
"type": "object"
}
},
{
"description": "Store a credential under NAME (create, or rotate an existing one) and return its metadata. store \"vault\" (default) encrypts it, referenced as secret://NAME; store \"env\" writes NAME=value to the project's .env, referenced as env://NAME. The value is never returned by this or any metadata function. Omitted consumers keep the current allowlist ([] for a new secret).",
"metadata": {},
"name": "secrets::set",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"StoreKind": {
"description": "Where a secret's value lives.",
"oneOf": [
{
"description": "Encrypted in `vault.json`; referenced as `secret://NAME`.",
"enum": [
"vault"
],
"type": "string"
},
{
"description": "An environment variable: the project's `.env`, else this worker's environment; referenced as `env://NAME`. Only who may read it is recorded here.",
"enum": [
"env"
],
"type": "string"
}
]
}
},
"properties": {
"consumers": {
"default": null,
"description": "Worker names allowed to resolve. Omit to keep the current list (`[]` for a new secret).",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"description": {
"default": null,
"description": "Omit to keep the current description; an empty string clears it.",
"type": [
"string",
"null"
]
},
"name": {
"description": "`^[A-Za-z_][A-Za-z0-9_.-]{0,127}$`; provider keys reuse their env var name.",
"type": "string"
},
"store": {
"allOf": [
{
"$ref": "#/definitions/StoreKind"
}
],
"default": "vault",
"description": "`vault` (default) or `env`."
},
"value": {
"description": "The credential. Encrypted in the vault, or written as `NAME=value` to the project's `.env`; never returned except by `secrets::resolve`.",
"type": "string"
}
},
"required": [
"name",
"value"
],
"title": "SetRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"StoreKind": {
"description": "Where a secret's value lives.",
"oneOf": [
{
"description": "Encrypted in `vault.json`; referenced as `secret://NAME`.",
"enum": [
"vault"
],
"type": "string"
},
{
"description": "An environment variable: the project's `.env`, else this worker's environment; referenced as `env://NAME`. Only who may read it is recorded here.",
"enum": [
"env"
],
"type": "string"
}
]
}
},
"description": "Everything about a secret except its value.",
"properties": {
"consumers": {
"description": "Worker names allowed to resolve the value. Empty: nobody.",
"items": {
"type": "string"
},
"type": "array"
},
"created_at": {
"type": "string"
},
"description": {
"type": [
"string",
"null"
]
},
"fingerprint": {
"description": "First 16 hex characters of HMAC-SHA256(master key, value). Changes on rotation. Vault only: an environment value can change outside this worker.",
"type": [
"string",
"null"
]
},
"hint": {
"description": "Masked value, e.g. `sk-ant…9f2c`; `••••` under 12 characters. Empty for an environment variable that is not set.",
"type": "string"
},
"last_resolved_at": {
"type": [
"string",
"null"
]
},
"last_resolved_by": {
"description": "Worker name of the last successful resolver.",
"type": [
"string",
"null"
]
},
"location": {
"description": "Env store: where the variable's value is read from now (the `.env` path, or this worker's environment); absent when it is not set.",
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"ref": {
"description": "`secret://NAME` or `env://NAME`, the string to put in versioned configuration.",
"type": "string"
},
"store": {
"$ref": "#/definitions/StoreKind"
},
"updated_at": {
"type": "string"
}
},
"required": [
"consumers",
"created_at",
"hint",
"name",
"ref",
"store",
"updated_at"
],
"title": "SecretMeta",
"type": "object"
}
},
{
"description": "Where the vault lives, where the master key comes from (III_SECRETS_KEY or a key file outside the project), how many secrets are stored, and the .env the env store uses.",
"metadata": {},
"name": "secrets::status",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "EmptyRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"KeySourceKind": {
"enum": [
"env",
"file"
],
"type": "string"
}
},
"properties": {
"count": {
"format": "uint",
"minimum": 0,
"type": "integer"
},
"env_count": {
"description": "Environment variables shared with at least one worker.",
"format": "uint",
"minimum": 0,
"type": "integer"
},
"env_file": {
"description": "The `.env` the env store reads and writes, when the worker has one.",
"type": [
"string",
"null"
]
},
"key_path": {
"description": "The key file, when `key_source` is `file`.",
"type": [
"string",
"null"
]
},
"key_source": {
"allOf": [
{
"$ref": "#/definitions/KeySourceKind"
}
],
"description": "`env` when III_SECRETS_KEY supplies the master key, else `file`."
},
"vault_path": {
"type": "string"
},
"version": {
"description": "Vault file format version.",
"format": "uint32",
"minimum": 0,
"type": "integer"
}
},
"required": [
"count",
"env_count",
"key_source",
"vault_path",
"version"
],
"title": "StatusResponse",
"type": "object"
}
}
],
"triggers": [
{
"description": "Fires after a secret is created, rotated, deleted or its consumers change. Carries name, ref, action, fingerprint and updated_at, never the value. Config: { names? } to narrow to some secrets.",
"invocation_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"metadata": {
"description": "Metadata attached to every invocation this binding receives."
},
"names": {
"description": "Secret names to fire for: a bare `NAME` for both stores, or `secret://NAME` / `env://NAME` for one. Omit or leave empty for every secret.",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
}
},
"title": "ChangedConfig",
"type": "object"
},
"metadata": {},
"name": "secrets::changed",
"return_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ChangeAction": {
"enum": [
"created",
"rotated",
"deleted",
"access_changed"
],
"type": "string"
}
},
"description": "A `secrets::changed` delivery. Never carries a value.",
"properties": {
"action": {
"$ref": "#/definitions/ChangeAction"
},
"fingerprint": {
"description": "The current fingerprint; absent after a deletion and for the env store.",
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"ref": {
"description": "`secret://NAME` or `env://NAME`.",
"type": "string"
},
"updated_at": {
"type": "string"
}
},
"required": [
"action",
"name",
"ref",
"updated_at"
],
"title": "ChangedEvent",
"type": "object"
}
}
]
}