Răsfoiți Sursa

core: remove hardcoded .env read block and use new permissions model instead

Aiden Cline 1 lună în urmă
părinte
comite
3611260405

+ 7 - 0
packages/opencode/src/agent/agent.ts

@@ -47,6 +47,13 @@ export namespace Agent {
       "*": "allow",
       doom_loop: "ask",
       external_directory: "ask",
+      // mirrors github.com/github/gitignore Node.gitignore pattern for .env files
+      read: {
+        "*": "allow",
+        "*.env": "deny",
+        "*.env.*": "deny",
+        "*.env.example": "allow",
+      },
     })
     const user = PermissionNext.fromConfig(cfg.permission ?? {})
 

+ 0 - 16
packages/opencode/src/tool/read.ts

@@ -8,7 +8,6 @@ import DESCRIPTION from "./read.txt"
 import { Filesystem } from "../util/filesystem"
 import { Instance } from "../project/instance"
 import { Identifier } from "../id/id"
-import { iife } from "@/util/iife"
 
 const DEFAULT_READ_LIMIT = 2000
 const MAX_LINE_LENGTH = 2000
@@ -47,21 +46,6 @@ export const ReadTool = Tool.define("read", {
       metadata: {},
     })
 
-    const block = iife(() => {
-      const basename = path.basename(filepath)
-      const whitelist = [".env.sample", ".env.example", ".example", ".env.template"]
-
-      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
-    })
-
-    if (block) {
-      throw new Error(`The user has blocked you from reading ${filepath}, DO NOT make further attempts to read it`)
-    }
-
     const file = Bun.file(filepath)
     if (!(await file.exists())) {
       const dir = path.dirname(filepath)

+ 35 - 18
packages/opencode/test/tool/read.test.ts

@@ -3,7 +3,8 @@ import path from "path"
 import { ReadTool } from "../../src/tool/read"
 import { Instance } from "../../src/project/instance"
 import { tmpdir } from "../fixture/fixture"
-import type { PermissionNext } from "../../src/permission/next"
+import { PermissionNext } from "../../src/permission/next"
+import { Agent } from "../../src/agent/agent"
 
 const ctx = {
   sessionID: "test",
@@ -122,29 +123,45 @@ describe("tool.read external_directory permission", () => {
 })
 
 describe("tool.read env file blocking", () => {
-  test.each([
+  const cases: [string, boolean][] = [
     [".env", true],
     [".env.local", true],
     [".env.production", true],
-    [".env.sample", false],
+    [".env.development.local", true],
     [".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")
-        }
-      },
+  ]
+
+  describe.each(["build", "plan"])("agent=%s", (agentName) => {
+    test.each(cases)("%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 agent = await Agent.get(agentName)
+          const ctxWithPermissions = {
+            ...ctx,
+            ask: async (req: Omit<PermissionNext.Request, "id" | "sessionID" | "tool">) => {
+              for (const pattern of req.patterns) {
+                const rule = PermissionNext.evaluate(req.permission, pattern, agent.permission)
+                if (rule.action === "deny") {
+                  throw new PermissionNext.DeniedError(agent.permission)
+                }
+              }
+            },
+          }
+          const read = await ReadTool.init()
+          const promise = read.execute({ filePath: path.join(tmp.path, filename) }, ctxWithPermissions)
+          if (blocked) {
+            await expect(promise).rejects.toThrow(PermissionNext.DeniedError)
+          } else {
+            expect((await promise).output).toContain("content")
+          }
+        },
+      })
     })
   })
 })