fix(core): prefer specific permission rules

This commit is contained in:
Shoubhit Dash 2026-05-21 23:32:14 +05:30
commit 5530bbe0ac
5 changed files with 100 additions and 61 deletions

View file

@ -19,15 +19,7 @@ export type Ruleset = typeof Ruleset.Type
const EDIT_TOOLS = ["edit", "write", "apply_patch"]
export function evaluate(permission: string, pattern: string, ...rulesets: Ruleset[]): Rule {
return (
rulesets
.flat()
.findLast((rule) => Wildcard.match(permission, rule.permission) && Wildcard.match(pattern, rule.pattern)) ?? {
action: "ask",
permission,
pattern: "*",
}
)
return select(permission, pattern, rulesets.flat())?.rule ?? { action: "ask", permission, pattern: "*" }
}
export function merge(...rulesets: Ruleset[]): Ruleset {
@ -38,8 +30,52 @@ export function disabled(tools: string[], ruleset: Ruleset): Set<string> {
return new Set(
tools.filter((tool) => {
const permission = EDIT_TOOLS.includes(tool) ? "edit" : tool
const rule = ruleset.findLast((rule) => Wildcard.match(permission, rule.permission))
return rule?.pattern === "*" && rule.action === "deny"
if (
ruleset.some(
(rule) => Wildcard.match(permission, rule.permission) && rule.pattern !== "*" && rule.action !== "deny",
)
) {
return false
}
return evaluate(permission, "*", ruleset).action === "deny"
}),
)
}
function select(permission: string, pattern: string, ruleset: Ruleset) {
return ruleset.reduce<Selected | undefined>((best, rule, index) => {
if (!Wildcard.match(permission, rule.permission) || !Wildcard.match(pattern, rule.pattern)) return best
const next = { rule, index, permission: specificity(rule.permission), pattern: specificity(rule.pattern) }
if (!best) return next
return compare(next, best) >= 0 ? next : best
}, undefined)
}
type Selected = {
rule: Rule
index: number
permission: Specificity
pattern: Specificity
}
type Specificity = {
wildcard: number
literal: number
}
function specificity(pattern: string): Specificity {
return {
wildcard: [...pattern.matchAll(/[?*]/g)].length,
literal: pattern.replace(/[?*]/g, "").length,
}
}
function compare(a: Selected, b: Selected) {
return (
b.permission.wildcard - a.permission.wildcard ||
a.permission.literal - b.permission.literal ||
b.pattern.wildcard - a.pattern.wildcard ||
a.pattern.literal - b.pattern.literal ||
a.index - b.index
)
}