one command, every language

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.

zsh
$ 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/
Cendarum prints nothing of its own on a successful run. After the prompts, the terminal belongs to your app.

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.

zsh
$ 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.

precedence
$ 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.

denylist
$ cendarum run --env dev -- printenv LD_PRELOAD cendarum: 'LD_PRELOAD' excluded (loader denylist); pass --only LD_PRELOAD to inject deliberately

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.

Reference

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.

Why the friction is the point

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.

zsh
$ 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
The agent then writes $STRIPE_SECRET_KEY and your shell resolves it. The value is never in the conversation.

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.

An injected secret is visible to your own user account while the process runs.
ps e and /proc/<pid>/environ expose it. That's true of every tool that does environment injection. What it buys is the trade from "in a file on disk indefinitely, copied to six machines, one git add -A from being published" to "in one process, while it runs."
It's a snapshot, not a subscription.
Values are injected at spawn. Rotating a key, revoking a grant, or locking your vault does not reach into a process that is already running. Restart it.
It doesn't work in CI, deliberately.
A human types the master password; a build runner has nobody. Supporting it properly needs a machine identity — a service-account key pair granted access like any other member — which is a load-bearing change to the key hierarchy rather than a flag. There is no environment variable for the master password, for the same reason.

Try it on the project you're annoyed about.