This commit is contained in:
Dax Raad 2025-12-19 17:01:53 -05:00
parent 16381bb20e
commit 598c6171cd
7 changed files with 228 additions and 251 deletions

View file

@ -1,10 +0,0 @@
---
description: Use this agent when you are asked to commit and push code changes to a git repository.
mode: subagent
---
You commit and push to git
Commit messages should be brief since they are used to generate release notes.
Messages should say WHY the change was made and not WHAT was changed.

View file

@ -5,9 +5,7 @@
// "url": "https://enterprise.dev.opencode.ai",
// },
"instructions": ["STYLE_GUIDE.md"],
"permission": {
"*": "ask",
},
// "permission": "deny",
"provider": {
"opencode": {
"options": {},

View file

@ -241,7 +241,8 @@ const AgentListCommand = cmd({
})
for (const agent of sortedAgents) {
process.stdout.write(`${agent.name} (${agent.mode})${EOL}`)
process.stdout.write(`${agent.name} (${agent.mode})` + EOL)
process.stdout.write(` ${JSON.stringify(agent.permission, null, 2)}` + EOL)
}
},
})

View file

@ -410,6 +410,8 @@ export namespace Config {
doom_loop: PermissionAction.optional(),
})
.catchall(PermissionRule)
.or(PermissionAction)
.transform((x) => (typeof x === "string" ? { "*": x } : x))
.meta({
ref: "PermissionConfig",
})

View file

@ -6,7 +6,6 @@ import { Instance } from "@/project/instance"
import { fn } from "@/util/fn"
import { Log } from "@/util/log"
import { Wildcard } from "@/util/wildcard"
import { sortBy } from "remeda"
import z from "zod"
export namespace PermissionNext {
@ -19,6 +18,7 @@ export namespace PermissionNext {
export const Rule = z
.object({
permission: z.string(),
pattern: z.string(),
action: Action,
})
@ -27,40 +27,29 @@ export namespace PermissionNext {
})
export type Rule = z.infer<typeof Rule>
export const Ruleset = z.record(z.string(), Rule.array()).meta({
export const Ruleset = Rule.array().meta({
ref: "PermissionRuleset",
})
export type Ruleset = z.infer<typeof Ruleset>
export function fromConfig(permission: Config.Permission) {
const ruleset: Ruleset = {}
const ruleset: Ruleset = []
for (const [key, value] of Object.entries(permission)) {
if (typeof value === "string") {
ruleset[key] = [
{
action: value,
pattern: "*",
},
]
ruleset.push({
permission: key,
action: value,
pattern: "*",
})
continue
}
ruleset[key] = Object.entries(value).map(([pattern, action]) => ({ pattern, action }))
ruleset.push(...Object.entries(value).map(([pattern, action]) => ({ permission: key, pattern, action })))
}
return ruleset
}
export function merge(...rulesets: Ruleset[]): Ruleset {
const result: Ruleset = {}
for (const ruleset of rulesets) {
for (const [permission, rules] of Object.entries(ruleset)) {
if (!result[permission]) {
result[permission] = rules
continue
}
result[permission] = result[permission].concat(rules)
}
}
return result as Ruleset
return rulesets.flat()
}
export const Request = z
@ -205,20 +194,15 @@ export namespace PermissionNext {
export function evaluate(permission: string, pattern: string, ruleset: Ruleset): Action {
log.info("evaluate", { permission, pattern, ruleset })
const rules: Rule[] = []
const entries = sortBy(Object.entries(ruleset), ([k]) => k.length)
for (const [permPattern, permRules] of entries) {
if (Wildcard.match(permission, permPattern)) {
rules.push(...permRules)
}
}
const match = rules.findLast((rule) => Wildcard.match(pattern, rule.pattern))
const match = ruleset.findLast(
(rule) => Wildcard.match(permission, rule.permission) && Wildcard.match(pattern, rule.pattern),
)
return match?.action ?? "ask"
}
const EDIT_TOOLS = ["edit", "write", "patch", "multiedit"]
export function disabledTools(tools: string[], ruleset: Ruleset): Set<string> {
export function disabled(tools: string[], ruleset: Ruleset): Set<string> {
const disabled = new Set<string>()
for (const tool of tools) {
const permission = EDIT_TOOLS.includes(tool) ? "edit" : tool

View file

@ -188,7 +188,7 @@ export namespace LLM {
}
async function resolveTools(input: Pick<StreamInput, "tools" | "agent" | "user">) {
const disabled = PermissionNext.disabledTools(Object.keys(input.tools), input.agent.permission)
const disabled = PermissionNext.disabled(Object.keys(input.tools), input.agent.permission)
for (const tool of Object.keys(input.tools)) {
if (input.user.tools?.[tool] === false || disabled.has(tool)) {
delete input.tools[tool]

View file

@ -5,17 +5,15 @@ import { PermissionNext } from "../../src/permission/next"
test("fromConfig - string value becomes wildcard rule", () => {
const result = PermissionNext.fromConfig({ bash: "allow" })
expect(result).toEqual({ bash: [{ pattern: "*", action: "allow" }] })
expect(result).toEqual([{ permission: "bash", pattern: "*", action: "allow" }])
})
test("fromConfig - object value converts to rules array", () => {
const result = PermissionNext.fromConfig({ bash: { "*": "allow", rm: "deny" } })
expect(result).toEqual({
bash: [
{ pattern: "*", action: "allow" },
{ pattern: "rm", action: "deny" },
],
})
expect(result).toEqual([
{ permission: "bash", pattern: "*", action: "allow" },
{ permission: "bash", pattern: "rm", action: "deny" },
])
})
test("fromConfig - mixed string and object values", () => {
@ -24,310 +22,309 @@ test("fromConfig - mixed string and object values", () => {
edit: "allow",
webfetch: "ask",
})
expect(result).toEqual({
bash: [
{ pattern: "*", action: "allow" },
{ pattern: "rm", action: "deny" },
],
edit: [{ pattern: "*", action: "allow" }],
webfetch: [{ pattern: "*", action: "ask" }],
})
expect(result).toEqual([
{ permission: "bash", pattern: "*", action: "allow" },
{ permission: "bash", pattern: "rm", action: "deny" },
{ permission: "edit", pattern: "*", action: "allow" },
{ permission: "webfetch", pattern: "*", action: "ask" },
])
})
test("fromConfig - empty object", () => {
const result = PermissionNext.fromConfig({})
expect(result).toEqual({})
expect(result).toEqual([])
})
// merge tests
test("merge - simple concatenation", () => {
const result = PermissionNext.merge(
{ bash: [{ pattern: "*", action: "allow" }] },
{ bash: [{ pattern: "*", action: "deny" }] },
[{ permission: "bash", pattern: "*", action: "allow" }],
[{ permission: "bash", pattern: "*", action: "deny" }],
)
expect(result).toEqual({
bash: [
{ pattern: "*", action: "allow" },
{ pattern: "*", action: "deny" },
],
})
expect(result).toEqual([
{ permission: "bash", pattern: "*", action: "allow" },
{ permission: "bash", pattern: "*", action: "deny" },
])
})
test("merge - adds new permission", () => {
const result = PermissionNext.merge(
{ bash: [{ pattern: "*", action: "allow" }] },
{ edit: [{ pattern: "*", action: "deny" }] },
[{ permission: "bash", pattern: "*", action: "allow" }],
[{ permission: "edit", pattern: "*", action: "deny" }],
)
expect(result).toEqual({
bash: [{ pattern: "*", action: "allow" }],
edit: [{ pattern: "*", action: "deny" }],
})
expect(result).toEqual([
{ permission: "bash", pattern: "*", action: "allow" },
{ permission: "edit", pattern: "*", action: "deny" },
])
})
test("merge - concatenates rules for same permission", () => {
const result = PermissionNext.merge(
{ bash: [{ pattern: "foo", action: "ask" }] },
{ bash: [{ pattern: "*", action: "deny" }] },
[{ permission: "bash", pattern: "foo", action: "ask" }],
[{ permission: "bash", pattern: "*", action: "deny" }],
)
expect(result).toEqual({
bash: [
{ pattern: "foo", action: "ask" },
{ pattern: "*", action: "deny" },
],
})
expect(result).toEqual([
{ permission: "bash", pattern: "foo", action: "ask" },
{ permission: "bash", pattern: "*", action: "deny" },
])
})
test("merge - multiple rulesets", () => {
const result = PermissionNext.merge(
{ bash: [{ pattern: "*", action: "allow" }] },
{ bash: [{ pattern: "rm", action: "ask" }] },
{ edit: [{ pattern: "*", action: "allow" }] },
[{ permission: "bash", pattern: "*", action: "allow" }],
[{ permission: "bash", pattern: "rm", action: "ask" }],
[{ permission: "edit", pattern: "*", action: "allow" }],
)
expect(result).toEqual({
bash: [
{ pattern: "*", action: "allow" },
{ pattern: "rm", action: "ask" },
],
edit: [{ pattern: "*", action: "allow" }],
})
expect(result).toEqual([
{ permission: "bash", pattern: "*", action: "allow" },
{ permission: "bash", pattern: "rm", action: "ask" },
{ permission: "edit", pattern: "*", action: "allow" },
])
})
test("merge - empty ruleset does nothing", () => {
const result = PermissionNext.merge({ bash: [{ pattern: "*", action: "allow" }] }, {})
expect(result).toEqual({ bash: [{ pattern: "*", action: "allow" }] })
const result = PermissionNext.merge([{ permission: "bash", pattern: "*", action: "allow" }], [])
expect(result).toEqual([{ permission: "bash", pattern: "*", action: "allow" }])
})
test("merge - preserves rule order", () => {
const result = PermissionNext.merge(
{
edit: [
{ pattern: "src/*", action: "allow" },
{ pattern: "src/secret/*", action: "deny" },
],
},
{ edit: [{ pattern: "src/secret/ok.ts", action: "allow" }] },
)
expect(result).toEqual({
edit: [
{ pattern: "src/*", action: "allow" },
{ pattern: "src/secret/*", action: "deny" },
{ pattern: "src/secret/ok.ts", action: "allow" },
[
{ permission: "edit", pattern: "src/*", action: "allow" },
{ permission: "edit", pattern: "src/secret/*", action: "deny" },
],
})
[{ permission: "edit", pattern: "src/secret/ok.ts", action: "allow" }],
)
expect(result).toEqual([
{ permission: "edit", pattern: "src/*", action: "allow" },
{ permission: "edit", pattern: "src/secret/*", action: "deny" },
{ permission: "edit", pattern: "src/secret/ok.ts", action: "allow" },
])
})
test("merge - config permission overrides default ask", () => {
// Simulates: defaults have "*": "ask", config sets bash: "allow"
const defaults: PermissionNext.Ruleset = [{ permission: "*", pattern: "*", action: "ask" }]
const config: PermissionNext.Ruleset = [{ permission: "bash", pattern: "*", action: "allow" }]
const merged = PermissionNext.merge(defaults, config)
// Config's bash allow should override default ask
expect(PermissionNext.evaluate("bash", "ls", merged)).toBe("allow")
// Other permissions should still be ask (from defaults)
expect(PermissionNext.evaluate("edit", "foo.ts", merged)).toBe("ask")
})
test("merge - config ask overrides default allow", () => {
// Simulates: defaults have bash: "allow", config sets bash: "ask"
const defaults: PermissionNext.Ruleset = [{ permission: "bash", pattern: "*", action: "allow" }]
const config: PermissionNext.Ruleset = [{ permission: "bash", pattern: "*", action: "ask" }]
const merged = PermissionNext.merge(defaults, config)
// Config's ask should override default allow
expect(PermissionNext.evaluate("bash", "ls", merged)).toBe("ask")
})
// evaluate tests
test("evaluate - exact pattern match", () => {
const result = PermissionNext.evaluate("bash", "rm", { bash: [{ pattern: "rm", action: "deny" }] })
const result = PermissionNext.evaluate("bash", "rm", [{ permission: "bash", pattern: "rm", action: "deny" }])
expect(result).toBe("deny")
})
test("evaluate - wildcard pattern match", () => {
const result = PermissionNext.evaluate("bash", "rm", { bash: [{ pattern: "*", action: "allow" }] })
const result = PermissionNext.evaluate("bash", "rm", [{ permission: "bash", pattern: "*", action: "allow" }])
expect(result).toBe("allow")
})
test("evaluate - last matching rule wins", () => {
const result = PermissionNext.evaluate("bash", "rm", {
bash: [
{ pattern: "*", action: "allow" },
{ pattern: "rm", action: "deny" },
],
})
const result = PermissionNext.evaluate("bash", "rm", [
{ permission: "bash", pattern: "*", action: "allow" },
{ permission: "bash", pattern: "rm", action: "deny" },
])
expect(result).toBe("deny")
})
test("evaluate - last matching rule wins (wildcard after specific)", () => {
const result = PermissionNext.evaluate("bash", "rm", {
bash: [
{ pattern: "rm", action: "deny" },
{ pattern: "*", action: "allow" },
],
})
const result = PermissionNext.evaluate("bash", "rm", [
{ permission: "bash", pattern: "rm", action: "deny" },
{ permission: "bash", pattern: "*", action: "allow" },
])
expect(result).toBe("allow")
})
test("evaluate - glob pattern match", () => {
const result = PermissionNext.evaluate("edit", "src/foo.ts", { edit: [{ pattern: "src/*", action: "allow" }] })
const result = PermissionNext.evaluate("edit", "src/foo.ts", [
{ permission: "edit", pattern: "src/*", action: "allow" },
])
expect(result).toBe("allow")
})
test("evaluate - last matching glob wins", () => {
const result = PermissionNext.evaluate("edit", "src/components/Button.tsx", {
edit: [
{ pattern: "src/*", action: "deny" },
{ pattern: "src/components/*", action: "allow" },
],
})
const result = PermissionNext.evaluate("edit", "src/components/Button.tsx", [
{ permission: "edit", pattern: "src/*", action: "deny" },
{ permission: "edit", pattern: "src/components/*", action: "allow" },
])
expect(result).toBe("allow")
})
test("evaluate - order matters for specificity", () => {
// If more specific rule comes first, later wildcard overrides it
const result = PermissionNext.evaluate("edit", "src/components/Button.tsx", {
edit: [
{ pattern: "src/components/*", action: "allow" },
{ pattern: "src/*", action: "deny" },
],
})
const result = PermissionNext.evaluate("edit", "src/components/Button.tsx", [
{ permission: "edit", pattern: "src/components/*", action: "allow" },
{ permission: "edit", pattern: "src/*", action: "deny" },
])
expect(result).toBe("deny")
})
test("evaluate - unknown permission returns ask", () => {
const result = PermissionNext.evaluate("unknown_tool", "anything", {
bash: [{ pattern: "*", action: "allow" }],
})
const result = PermissionNext.evaluate("unknown_tool", "anything", [
{ permission: "bash", pattern: "*", action: "allow" },
])
expect(result).toBe("ask")
})
test("evaluate - empty ruleset returns ask", () => {
const result = PermissionNext.evaluate("bash", "rm", {})
const result = PermissionNext.evaluate("bash", "rm", [])
expect(result).toBe("ask")
})
test("evaluate - no matching pattern returns ask", () => {
const result = PermissionNext.evaluate("edit", "etc/passwd", { edit: [{ pattern: "src/*", action: "allow" }] })
const result = PermissionNext.evaluate("edit", "etc/passwd", [
{ permission: "edit", pattern: "src/*", action: "allow" },
])
expect(result).toBe("ask")
})
test("evaluate - empty rules array returns ask", () => {
const result = PermissionNext.evaluate("bash", "rm", { bash: [] })
const result = PermissionNext.evaluate("bash", "rm", [])
expect(result).toBe("ask")
})
test("evaluate - multiple matching patterns, last wins", () => {
const result = PermissionNext.evaluate("edit", "src/secret.ts", {
edit: [
{ pattern: "*", action: "ask" },
{ pattern: "src/*", action: "allow" },
{ pattern: "src/secret.ts", action: "deny" },
],
})
const result = PermissionNext.evaluate("edit", "src/secret.ts", [
{ permission: "edit", pattern: "*", action: "ask" },
{ permission: "edit", pattern: "src/*", action: "allow" },
{ permission: "edit", pattern: "src/secret.ts", action: "deny" },
])
expect(result).toBe("deny")
})
test("evaluate - non-matching patterns are skipped", () => {
const result = PermissionNext.evaluate("edit", "src/foo.ts", {
edit: [
{ pattern: "*", action: "ask" },
{ pattern: "test/*", action: "deny" },
{ pattern: "src/*", action: "allow" },
],
})
const result = PermissionNext.evaluate("edit", "src/foo.ts", [
{ permission: "edit", pattern: "*", action: "ask" },
{ permission: "edit", pattern: "test/*", action: "deny" },
{ permission: "edit", pattern: "src/*", action: "allow" },
])
expect(result).toBe("allow")
})
test("evaluate - exact match at end wins over earlier wildcard", () => {
const result = PermissionNext.evaluate("bash", "/bin/rm", {
bash: [
{ pattern: "*", action: "allow" },
{ pattern: "/bin/rm", action: "deny" },
],
})
const result = PermissionNext.evaluate("bash", "/bin/rm", [
{ permission: "bash", pattern: "*", action: "allow" },
{ permission: "bash", pattern: "/bin/rm", action: "deny" },
])
expect(result).toBe("deny")
})
test("evaluate - wildcard at end overrides earlier exact match", () => {
const result = PermissionNext.evaluate("bash", "/bin/rm", {
bash: [
{ pattern: "/bin/rm", action: "deny" },
{ pattern: "*", action: "allow" },
],
})
const result = PermissionNext.evaluate("bash", "/bin/rm", [
{ permission: "bash", pattern: "/bin/rm", action: "deny" },
{ permission: "bash", pattern: "*", action: "allow" },
])
expect(result).toBe("allow")
})
// wildcard permission tests
test("evaluate - wildcard permission matches any permission", () => {
const result = PermissionNext.evaluate("bash", "rm", {
"*": [{ pattern: "*", action: "deny" }],
})
const result = PermissionNext.evaluate("bash", "rm", [{ permission: "*", pattern: "*", action: "deny" }])
expect(result).toBe("deny")
})
test("evaluate - wildcard permission with specific pattern", () => {
const result = PermissionNext.evaluate("bash", "rm", {
"*": [{ pattern: "rm", action: "deny" }],
})
const result = PermissionNext.evaluate("bash", "rm", [{ permission: "*", pattern: "rm", action: "deny" }])
expect(result).toBe("deny")
})
test("evaluate - glob permission pattern", () => {
const result = PermissionNext.evaluate("mcp_server_tool", "anything", {
"mcp_*": [{ pattern: "*", action: "allow" }],
})
const result = PermissionNext.evaluate("mcp_server_tool", "anything", [
{ permission: "mcp_*", pattern: "*", action: "allow" },
])
expect(result).toBe("allow")
})
test("evaluate - specific permission and wildcard permission combined", () => {
const result = PermissionNext.evaluate("bash", "rm", {
"*": [{ pattern: "*", action: "deny" }],
bash: [{ pattern: "*", action: "allow" }],
})
const result = PermissionNext.evaluate("bash", "rm", [
{ permission: "*", pattern: "*", action: "deny" },
{ permission: "bash", pattern: "*", action: "allow" },
])
expect(result).toBe("allow")
})
test("evaluate - wildcard permission does not match when specific exists", () => {
const result = PermissionNext.evaluate("edit", "src/foo.ts", {
"*": [{ pattern: "*", action: "deny" }],
edit: [{ pattern: "src/*", action: "allow" }],
})
const result = PermissionNext.evaluate("edit", "src/foo.ts", [
{ permission: "*", pattern: "*", action: "deny" },
{ permission: "edit", pattern: "src/*", action: "allow" },
])
expect(result).toBe("allow")
})
test("evaluate - multiple matching permission patterns combine rules", () => {
const result = PermissionNext.evaluate("mcp_dangerous", "anything", {
"*": [{ pattern: "*", action: "ask" }],
"mcp_*": [{ pattern: "*", action: "allow" }],
mcp_dangerous: [{ pattern: "*", action: "deny" }],
})
const result = PermissionNext.evaluate("mcp_dangerous", "anything", [
{ permission: "*", pattern: "*", action: "ask" },
{ permission: "mcp_*", pattern: "*", action: "allow" },
{ permission: "mcp_dangerous", pattern: "*", action: "deny" },
])
expect(result).toBe("deny")
})
test("evaluate - wildcard permission fallback for unknown tool", () => {
const result = PermissionNext.evaluate("unknown_tool", "anything", {
"*": [{ pattern: "*", action: "ask" }],
bash: [{ pattern: "*", action: "allow" }],
})
const result = PermissionNext.evaluate("unknown_tool", "anything", [
{ permission: "*", pattern: "*", action: "ask" },
{ permission: "bash", pattern: "*", action: "allow" },
])
expect(result).toBe("ask")
})
test("evaluate - permission patterns sorted by length regardless of object order", () => {
// specific permission listed before wildcard, but specific should still win
const result = PermissionNext.evaluate("bash", "rm", {
bash: [{ pattern: "*", action: "allow" }],
"*": [{ pattern: "*", action: "deny" }],
})
expect(result).toBe("allow")
const result = PermissionNext.evaluate("bash", "rm", [
{ permission: "bash", pattern: "*", action: "allow" },
{ permission: "*", pattern: "*", action: "deny" },
])
// With flat list, last matching rule wins - so "*" matches bash and wins
expect(result).toBe("deny")
})
// disabledTools tests
// disabled tests
test("disabledTools - returns empty set when all tools allowed", () => {
const result = PermissionNext.disabledTools(["bash", "edit", "read"], {
"*": [{ pattern: "*", action: "allow" }],
})
test("disabled - returns empty set when all tools allowed", () => {
const result = PermissionNext.disabled(["bash", "edit", "read"], [{ permission: "*", pattern: "*", action: "allow" }])
expect(result.size).toBe(0)
})
test("disabledTools - disables tool when denied", () => {
const result = PermissionNext.disabledTools(["bash", "edit", "read"], {
bash: [{ pattern: "*", action: "deny" }],
"*": [{ pattern: "*", action: "allow" }],
})
test("disabled - disables tool when denied", () => {
const result = PermissionNext.disabled(
["bash", "edit", "read"],
[
{ permission: "*", pattern: "*", action: "allow" },
{ permission: "bash", pattern: "*", action: "deny" },
],
)
expect(result.has("bash")).toBe(true)
expect(result.has("edit")).toBe(false)
expect(result.has("read")).toBe(false)
})
test("disabledTools - disables edit/write/patch/multiedit when edit denied", () => {
const result = PermissionNext.disabledTools(["edit", "write", "patch", "multiedit", "bash"], {
edit: [{ pattern: "*", action: "deny" }],
"*": [{ pattern: "*", action: "allow" }],
})
test("disabled - disables edit/write/patch/multiedit when edit denied", () => {
const result = PermissionNext.disabled(
["edit", "write", "patch", "multiedit", "bash"],
[
{ permission: "*", pattern: "*", action: "allow" },
{ permission: "edit", pattern: "*", action: "deny" },
],
)
expect(result.has("edit")).toBe(true)
expect(result.has("write")).toBe(true)
expect(result.has("patch")).toBe(true)
@ -335,70 +332,75 @@ test("disabledTools - disables edit/write/patch/multiedit when edit denied", ()
expect(result.has("bash")).toBe(false)
})
test("disabledTools - does not disable when partially denied", () => {
const result = PermissionNext.disabledTools(["bash"], {
bash: [
{ pattern: "*", action: "allow" },
{ pattern: "rm *", action: "deny" },
test("disabled - does not disable when partially denied", () => {
const result = PermissionNext.disabled(
["bash"],
[
{ permission: "bash", pattern: "*", action: "allow" },
{ permission: "bash", pattern: "rm *", action: "deny" },
],
})
)
expect(result.has("bash")).toBe(false)
})
test("disabledTools - does not disable when action is ask", () => {
const result = PermissionNext.disabledTools(["bash", "edit"], {
"*": [{ pattern: "*", action: "ask" }],
})
test("disabled - does not disable when action is ask", () => {
const result = PermissionNext.disabled(["bash", "edit"], [{ permission: "*", pattern: "*", action: "ask" }])
expect(result.size).toBe(0)
})
test("disabledTools - disables when wildcard deny even with specific allow", () => {
test("disabled - disables when wildcard deny even with specific allow", () => {
// Tool is disabled because evaluate("bash", "*", ...) returns "deny"
// The "echo *" allow rule doesn't match the "*" pattern we're checking
const result = PermissionNext.disabledTools(["bash"], {
bash: [
{ pattern: "*", action: "deny" },
{ pattern: "echo *", action: "allow" },
const result = PermissionNext.disabled(
["bash"],
[
{ permission: "bash", pattern: "*", action: "deny" },
{ permission: "bash", pattern: "echo *", action: "allow" },
],
})
)
expect(result.has("bash")).toBe(true)
})
test("disabledTools - does not disable when wildcard allow after deny", () => {
const result = PermissionNext.disabledTools(["bash"], {
bash: [
{ pattern: "rm *", action: "deny" },
{ pattern: "*", action: "allow" },
test("disabled - does not disable when wildcard allow after deny", () => {
const result = PermissionNext.disabled(
["bash"],
[
{ permission: "bash", pattern: "rm *", action: "deny" },
{ permission: "bash", pattern: "*", action: "allow" },
],
})
)
expect(result.has("bash")).toBe(false)
})
test("disabledTools - disables multiple tools", () => {
const result = PermissionNext.disabledTools(["bash", "edit", "webfetch"], {
bash: [{ pattern: "*", action: "deny" }],
edit: [{ pattern: "*", action: "deny" }],
webfetch: [{ pattern: "*", action: "deny" }],
})
test("disabled - disables multiple tools", () => {
const result = PermissionNext.disabled(
["bash", "edit", "webfetch"],
[
{ permission: "bash", pattern: "*", action: "deny" },
{ permission: "edit", pattern: "*", action: "deny" },
{ permission: "webfetch", pattern: "*", action: "deny" },
],
)
expect(result.has("bash")).toBe(true)
expect(result.has("edit")).toBe(true)
expect(result.has("webfetch")).toBe(true)
})
test("disabledTools - wildcard permission denies all tools", () => {
const result = PermissionNext.disabledTools(["bash", "edit", "read"], {
"*": [{ pattern: "*", action: "deny" }],
})
test("disabled - wildcard permission denies all tools", () => {
const result = PermissionNext.disabled(["bash", "edit", "read"], [{ permission: "*", pattern: "*", action: "deny" }])
expect(result.has("bash")).toBe(true)
expect(result.has("edit")).toBe(true)
expect(result.has("read")).toBe(true)
})
test("disabledTools - specific allow overrides wildcard deny", () => {
const result = PermissionNext.disabledTools(["bash", "edit", "read"], {
"*": [{ pattern: "*", action: "deny" }],
bash: [{ pattern: "*", action: "allow" }],
})
test("disabled - specific allow overrides wildcard deny", () => {
const result = PermissionNext.disabled(
["bash", "edit", "read"],
[
{ permission: "*", pattern: "*", action: "deny" },
{ permission: "bash", pattern: "*", action: "allow" },
],
)
expect(result.has("bash")).toBe(false)
expect(result.has("edit")).toBe(true)
expect(result.has("read")).toBe(true)