CLOUDMORROW

Resources / Operations handbook

Secrets

Keeping keys and passwords in the vault.

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.