Skip to content

fix: reduce false approval popups without policy changes - #3

Open
joejojoking-cloud wants to merge 1 commit into
NanmiCoder:mainfrom
joejojoking-cloud:fix/reduce-false-approval-popups
Open

fix: reduce false approval popups without policy changes#3
joejojoking-cloud wants to merge 1 commit into
NanmiCoder:mainfrom
joejojoking-cloud:fix/reduce-false-approval-popups

Conversation

@joejojoking-cloud

Copy link
Copy Markdown

背景

Auto 权限模式在真实使用中审批弹窗偏频繁。三类误报(均不涉及判定策略本身的改动,只修误判、减冗余):

  1. PowerShell 惯用写法被误判:$x = 5$x = 'abc'$script = @'...'@ 等赋值语句的首词被 tokenizer 标记为 dynamic,走 manualReview 直接弹审批——实际是赋值目标而非命令,最常见的误报来源。
  2. 循环内重复调用每次都调一次分类器 LLM,且分类器失败(超时/网络抖动)时 fail-closed 弹审批,进一步放大弹窗频率。
  3. 分类器瞬时失败没有重试,一次抖动就变成一次审批弹窗。

改动

1. PowerShell 赋值识别(shell.ts

  • $x = 5$x = 'abc'$script = @'...'@(多行 here-string)、$x = $null/$true/$false$x = $y$x = $env:PATH直接 allow(RHS 不执行任何命令)
  • 非字面量 RHS($x = Get-ChildItem$x = git commit$x = Remove-Item $y)→ 递归走完整评估管线,删除检查、git 检查、分类器路由全部保留,重定向目标随递归保留
  • 安全红线:
    • "纯字面量"复用 tokenizer 的 dynamic/glob 标记——插值双引号 "$y"@"..."@$(...)(opaque)绝不算字面量
    • 无数据流分析$x = 5; Remove-Item $x 仍弹审批($x 可能被其他路径修改)
    • 仅 pwsh;bash 的 x=5 语法不受影响

2. 分类器判定缓存(新增 classifier-cache.ts + index.ts

  • LRU 缓存(默认 128 条)+ TTL(默认 60s),只缓存 allow/deny——ask 永不缓存(缓存 ask 会绕过审批弹窗)
  • 缓存 key = sha256(工具名 + 消毒参数 + workspaceRoot + policyReason + trustedUserMessages + route):分类器判定以用户授权消息为条件,key 必须包含它,否则会话 A 的授权会被会话 B 的同参数调用复用(跨会话授权泄漏)
  • 循环内的重复调用只调一次 LLM
  • 新增配置:classifierCacheTtlMsclassifierCacheMaxEntries(有默认值,现有 {} 配置无需改动)

3. 分类器失败重试一次(index.ts

  • 首次失败 → 重试一次 → 仍失败才 fail-closed 弹审批(成本一次请求,收益少一次弹窗)
  • exec.signal.aborted 时跳过重试直接 fail-closed(工具已取消,重试无意义)

测试

pnpm verify 全绿:typecheck + build + 77 tests(新增 11 个:赋值识别 5 组、缓存单元 4、缓存/重试集成 5,含"跨会话不同 trustedUserMessages 不命中缓存"、"中止不重试"等安全用例)。

Three safety-no-loss changes to the deterministic and classifier layers:

- shell.ts: recognize PowerShell variable assignments ( = 5,  = 'abc',
   = @'...'@) as assignment targets, not commands. A pure-literal
  RHS (reusing the tokenizer's own dynamic/glob flags; quoted interpolated
  words stay out) is allowed; any other RHS is assessed recursively through
  the full command machinery with redirection targets preserved. No dataflow
  analysis: ' = 5; Remove-Item ' still asks. Kills the 'command name is
  produced by a dynamic expansion' popups on routine assignment lines.

- index.ts: LRU cache of final classifier verdicts (TTL 60s, 128 entries).
  Key is a sha256 of the FULL sanitized payload including trustedUserMessages
  and the model route, so a verdict conditioned on one session's user
  authorization can never be reused by another session's identical call.
  ask verdicts are never cached - caching them would bypass the approval
  popup. Repeated calls in a loop hit the classifier once.

- index.ts: retry the classifier once before fail-closing to approval, so a
  transient timeout or network jitter no longer pops an approval request.
  An aborted tool call skips the retry and fails closed immediately.

Config: classifierCacheTtlMs (60s), classifierCacheMaxEntries (128).
Tests: 77 pass (11 new).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant