Skip to content
All writing

How to Stop OpenCode from Reading .env Files

Use an OpenCode plugin to block common .env reads, credential-file access, and environment dumps before tool execution.

#snippetsdev-toolssecurityai

This is an env-protection.js plugin for OpenCode to prevent the agent from accidentally reading or modifying .env files or dumping environment variables to its memory.

Create ~/.config/opencode/plugins/ and save this as env-protection.js inside it. Local plugins load automatically at startup; restart OpenCode after saving.

// Secret-file guard for every tool that reads/writes via paths or shell.
// Blocks .env-style files, key material, home rc files holding tokens, and
// environment dumps. .env.example / .env.template / env.ts schemas pass.
export const EnvProtection = async () => {
  const EXAMPLE_BASES = new Set([
    ".env.example", ".env.template", ".env.sample", ".env.defaults",
    "env.ts", "env.mjs", "env.d.ts",
  ]);

  const HOME_RC = new Set([
    ".netrc", ".npmrc", ".pypirc", ".yarnrc", ".git-credentials",
    ".pgpass", ".my.cnf", ".htpasswd", ".tfstate", "credentials", ".vercel",
  ]);

  const SECRET_PATH = /(^|\/)(\.ssh|\.gnupg|\.aws|\.kube\/config|\.docker\/config|\.config\/gh|\.cargo\/credentials|\.codex\/config\.toml)(\/|$)/;

  const isSensitive = (value) => {
    if (typeof value !== "string") return false;
    const tokens = (value.match(/[^\s"';&|<>()]+/g) || []).map((t) => t.replace(/^['"]|['"]$/g, ""));
    return tokens.some((t) => {
      const base = t.split("/").pop();
      if (EXAMPLE_BASES.has(base)) return false;
      if (base === ".env" || base.startsWith(".env.") || base === ".envrc") return true;
      if (base === ".dev.vars" || base.startsWith(".dev.vars.")) return true;
      if (/\.(pem|p12|pfx|ppk|p8)$/.test(base)) return true;
      if (/\.key$/.test(base)) return true;
      if (/^(id_rsa|id_dsa|id_ed25519|id_ecdsa)\b/.test(base)) return true;
      if (/^(credentials|secrets)\.(json|ya?ml|toml)$/.test(base)) return true;
      if (HOME_RC.has(base)) return true;
      if (/_history$/.test(base) || /\.[a-z0-9]+_history$/.test(base)) return true;
      if (SECRET_PATH.test(t)) return true;
      if (/\/proc\/[^/]+\/environ/.test(t)) return true;
      return false;
    });
  };

  const isEnvDump = (command) => /\bprintenv\b/.test(command) || /(^|[\s;&|"'])env\s*($|[;&|])/.test(command);

  const SENSITIVE_TOOLS = new Set(["read", "edit", "write", "patch", "bash", "grep"]);

  const blocked = (tool) => `Blocked: access to secret files (${tool}) is not allowed. Use .env.example or real env vars instead.`;

  return {
    "tool.execute.before": async (input, output) => {
      if (!SENSITIVE_TOOLS.has(input.tool)) return;
      const args = output.args || {};

      if (input.tool === "bash") {
        if (isSensitive(args.command) || isEnvDump(args.command)) {
          throw new Error(blocked("bash"));
        }
      } else if (input.tool === "grep") {
        const targets = [args.include, args.path].filter(Boolean).join(" ");
        if (isSensitive(targets)) {
          throw new Error(blocked("grep"));
        }
      } else if (isSensitive(args.filePath)) {
        throw new Error(blocked(input.tool));
      }
    },
  };
};

Test with dummy .env and .env.example files. The guard should deny direct secret-file reads and permit the example file. Checks cover recognized paths and commands, not every possible shell access; keep production secrets outside the agent's reach.

For other Claude Code alternatives and the shared approach, read how to protect secrets from AI coding agents.

Get new posts by email

New writing in your inbox. Unsubscribe anytime.