Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion packages/opencode/src/permission/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,20 @@ import { Storage } from "@/storage/storage"
import { fn } from "@/util/fn"
import { Log } from "@/util/log"
import { Wildcard } from "@/util/wildcard"
import os from "os"
import z from "zod"

export namespace PermissionNext {
const log = Log.create({ service: "permission" })

function expand(pattern: string): string {
if (pattern.startsWith("~/")) return os.homedir() + pattern.slice(1)
if (pattern === "~") return os.homedir()
if (pattern.startsWith("$HOME/")) return os.homedir() + pattern.slice(5)
if (pattern.startsWith("$HOME")) return os.homedir() + pattern.slice(5)
return pattern
}

export const Action = z.enum(["allow", "deny", "ask"]).meta({
ref: "PermissionAction",
})
Expand Down Expand Up @@ -44,7 +53,9 @@ export namespace PermissionNext {
})
continue
}
ruleset.push(...Object.entries(value).map(([pattern, action]) => ({ permission: key, pattern, action })))
ruleset.push(
...Object.entries(value).map(([pattern, action]) => ({ permission: key, pattern: expand(pattern), action })),
)
}
return ruleset
}
Expand Down
38 changes: 38 additions & 0 deletions packages/opencode/test/permission/next.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { test, expect } from "bun:test"
import os from "os"
import { PermissionNext } from "../../src/permission/next"
import { Instance } from "../../src/project/instance"
import { Storage } from "../../src/storage/storage"
Expand Down Expand Up @@ -38,6 +39,43 @@ test("fromConfig - empty object", () => {
expect(result).toEqual([])
})

test("fromConfig - expands tilde to home directory", () => {
const result = PermissionNext.fromConfig({ external_directory: { "~/projects/*": "allow" } })
expect(result).toEqual([{ permission: "external_directory", pattern: `${os.homedir()}/projects/*`, action: "allow" }])
})

test("fromConfig - expands $HOME to home directory", () => {
const result = PermissionNext.fromConfig({ external_directory: { "$HOME/projects/*": "allow" } })
expect(result).toEqual([{ permission: "external_directory", pattern: `${os.homedir()}/projects/*`, action: "allow" }])
})

test("fromConfig - expands $HOME without trailing slash", () => {
const result = PermissionNext.fromConfig({ external_directory: { $HOME: "allow" } })
expect(result).toEqual([{ permission: "external_directory", pattern: os.homedir(), action: "allow" }])
})

test("fromConfig - does not expand tilde in middle of path", () => {
const result = PermissionNext.fromConfig({ external_directory: { "/some/~/path": "allow" } })
expect(result).toEqual([{ permission: "external_directory", pattern: "/some/~/path", action: "allow" }])
})

test("fromConfig - expands exact tilde to home directory", () => {
const result = PermissionNext.fromConfig({ external_directory: { "~": "allow" } })
expect(result).toEqual([{ permission: "external_directory", pattern: os.homedir(), action: "allow" }])
})

test("evaluate - matches expanded tilde pattern", () => {
const ruleset = PermissionNext.fromConfig({ external_directory: { "~/projects/*": "allow" } })
const result = PermissionNext.evaluate("external_directory", `${os.homedir()}/projects/file.txt`, ruleset)
expect(result.action).toBe("allow")
})

test("evaluate - matches expanded $HOME pattern", () => {
const ruleset = PermissionNext.fromConfig({ external_directory: { "$HOME/projects/*": "allow" } })
const result = PermissionNext.evaluate("external_directory", `${os.homedir()}/projects/file.txt`, ruleset)
expect(result.action).toBe("allow")
})

// merge tests

test("merge - simple concatenation", () => {
Expand Down
8 changes: 8 additions & 0 deletions packages/web/src/content/docs/permissions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ Permission patterns use simple wildcard matching:
- `?` matches exactly one character
- All other characters match literally

### Home Directory Expansion

You can use `~` or `$HOME` at the start of a pattern to reference your home directory. This is particularly useful for `external_directory` rules.

- `~/projects/*` -> `/Users/username/projects/*`
- `$HOME/projects/*` -> `/Users/username/projects/*`
- `~` -> `/Users/username`

---

## Available Permissions
Expand Down