How it handles secrets
This server sits between a secrets manager and a language model. The interesting part is not what it can do — it is what it refuses to.
Values do not come back
oneguard_env_sync and oneguard_env_push report variable names and a count. The CLI decrypts to disk; nothing in the tool result carries a value.
That is not a convention, it is a test: the suite writes a known value into a mock secret, syncs it, and asserts the string appears in the .env on disk and nowhere in the tool result.
Creating a credential never reveals it
oneguard_secrets_generate has the CLI generate the value and store it directly, merging into the secret so every other variable survives. The agent learns that DB_PASSWORD now exists and nothing more.
oneguard_generate is the one tool that returns a value — a value stored nowhere is useless unless returned — and its description steers the model to the other tool whenever the value is destined for a secret.
The agent's session is not yours
The CLI keeps its key in $HOME/.oneguard/credentials.json, one file shared by every copy of the CLI on the machine. This server hands each subprocess its own HOME.
So an auth login from the agent cannot overwrite the key you use in your terminal, and a key rejected here cannot sign you out there.
Destructive tools ask for proof
Deleting a secret requires its exact name; removing a member requires their exact email. Both are looked up on the server first and compared before anything happens — an agent working from a half-remembered name is stopped rather than allowed to guess.
Tools that replace rather than add say so in the description the model reads, because that is the behaviour that surprises people: oneguard_secrets_edit and oneguard_env_push both overwrite a whole payload.
Two independent brakes
There are two ways to stop an agent changing anything, and they work at different layers:
| | Enforced by | Effect |
| --- | --- | --- |
| A read API key | The OneGuard server | Every write is refused, whatever the agent, this server, or a modified client tries |
| ONEGUARD_MCP_READONLY=1 | This server | The mutating tools are never advertised, so the model cannot be talked into calling one |
Use the key for the guarantee and the flag to keep the tool list focused. oneguard_status reports can_write, so the agent knows which it has before it tries something that will be refused.
Interactive commands are never invoked
oneguard env sync on an unlinked directory prints a numbered menu and blocks on stdin. That cannot work when there is no human on the other end of the pipe — the server would hang until something killed it.
So the selection is turned inside out: on an unlinked directory the server returns the list of vaults for you to choose from, writes the .oneguard link itself, then calls the non-interactive path. Subprocess stdin is closed, so anything that tries to prompt fails immediately instead of hanging.
What this does not protect against
Worth being plain about:
- An agent with a write key can do anything that key can do. The guardrails above make mistakes less likely; they are not a sandbox.
- Values written to
.envare on your disk in plaintext, as they must be for your tooling to read them. Keep.envout of version control. - Anything you paste into a chat is in that chat. Use
oneguard_secrets_generaterather than typing a credential you want to keep private.