contenox

How to keep secrets away from AI agents (and out of git)

Updated

SAFETY AND CONTROL Keep secrets from agents

To keep secrets away from AI agents, block the agent from reading .env and credential files with a permission rule, keep real keys out of the project folder, and make shell commands ask first so the agent can’t simply cat the file. .gitignore does not help here: it stops commits, not reads.

Anything an agent reads goes into the model’s context. With a hosted model, that means your key was sent to the provider.

How it happens

You ask the agent why the app can’t reach the database. Reasonably, it opens .env to check the settings. Your production database password is now in the conversation, sent to the model, and maybe saved in the session history on disk.

Nobody did anything wrong. That is why it needs a rule, not good intentions.

Other common paths:

  • Command output: env, printenv, docker inspect, kubectl get secret -o yaml, or a verbose test run that logs a connection string.
  • Error messages: a stack trace with a database URL, password included.
  • Search: grep -r "API_KEY" . returns the keys themselves.
  • Copying into code: the agent pastes a key from .env into a test fixture “to make it work”, and it gets committed.

Does my coding agent read .env files?

If nothing stops it, yes. .gitignore only controls what git commits. Some agents skip ignored files when they search, but a direct read or a shell cat .env still works.

Claude Code’s docs also say a .claudeignore file has no effect. Use permission rules instead.

How do I block .env in Claude Code?

Add deny rules to .claude/settings.json in the project, or to ~/.claude/settings.json for all projects:

{
  "permissions": {
    "deny": [
      "Read(**/.env)",
      "Read(**/.env.*)",
      "Read(**/*.pem)",
      "Read(**/*.key)",
      "Read(./secrets/**)",
      "Read(~/.ssh/**)",
      "Read(~/.aws/**)"
    ]
  }
}

These rules cover Claude Code’s file tools and also shell commands it recognizes, such as cat, head and tail. The docs list what they don’t cover: a script that opens the file itself, like a Python one-liner, and a search such as grep -r that reads files without naming them. To close that gap, turn on Claude Code’s sandbox with /sandbox. Your Read deny rules then also apply at the operating-system level to the commands it runs in the sandbox.

How do I keep secrets from Codex?

You can limit which environment variables Codex passes to the commands it runs. In ~/.codex/config.toml:

[shell_environment_policy]
inherit = "core"
ignore_default_excludes = false

inherit = "core" passes only a basic set of variables. ignore_default_excludes = false also strips any variable whose name contains KEY, SECRET or TOKEN. That filter is off by default, so set it yourself.

Inside its workspace-write sandbox, Codex can still read project files, including .env. Keep real values out of the project (next section), and use approval_policy = "on-request" so you see commands before they run.

How do I block .env in OpenCode?

OpenCode already asks before it reads .env and .env.* files. To block them outright, add to opencode.json:

{
  "$schema": "https://opencode.ai/config.json",
  "permission": {
    "read": {
      "*": "allow",
      "*.env": "deny",
      "*.env.*": "deny",
      "*.env.example": "allow"
    }
  }
}

The last matching rule wins, so the *.env.example line must come after the deny lines. Shell commands are a separate rule under bash; set it to ask so a cat .env shows up as a prompt.

How do I set this up in Contenox?

The default permission set already asks before the agent reads .env, .env.*, *.pem or *.key, and every shipped set blocks SSH and GPG keys, cloud credentials, password managers, browser profiles and shell history. To turn the .env prompt into a hard block, open Permissions, go to Read files, and add the paths under Never allow these paths. See built-in tools and permissions.

What about shell commands?

Blocking the file tool is not enough on its own. Make shell commands ask first, so a cat .env or printenv shows up as a prompt you can reject. See AI agent permissions.

How do I keep real keys out of the project?

The safest secret is one that isn’t in the project folder at all.

  • Commit a .env.example with fake values. The agent learns which variables exist without seeing real ones:

    DATABASE_URL=postgres://user:password@localhost:5432/app_dev
    STRIPE_API_KEY=sk_test_replace_me
  • Use development credentials locally. A local database with a throwaway password leaks nothing that matters.

  • Use a secret manager or your OS keychain, and load secrets only in the process that needs them, not in the terminal you start the agent from. If the agent’s shell inherits your environment, it inherits your keys.

  • Keep production credentials off your laptop where you can.

What if the agent needs an API key to do its job?

Sometimes it does, for example to read issues through an MCP server. Then:

  • Create a separate token just for the agent, not your personal one.
  • Give it the smallest scope: read-only if it only reads, one repository instead of all.
  • Set an expiry date.
  • Put the token in the tool’s own config, outside the project folder, never in the prompt or a project file.

See What is MCP? for how servers get their credentials.

How do I stop secrets from being committed?

Agents commit faster than you review. Add a secret scanner as a pre-commit hook. With the pre-commit framework and gitleaks, create .pre-commit-config.yaml in the repo root:

repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.24.2
    hooks:
      - id: gitleaks

Then install it, and move rev to the newest release:

pre-commit install
pre-commit autoupdate

detect-secrets works the same way, with one extra step: create a baseline of what is already there with detect-secrets scan > .secrets.baseline, and pass it to the hook with args: ['--baseline', '.secrets.baseline'].

On GitHub, secret scanning also runs for free on public repositories. Still read the diff before you merge. See how to review AI-written code.

Should I also put it in AGENTS.md?

Yes, as a second line. Permissions enforce; instructions keep the agent from trying, which saves you prompts:

## Secrets
- Never read `.env` or anything under `secrets/`. Use `.env.example` for variable names.
- Never print environment variables.
- Never hardcode credentials. Read them from the environment.

See AGENTS.md explained.

What do I do if a secret already reached the agent?

  1. Rotate it now. Revoke the old value and create a new one. Assume it was exposed.
  2. Search the diff and git history for the value in code, tests and commit messages.
  3. Check the agent’s local session history and logs, and delete entries that contain it where you can.
  4. Close the path it took: add the file to your block list, or stop the command that printed it.

Only rotation actually fixes the leak. The rest is cleanup.

Rules don’t stop scripts

Permission rules match the commands the agent usually writes. A script the agent writes and runs can still open any file your user can read. For untrusted repositories or long unattended runs, put the agent in a container, a VM or a separate OS user that has no access to your real keys: see how to sandbox an AI coding agent. A local model keeps the key from reaching a provider, but the agent can still copy it into code, so the other steps still apply. See how to run local LLMs for coding.

Related

Esc to close