cendarum run
cendarum run [--project <name|id>] --env <name|id> [--only KEY,KEY] \ [--email <email>] [--shell] -- <cmd> [args...]Everything after -- is executed verbatim. --env is required, and so is the
-- separator with at least one argument after it.
cendarum run --env dev -- npm run devcendarum run --project acme-api --env staging -- ./bin/servercendarum run --env dev -- printenv API_URL| Flag | Meaning |
|---|---|
--env <name|id> |
Required. Which environment to inject |
--project <name|id> |
Disambiguates when the environment name exists in more than one project |
--email <email> |
Account email. Falls back to CENDARUM_EMAIL, then an interactive prompt |
--only KEY,KEY |
Inject only these keys. Repeatable; the lists are combined and de-duplicated, and surrounding whitespace is trimmed. An empty key is a usage error |
--shell |
Run the command through your shell, for pipes and &&. Off by default |
-h, --help |
Print usage |
Precedence: the vault wins
Section titled “Precedence: the vault wins”The child’s environment is your real environment, with Cendarum’s managed keys overlaid on top.
$ API_URL=ambient cendarum run --env dev -- printenv API_URLhttps://api.example.testThe vault value wins, because the vault is the source of truth for the keys it
manages. Everything else in your environment — PATH, HOME, TERM, your
shell’s variables — passes through untouched.
--only narrows what gets injected; anything outside that list is left exactly
as it was.
This also settles the dotenv question. Node’s dotenv, python-dotenv, and
essentially every other loader default to “an existing environment variable
wins”, so a framework that auto-loads .env at startup will find our value
already present and leave it alone. run just works.
The loader denylist
Section titled “The loader denylist”Some variable names don’t configure your program — they configure the machinery
that starts your program. A stored value for one of those could hijack every
process launched under cendarum run, which matters because a teammate with a
write grant can edit your environment.
So those names are skipped, with a warning naming the key:
cendarum: 'LD_PRELOAD' excluded (loader denylist); pass --only LD_PRELOAD to inject deliberatelyThe list is:
| Names | |
|---|---|
| Prefixes | LD_, DYLD_ |
| Exact | PATH, NODE_OPTIONS, PYTHONSTARTUP, PYTHONPATH, PYTHONHOME, RUBYOPT, PERL5OPT, PERL5LIB, BASH_ENV, ENV, ZDOTDIR, IFS, SHELLOPTS, GLIBC_TUNABLES |
Naming a key in --only overrides the skip — the escape hatch is explicit and
per-key, so you can do it when you mean to and can’t do it by accident.
NODE_ENV is not on the list, and neither is anything else that merely
configures your application. Only loader and interpreter bootstrap names are.
--shell
Section titled “--shell”Off by default, because cendarum run -- npm start executing npm directly is
both faster and free of quoting surprises. Turn it on when you need shell syntax:
cendarum run --env dev --shell -- 'echo $FEATURE_FLAG && printenv API_URL'With --shell, exactly one argument may follow -- — quote the whole command.
Two arguments is a usage error rather than a silent surprise.
Exit codes
Section titled “Exit codes”run is transparent, so it composes inside scripts and Makefiles:
| Code | Meaning |
|---|---|
| the child’s | Whatever your command exited with, passed straight through |
128 + n |
The child was killed by signal n (Unix shell convention) |
127 |
The command was not found |
126 |
Found, but not executable |
2 |
Usage error — a bad flag, a missing --env, a missing -- |
$ cendarum run --env dev -- sh -c 'exit 42'; echo "exit=$?"exit=42Signals and streams
Section titled “Signals and streams”stdin, stdout, and stderr are passed through untouched, so interactive programs,
pagers, and progress bars behave normally. SIGINT and SIGTERM are forwarded
to the child.
On a terminal, Ctrl-C reaches the child twice — once from the kernel delivering to the whole process group, once from our forward. Harmless for termination, and the alternative (taking over terminal job control) would stop any child that tried to read from the terminal.
What it does not do
Section titled “What it does not do”- It does not keep watching. The values are a snapshot taken at spawn. A rotation, a revoked grant, or a locked vault does not reach a running process. Restart it.
- It does not work headless. A human types the master password. CI needs a machine identity, which is a different branch of the key hierarchy and deliberately out of scope for now — see the FAQ.
- It does not resolve a trust prompt. If your account keys changed in a way
that needs review,
runrefuses withbundle_review_requiredand sends you to the desktop app, which is where the trust-review interface lives.
The honest limitation
Section titled “The honest limitation”Once injected, the values live in the child’s process environment, and on Linux
that means /proc/<pid>/environ and ps e expose them to anything running as
your user for as long as the process lives.
That is inherent to environment injection and true of every tool that does it.
What it buys you is the trade from “in a file on disk indefinitely, copied to
every machine, one git add -A from being published” to “in one process, while
it runs”. We think that’s clearly the better side of the trade, and we’d rather
state the terms than imply there aren’t any.