tool-permission-system — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited tool-permission-system (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Every time an agent calls a tool, a permission pipeline runs before execution. This pipeline is the single place that decides: auto-allow, ask the user, or deny. The pipeline is layered — different stakeholders (enterprise admin, user, project team, session) can each contribute rules, with higher layers overriding lower ones.
Tool call request
↓
[硬否决] Deny rules → immediate deny
↓
[强制确认] Ask rules → force prompt (even in bypass mode)
↓
[工具自身] Tool's checkPermissions() → tool-specific logic
↓
[安全绕过免疫] Safety checks (.git/, .claude/, shell configs) → prompt, immune to bypass
↓
[模式快速通过] Bypass / acceptEdits mode → immediate allow
↓
[白名单] Allow rules → immediate allow
↓
[默认] passthrough → prompt user (ask)外层包装(作用于整条流水线之后):
dontAsk 模式:把所有 ask 转为 deny(用于无交互的后台 agent)auto 模式:把所有 ask 转给 AI 分类器判断,而不是打断用户headless 模式:先跑 PermissionRequest hooks,hooks 没回应就自动 denytype PermissionBehavior = 'allow' | 'deny' | 'ask'
type PermissionDecision =
| { behavior: 'allow'; updatedInput?: unknown; decisionReason?: DecisionReason }
| { behavior: 'ask'; message: string; suggestions?: PermissionUpdate[] }
| { behavior: 'deny'; message: string; decisionReason: DecisionReason }规则来源按优先级从高到低排列:
policySettings ← 企业管理员,用户不可覆盖
userSettings ← 用户全局 (~/.agent/settings.json)
projectSettings ← 项目级 (.agent/settings.json,可提交 git)
localSettings ← 本地私有 (.agent/settings.local.json)
cliArg ← 启动参数
command ← 运行时命令
session ← 当次会话临时每条规则的格式:ToolName 或 ToolName(content)。
async function hasPermission(tool, input, context): Promise<PermissionDecision> {
// Step 1: deny rules (优先级最高,含企业强制)
const denyRule = findMatchingRule(context.denyRules, tool, input)
if (denyRule) return { behavior: 'deny', message: '...', decisionReason: { type: 'rule', rule: denyRule } }
// Step 2: ask rules (强制弹框,绕过模式也无法跳过)
const askRule = findMatchingRule(context.askRules, tool, input)
if (askRule) return { behavior: 'ask', message: '...' }
// Step 3: 工具自身的 checkPermissions()
const toolResult = await tool.checkPermissions(input, context)
if (toolResult.behavior === 'deny') return toolResult
if (toolResult.behavior === 'ask' && toolResult.decisionReason?.type === 'rule') return toolResult // ask rule 免疫 bypass
if (toolResult.behavior === 'ask' && toolResult.decisionReason?.type === 'safetyCheck') return toolResult // 安全检查免疫 bypass
// Step 4: bypass 模式快速通过
if (context.mode === 'bypassPermissions') return { behavior: 'allow', updatedInput: input }
// Step 5: allow rules 白名单
const allowRule = findMatchingRule(context.allowRules, tool, input)
if (allowRule) return { behavior: 'allow', updatedInput: input }
// Step 6: 默认转 ask
return { behavior: 'ask', message: `Agent requested to use ${tool.name}` }
}每个工具可以有自己的内部权限逻辑:
class MyTool implements Tool {
async checkPermissions(input, context): Promise<PermissionResult> {
// 检查工具特定规则(如 Bash 检查具体命令前缀)
const allowRules = getRuleContentsForTool(context, this, 'allow')
if (allowRules.has(getCommandPrefix(input.command))) {
return { behavior: 'allow' }
}
// 检查危险路径
if (isDangerousPath(input.path)) {
return {
behavior: 'ask',
message: '...',
decisionReason: { type: 'safetyCheck', reason: '...', classifierApprovable: false }
}
}
return { behavior: 'passthrough', message: '...' }
}
}Hook 让用户/企业在工具生命周期各节点插入自定义逻辑:
// 配置格式(settings.json)
{
"hooks": {
"PreToolUse": [{
"matcher": "MyTool", // 可选,工具名过滤
"hooks": [{
"type": "command", // command | prompt | agent | http
"command": "check-safety.sh $TOOL_INPUT"
}]
}],
"PostToolUse": [{
"matcher": "FileEdit",
"hooks": [{ "type": "command", "command": "prettier --write $FILE_PATH" }]
}]
}
}Hook 执行结果影响权限决策:
{"action": "allow"} → 覆盖决策当使用 AI 分类器自动判断权限时,需要 circuit breaker 防止分类器过于严格:
// 连续拒绝 3 次或累计拒绝 20 次 → 回退到人工确认
const DENIAL_LIMITS = { maxConsecutive: 3, maxTotal: 20 }
function shouldFallback(state: DenialTrackingState): boolean {
return (
state.consecutiveDenials >= DENIAL_LIMITS.maxConsecutive ||
state.totalDenials >= DENIAL_LIMITS.maxTotal
)
}设计时必须明确的三个问题:
decisionReason.type === 'safetyCheck' 标记shouldAvoidPermissionPrompts = true + 跑 hooks + 自动 deny// 最简实现:三层规则 + 工具自检
type Rule = { toolName: string; content?: string; behavior: 'allow' | 'deny' | 'ask' }
type PermissionContext = {
mode: 'default' | 'bypassPermissions' | 'acceptEdits'
allowRules: Rule[]
denyRules: Rule[]
askRules: Rule[]
}
async function checkPermission(toolName: string, input: unknown, ctx: PermissionContext) {
if (ctx.denyRules.some(r => matches(r, toolName, input))) return 'deny'
if (ctx.askRules.some(r => matches(r, toolName, input))) return 'ask'
if (ctx.mode === 'bypassPermissions') return 'allow'
if (ctx.allowRules.some(r => matches(r, toolName, input))) return 'allow'
return 'ask' // default: prompt
}This skill owns:
checkPermissions() 接口设计This skill does not own:
references/permission-types.tsreferences/permission-pipeline.mdreferences/denial-tracking.tsreferences/hook-system.mdreferences/settings-examples.json~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.