|
|
@@ -12,6 +12,7 @@ import { LayerNode } from "@opencode-ai/util/effect/layer-node"
|
|
|
import { FSUtil } from "@opencode-ai/util/fs-util"
|
|
|
import { Global } from "@opencode-ai/util/global"
|
|
|
import { Permission } from "@opencode-ai/core/permission"
|
|
|
+import { AgentPlugin } from "@opencode-ai/core/plugin/agent"
|
|
|
import { AbsolutePath } from "@opencode-ai/core/schema"
|
|
|
import { ConfigMigrateV1 } from "@opencode-ai/core/v1/config/migrate"
|
|
|
import { advance, drain } from "../lib/clock"
|
|
|
@@ -59,6 +60,96 @@ describe("ConfigAgentPlugin.Plugin", () => {
|
|
|
}),
|
|
|
)
|
|
|
|
|
|
+ it.effect("applies remote permission defaults before explicit global and build rules", () =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const agents = yield* Agent.Service
|
|
|
+ const global = yield* Global.Service
|
|
|
+ yield* AgentPlugin.Plugin.effect(host({ agent: agentHost(agents) }))
|
|
|
+
|
|
|
+ const entries = [
|
|
|
+ new Document({
|
|
|
+ type: "document",
|
|
|
+ info: decode(
|
|
|
+ ConfigMigrateV1.migrate({
|
|
|
+ permission: {
|
|
|
+ bash: "ask",
|
|
|
+ edit: "ask",
|
|
|
+ webfetch: "ask",
|
|
|
+ read: {
|
|
|
+ "*": "allow",
|
|
|
+ "*.env": "deny",
|
|
|
+ "*.env.*": "deny",
|
|
|
+ "*.env.example": "allow",
|
|
|
+ "*.dev.vars": "deny",
|
|
|
+ "~/.local/share/opencode/mcp-auth.json": "deny",
|
|
|
+ "$HOME/.local/share/opencode/mcp-auth.json": "deny",
|
|
|
+ },
|
|
|
+ external_directory: {
|
|
|
+ "*": "ask",
|
|
|
+ "~/.local/share/opencode/*": "deny",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ }),
|
|
|
+ new Document({
|
|
|
+ type: "document",
|
|
|
+ info: decode({
|
|
|
+ permissions: [{ action: "*", resource: "*", effect: "allow" }],
|
|
|
+ agents: {
|
|
|
+ build: {
|
|
|
+ permissions: [
|
|
|
+ { action: "external_directory", resource: "*", effect: "allow" },
|
|
|
+ {
|
|
|
+ action: "external_directory",
|
|
|
+ resource: "~/.local/share/opencode/*",
|
|
|
+ effect: "deny",
|
|
|
+ },
|
|
|
+ { action: "read", resource: "*.env", effect: "deny" },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ }),
|
|
|
+ ]
|
|
|
+
|
|
|
+ yield* ConfigAgentPlugin.Plugin.effect(host({ agent: agentHost(agents) })).pipe(
|
|
|
+ Effect.provide(Config.testLayer(entries)),
|
|
|
+ )
|
|
|
+
|
|
|
+ const build = yield* agents.get(Agent.defaultID)
|
|
|
+ if (!build) throw new Error("expected configured build agent")
|
|
|
+ const opencodeData = path.join(global.home, ".local", "share", "opencode", "*")
|
|
|
+ const mcpAuth = path.join(global.home, ".local", "share", "opencode", "mcp-auth.json")
|
|
|
+ expect(build.permissions).toEqual([
|
|
|
+ ...defaultPermissions(global),
|
|
|
+ { action: "question", resource: "*", effect: "allow" },
|
|
|
+ { action: "shell", resource: "*", effect: "ask" },
|
|
|
+ { action: "edit", resource: "*", effect: "ask" },
|
|
|
+ { action: "webfetch", resource: "*", effect: "ask" },
|
|
|
+ { action: "read", resource: "*", effect: "allow" },
|
|
|
+ { action: "read", resource: "*.env", effect: "deny" },
|
|
|
+ { action: "read", resource: "*.env.*", effect: "deny" },
|
|
|
+ { action: "read", resource: "*.env.example", effect: "allow" },
|
|
|
+ { action: "read", resource: "*.dev.vars", effect: "deny" },
|
|
|
+ { action: "read", resource: mcpAuth, effect: "deny" },
|
|
|
+ { action: "read", resource: mcpAuth, effect: "deny" },
|
|
|
+ { action: "external_directory", resource: "*", effect: "ask" },
|
|
|
+ { action: "external_directory", resource: opencodeData, effect: "deny" },
|
|
|
+ { action: "*", resource: "*", effect: "allow" },
|
|
|
+ { action: "external_directory", resource: "*", effect: "allow" },
|
|
|
+ { action: "external_directory", resource: opencodeData, effect: "deny" },
|
|
|
+ { action: "read", resource: "*.env", effect: "deny" },
|
|
|
+ ])
|
|
|
+ expect(Permission.evaluate("shell", "bun test", build.permissions).effect).toBe("allow")
|
|
|
+ expect(Permission.evaluate("edit", "src/index.ts", build.permissions).effect).toBe("allow")
|
|
|
+ expect(Permission.evaluate("webfetch", "https://example.com", build.permissions).effect).toBe("allow")
|
|
|
+ expect(Permission.evaluate("read", ".env", build.permissions).effect).toBe("deny")
|
|
|
+ expect(Permission.evaluate("external_directory", opencodeData, build.permissions).effect).toBe("deny")
|
|
|
+ expect(Permission.evaluate("external_directory", "/outside/*", build.permissions).effect).toBe("allow")
|
|
|
+ }),
|
|
|
+ )
|
|
|
+
|
|
|
it.effect("applies all global permissions before agent-specific permissions", () =>
|
|
|
Effect.gen(function* () {
|
|
|
const agents = yield* Agent.Service
|