Secrets
Keys and passwords, yours. A secret lives in a vault and an
environment. The vault is default until you name one — home,
work, verticore — and the environment is local until you say
production. Everywhere, a value is hidden until you ask for it.
In the web app
The Secrets tab: your vaults, then an environment's keys. A value is dots until the eye beside it is tapped, and the copy button copies it without showing it. The plus beside the title adds one; a row opens its sheet, with the value in a password box with the same eye.
In the terminal app
Secrets (f3): your vaults down the left, and for the one picked, its
environments across the top and that environment's keys below. ctrl+n
adds one, v reveals the value under the cursor (or hides it again), c
copies it without showing it, enter opens it (ctrl+r shows the value on
the sheet), e works in another environment, n in the vault list names a
new vault.
On the command line
Both have a default, so the common case is bare:
cloudmorrow secret list # keys only; values stay hidden
cloudmorrow secret list -e production
cloudmorrow secret list --all # every environment at once
cloudmorrow secret list --reveal # values too, on purpose
cloudmorrow secret get OPENAI_API_KEY # raw value, nothing else
cloudmorrow secret set OPENAI_API_KEY # prompts, no echo
cloudmorrow secret remove OLD_KEY -e production
cloudmorrow secret vaults # what vaults hold something
cloudmorrow secret environments # what environments this vault has
The rest is the command line's, because it reads and writes files on your
machine. -v names the vault, -e the environment, and the config remembers a
default for each:
cloudmorrow secret list -v verticore
cloudmorrow secret get OPENAI_API_KEY -v verticore -e production
cloudmorrow config set vault home # from now on, -v home is implied
secret get writes the value to stdout and nothing else, which is the whole
point:
cloudmorrow secret get OPENAI_API_KEY > key.txt
export OPENAI_API_KEY="$(cloudmorrow secret get OPENAI_API_KEY)"
secret set never takes the value as an argument — an argument shows up in
ps and lands in your shell history. At a terminal it prompts without echo;
handed something on stdin it takes that instead, minus one trailing newline:
cloudmorrow secret set DATABASE_URL -e production # prompts
printf '%s' "$OPENAI_API_KEY" | cloudmorrow secret set OPENAI_API_KEY
cloudmorrow secret set OPENAI_API_KEY < secret.txt
Whole files, in and out:
cloudmorrow secret import -f .env.production -e production -v verticore
cloudmorrow secret export -e production -f .env # write a .env, mode 600
cloudmorrow secret run -e production -- npm start # no file at all
cloudmorrow secret purge -e staging # the whole environment
cloudmorrow secret purge -v scratch --vault-and-all # the whole vault
Importing merges by default: keys in the file are set, everything else is left
alone. Two flags change that, and --dry-run shows what either would do before
it does it:
| flag | |
|---|---|
--prune |
delete keys the file does not mention, so the environment matches it |
--no-overwrite |
keep values that are already there, fill in only what is missing |
--dry-run |
report what would change and write nothing |
secret run puts the values in a child process's environment and nowhere
else — no file to forget to delete. secret export writes mode 600, and
says so if it just wrote into a git repo that is not ignoring the file.
Environments are free-form: local, development, staging, production,
test, anything you type. Leave -e out and the configured default is used
(local, changed with cloudmorrow config set environment <name>). If the
environment you asked for is empty and others are not, the answer says which.
Vaults are the same shape, and a vault exists exactly as long as it holds
something: there is nothing to create and nothing to delete but the secrets.
Secrets is a Quill. The store, /api/secrets and cm secret are the
foundation and work without it; the Secrets Quill (installed on a fresh
server, and at boot on one that had Secrets built in) is the screens: a kit
list of the secret datamodel, picked through by vault and then
environment, served by the core's vaults backend from this same store.
cm secrets list -g work/production and cm secrets show KEY --reveal are
that screen on the command line. No assistant ever reaches a secret — the
secret datamodel is refused to every MCP tool.
What a vault used to be. Secrets belonged to a project — a name, a
git URL, and a checkout project add read .env files out of and project clone wrote them back into. That is gone: a secret is yours, not a
project's, and where it goes is a place you name. An older server's secrets
keep their place under a vault of the project's name, because the column
was renamed and the values, and their seals, kept.
How they are stored
Values are sealed with AES-256-GCM under the server's key, each bound to the row it belongs to — owner, vault, environment and key — so a sealed value cannot be moved to another row, or another account, without the tamper check failing. Key names, vaults, environments, lengths and a keyed fingerprint of each value stay in the clear, which is what lets a listing be a listing without opening anything. The fingerprint is an HMAC, not a plain hash: it tells two values apart without being guessable. The key, and what else it seals, is Encryption at rest.
Types: what kinds of data there are
Secrets are the first thing in the foundation: a kind of data the server
owns rather than any app. GET /api/types lists every kind there is —
users, secrets, files, shares, machines and notifications from the
foundation, then notes, boards and tasks, calendars and events, channels
and messages from the included apps — with its fields, which of them are
sealed, whether it may be shared, which apps use it and whether an
assistant let in over MCP may reach it. Secrets say no to that last one,
on purpose. Each app's row in /api/server/features names the types it
uses. Nothing enforces yet; this is the catalogue the guard will be
checked against, and the promise that a kind of data an app introduces is
yours, listed here, for any other app you allow.
From docs/MANUAL.md in the Cloudmorrow repository. Something wrong? A pull request there fixes it here.