There's no integration to write.
cendarum run decrypts an environment on your machine and hands the values to your program through the spawn syscall itself. No SDK, no config file, nothing written to disk.
$ cendarum run --env dev -- npm run dev Email: you@example.com Master password: Secret Key (SK1-…): > my-api@0.1.0 dev > vite VITE v7.0.4 ready in 412 ms Local: http://localhost:5173/ why it works everywhere
Injection happens below the language.
Process creation hands the new process an environment block — a list of KEY=VALUE pairs. That's an operating-system concept, not a library, and every runtime reads from the same one.
| Node | process.env.X |
|---|---|
| Python | os.environ["X"] |
| Go | os.Getenv("X") |
| Ruby | ENV["X"] |
| Rust | std::env::var("X") |
| Java | System.getenv("X") |
| PHP | getenv("X") |
| shell / Make | $X |
There is exactly one injection path to build, and it serves every language at once — which is why we will never write a per-ecosystem adapter. Child processes inherit the block, so injecting at the top of a tree flows all the way down.
prove it
Nothing leaks into your shell.
The second command is the interesting one. Same variable, no cendarum run, empty line.
$ cendarum run --env dev -- sh -c 'echo $DB_PASSWORD' s3cr3t-smoke-value $ sh -c 'echo $DB_PASSWORD' the rules
Two decisions worth knowing about.
The vault wins
Your real environment is inherited — PATH, HOME, everything — and Cendarum's managed keys are overlaid on top. For the keys it manages, the vault is the source of truth.
This is also why an existing dotenv call in your app stays harmless: those libraries default to "an existing environment variable wins", so they find our value and leave it alone.
$ API_URL=ambient cendarum run --env dev -- printenv API_URL https://api.example.test Loader variables are skipped
Some names don't configure your program — they configure the machinery that starts it. 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 they're skipped, with a warning naming the key, and --only is the explicit per-key override. The denylist runs after scopes compose, so a personal LD_PRELOAD is skipped like a shared one.
$ cendarum run --env dev -- printenv LD_PRELOAD cendarum: 'LD_PRELOAD' excluded (loader denylist); pass --only LD_PRELOAD
to inject deliberately per-developer overrides
Override one key for yourself. The team keeps theirs.
Precedence is ambient, then shared, then personal — and personal wins only for you. The override is encrypted under a key derived from your own key pair; teammates, admins, and our server can't read it.
$ printf 'API_URL=personal-value\nMY_TOKEN=personal-tok\n' \
| cendarum import --env dev --scope personal cendarum: imported 2 personal overrides into dev (version 1) $ cendarum run --env dev -- printenv API_URL cendarum: personal overrides shadow the shared values of: API_URL — verify
these are still current (a rotated team value stays shadowed by your override) personal-value Deliberate friction
import and export require --scope, refused before any prompt or network call. There is no default to forget: a merged default is how a personal secret ends up inside a .env you hand to a teammate.
A read grant can write its own
Writing your own overlay mutates nothing shared, so a read grant is enough. The feature exists for exactly the read-only collaborator who needs a personal token — and nobody, including us, can read what they store.
The import wrote two keys, but the warning names only API_URL — a key that exists only in your overlay shadows nothing. Only real shadows are named, key names only, never values. Personal overrides, in detail
the other two commands
One to get in. One we'd rather you didn't need.
cendarum import
Your existing .env, encrypted, in one command. Comments, quotes, and export prefixes tolerated. Two lines setting the same key is an error naming both line numbers — never a silent last-one-wins.
Writes are scoped: --scope shared for the team's stream, --scope personal for an overlay only you can read. Required, not defaulted — a default would decide where your secrets land.
cendarum export
Writes plaintext to disk, for the rare tool that reads only a file. It warns on every invocation, there is no flag to silence it, and it refuses to write inside a git working tree without --force.
--scope shared|personal|effective is required; effective writes exactly what run would inject, shadow warning included.
a use case people keep asking about
Let an AI agent make real API calls.
You want Claude Code or aider to actually hit the API — a real request, a real error you can debug. So it needs a real key. Paste it into the chat and the model refuses, correctly: a credential in a transcript is a credential sent to a provider.
$ cendarum run --env dev --only STRIPE_SECRET_KEY -- claude Email: you@example.com Master password: Secret Key (SK1-…): the agent now has one key, by name Referenced, not pasted
The agent inherits the environment, so it refers to the key by name. What lands in the transcript is $STRIPE_SECRET_KEY — not the value. Nothing to refuse, because nothing was pasted.
One key, not your whole environment
--only narrows what gets injected. An agent session is a long-running process doing things you did not approve one by one; it does not also need your database URL.
It is not a boundary against the agent — it runs in your environment, and curl -v prints the header. The docs say so plainly, along with what to do if it leaks. Read the whole pattern, limits included.
The honest limitation.
Injection has one exposure, and it's inherent rather than incidental.