# Keep API keys out of OpenCode

What OpenCode's model can see, what zn-gate's OpenCode plugin blocks or scrubs before and after each tool call and on your prompts, what it does not cover, how to set it up and a short test with fake values.

Source: https://usezn.com/docs/integrations/opencode-keys/

OpenCode sends to its model whatever enters the conversation: the prompts you type, the files the agent reads, the output of shell commands and the results of MCP tools. An API key in any of those can reach the model. zn-gate installs a plugin in OpenCode that checks these points. This page explains what the plugin does according to zn-gate's documentation, what it cannot do, and gives you a test to run yourself.

## Where a key can reach the model

zn-gate's OpenCode plugin looks at three moments in a session:

- **A prompt you type.** A secret pasted into it can be blocked before it is sent. OpenCode shows this as an error.
- **A tool call, before it runs.** Reads of `.env` files with values, of credential files and of zn-gate's own `~/.zn` folder can be blocked, whichever tool is used. Writes to `~/.zn` and to the agents' hook and plugin configuration can be blocked too.
- **The output of a tool, before the model sees it.** Secret values in it are replaced with markers. This covers every tool, MCP tools included, and the preview OpenCode shows in its terminal interface.

The plugin has no check on the model's final answer. And OpenCode keeps its own copy of the session locally, so what zn-gate does to what the model sees does not clean everything OpenCode stores (see the limits below).

## What zn-gate checks in OpenCode

`zn hooks install opencode` writes a small plugin, `zn-gate.js`, into OpenCode's plugins folder. This table follows [compat.md](https://github.com/usezn/zn/blob/main/docs/compat.md) in the zn-gate repository, for the current published version.

| Point | What zn-gate does in OpenCode |
|---|---|
| Reads of `.env` files with values, credential files and `~/.zn` | Blocked in all tools. |
| Writes to `~/.zn` and to the agents' hook and plugin configuration | Blocked (write, edit, `apply_patch` and shell). |
| Tool output | Scrubbed in every tool before the model sees it, and in the terminal preview. |
| A secret pasted into a prompt | Blocked. OpenCode shows it as an error. |
| A command that uses `__ZN_<NAME>__` markers | Rewritten automatically to run through `zn run`. OpenCode's own permission rules still apply. |
| The `secrets` MCP server | Optional. `zn hooks install opencode` prints the snippet to add to `opencode.json`. |
| zn-gate's end-to-end run with OpenCode | Yes: 0 values sent to the model provider. |

A rewrite does not approve anything, and OpenCode's own permission rules still apply.

zn-gate does not restore a detected value into a command in OpenCode: the markers stay as they are, because OpenCode keeps the arguments a tool ran with in the session, where the model can read them.

## What zn-gate does not cover in OpenCode

- If the zn plugin cannot run, OpenCode calls and messages are blocked rather than let through. A project install names `zn` on the PATH, so on a machine without zn the plugin cannot run and calls are blocked there.
- A secret with no known format that is not in the vault or in a `.env` file can get through the scrubber.
- The model sees the wrapped command in its own history, including the local paths of `node` and zn (no secret value).
- Live output that OpenCode stores while a command is still running stays in its local database, because it is stored before the plugin can scrub it.
- A shell command or a patch longer than 100 KB is blocked, because zn-gate cannot check it in the time OpenCode waits for the plugin.
- On Windows, this has not been tested. The command wrapping is written for bash: with PowerShell as OpenCode's shell, a command with a marker fails instead of running (no value is exposed).
- OpenCode's documentation lists no setting that turns plugins off without a sign in the agent, so zn-gate has nothing to detect there (checked by zn-gate on 2026-10-17).

## Set it up

You need Node.js 18 or later. If you already installed zn-gate for OpenCode, update it and run the second line again:

```bash
npm install -g zn-gate
zn hooks install opencode
```

Restart OpenCode. Then run `zn-gate check` in a terminal. It should show **OK** for OpenCode ([what the output means](https://usezn.com/docs/check/)). `zn-gate check` confirms zn's rules for OpenCode but does not start OpenCode. The full steps are on the [OpenCode install page](https://usezn.com/docs/install/opencode/).

To keep real values out of your `.env` altogether, move them into zn-gate's vault. Your `.env` then holds markers such as `__ZN_STRIPE_KEY__`:

```bash
zn unlock
zn env shadow .env
```

More in [Scrubber](https://usezn.com/docs/zn-gate/scrubber/). To undo the plugin: `zn hooks uninstall opencode`.

## Test it on your own machine

This test uses fake values. Parts of it follow what the documentation says should happen and have not been run end to end by us on your version of OpenCode, so check each step yourself.

1. In a new empty folder, create a `.env` file with fake values only:

   ```text
   EXAMPLE_API_KEY=sk-test-EXAMPLE-not-a-real-key
   EXAMPLE_URL=https://example.com
   ```

2. Install the plugin (`zn hooks install opencode`), restart OpenCode in that folder, and run `zn-gate check` in another terminal.
3. Ask OpenCode: "Read the file .env and tell me what it contains." According to compat.md, zn-gate blocks reads of `.env` files with values in all tools, so you should see a message from zn-gate instead of the values.
4. Paste into the prompt the AWS documentation example key `AKIAIOSFODNN7EXAMPLE`. If zn-gate recognizes the format, OpenCode should show an error and not send the prompt. If it is not blocked, the format may not be one zn-gate detects.
5. Optional: run `zn unlock` and `zn env shadow .env`, open the file and check that it holds markers. Ask OpenCode to read it again and compare. In zn-gate's own end-to-end run, OpenCode got the markers and not the values.
6. Remove the sample folder when you are done. To remove the plugin: `zn hooks uninstall opencode`.

Only your own results count here. If a step behaves differently from what this page says, [tell us](https://usezn.com/contact/) and include your OpenCode version.

## Frequently asked questions

**Can OpenCode's model see my API keys?** Anything the agent reads, runs or receives can reach the model: files, shell output, MCP results and prompts. zn-gate's plugin checks those points, with the limits above.

**Does zn-gate stop OpenCode from reading `.env`?** Its plugin blocks reads of `.env` files with values in all tools.

**Does it check the model's answer?** No. The plugin checks your prompts, the tool calls and the tool output.

**What if the plugin breaks?** OpenCode calls and messages are blocked until you fix it. Run `zn-gate check` to see what to do.

**How do I undo it?** `zn hooks uninstall opencode`, then restart OpenCode.

## Related pages

- [Keep API keys out of Codex CLI](https://usezn.com/docs/integrations/codex-keys/)
- [I pasted an API key into my coding agent. What now?](https://usezn.com/docs/integrations/pasted-key/)

## Sources

- zn-gate, per-agent compatibility and the OpenCode plugin: [compat.md](https://github.com/usezn/zn/blob/main/docs/compat.md)
- zn-gate, OpenCode install page: [/docs/install/opencode/](https://usezn.com/docs/install/opencode/)
- OpenCode, plugins documentation: <https://opencode.ai/docs/plugins>
