diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts index 27426ad24..fd81c4864 100644 --- a/packages/opencode/src/tool/read.ts +++ b/packages/opencode/src/tool/read.ts @@ -60,10 +60,12 @@ export const ReadTool = Tool.define("read", { } const block = iife(() => { - const whitelist = [".env.sample", ".example"] + const basename = path.basename(filepath) + const whitelist = [".env.sample", ".env.example", ".example", ".env.template"] - if (whitelist.some((w) => filepath.endsWith(w))) return false - if (filepath.includes(".env")) return true + if (whitelist.some((w) => basename.endsWith(w))) return false + // Block .env, .env.local, .env.production, etc. but not .envrc + if (/^\.env(\.|$)/.test(basename)) return true return false }) diff --git a/packages/opencode/test/tool/read.test.ts b/packages/opencode/test/tool/read.test.ts new file mode 100644 index 000000000..47a7aee2a --- /dev/null +++ b/packages/opencode/test/tool/read.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, test } from "bun:test" +import path from "path" +import { ReadTool } from "../../src/tool/read" +import { Instance } from "../../src/project/instance" +import { tmpdir } from "../fixture/fixture" + +const ctx = { + sessionID: "test", + messageID: "", + callID: "", + agent: "build", + abort: AbortSignal.any([]), + metadata: () => {}, +} + +describe("tool.read env file blocking", () => { + test.each([ + [".env", true], + [".env.local", true], + [".env.production", true], + [".env.sample", false], + [".env.example", false], + [".envrc", false], + ["environment.ts", false], + ])("%s blocked=%s", async (filename, blocked) => { + await using tmp = await tmpdir({ + init: (dir) => Bun.write(path.join(dir, filename), "content"), + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const read = await ReadTool.init() + const promise = read.execute({ filePath: path.join(tmp.path, filename) }, ctx) + if (blocked) { + await expect(promise).rejects.toThrow("blocked") + } else { + expect((await promise).output).toContain("content") + } + }, + }) + }) +})