权限闸门与绕过测试Permission gates and bypass testing
先说问题The problem
你写了「只读命令自动放行」的检测器,觉得很稳。社区报的绕过样本:/bin/rm、\rm、env rm、命令替换、LD_PRELOAD= 前缀。本模块把它们逐条喂给框架自己的 Bash 检测器实测:8 条里 7 条落在 ASK,唯一自动放行的是只读的 ls -a;一条「总是允许 Bash」的规则能静默其中 2 条,但另外 5 条带 bypass_immune 的 ASK 静默不掉——只有 BYPASS 模式能,而那等于没有闸门。
You wrote a read-only detector that auto-allows safe commands and felt solid. The field reports list /bin/rm, backslash-rm, env rm, command substitution and an LD_PRELOAD= prefix. Fed one by one into the framework's own Bash detector: 7 of 8 land on ASK, the only auto-allow is the read-only ls -a, one 'always allow Bash' rule silences 2 of them, and the 5 bypass_immune asks survive the rule. Only BYPASS mode silences those — which is no gate at all.
先动手Do it first
先在页面上接线——页面只懂规则。然后跑下面那条判题,那才是真会失败的东西。Wire it on the page — the bench only knows the rules. Then run the check below; that is the part that can actually fail.
resource_access_policy=
ResourceAccessPolicyBase
选填optional
决定调用方能用到哪些被共享的资源,多租户影响面由此收口。Decides which shared resources a caller may use. The multi-tenant blast radius lives here.
create_app(…)
为什么这样设计Why it is built this way
01五种模式,不是一句提示词Five modes, not one prompt
PermissionMode 有 DEFAULT / ACCEPT_EDITS / EXPLORE / BYPASS / DONT_ASK。DONT_ASK 会把 ASK 转成 DENY——不是「别问了继续做」。
PermissionMode offers DEFAULT / ACCEPT_EDITS / EXPLORE / BYPASS / DONT_ASK. DONT_ASK converts ASK into DENY — it does not mean stop asking and carry on.
02ASK 是行为,不是文本ASK is a behaviour, not a sentence
PermissionBehavior 的 ALLOW / DENY / ASK / PASSTHROUGH 是引擎返回的决策对象,带 suggested rules 给人类点。模型说「已转人工」不算转。
PermissionBehavior ALLOW / DENY / ASK / PASSTHROUGH is a decision object the engine returns, carrying suggested rules a human can click. The model saying 'escalated' is not an escalation.
03安全类 ASK 免疫于放行规则Safety asks are immune to allow rules
引擎区分普通 ASK 与 safety ask(decision.bypass_immune):后者不会被一条 allow 规则静默。实测 corpus 里 8 条有 5 条属于这类——但 LD_PRELOAD= 前缀那条不算,它被「总是允许 Bash」静默了。免疫是按危险等级判的,不是按「有没有前缀花样」判的。
The engine separates ordinary asks from safety asks (decision.bypass_immune); a safety ask cannot be silenced by an allow rule. In the measured corpus 5 of 8 are that class — but the LD_PRELOAD= case is not, and the 'always allow Bash' rule silences it. Immunity is graded by how destructive the call is, not by whether the command pulled a prefix trick.
04绕过用例进回归Bypass cases belong in the regression
把 /bin/rm、\rm、env rm、命令替换写进评测集,每次改检测器都跑,并且断言「几条落在 ASK、几条被规则静默、几条免疫」三个数一起对。闸门的安全边界是测出来的,不是设计出来的。
Put /bin/rm, backslash-rm, env rm and command substitution into the eval set and run it on every detector change, asserting all three counts together: how many ask, how many an allow rule silences, how many stay immune. A gate's boundary is measured, not designed.
用到的 APIAPIs in play
代码The code
engine = PermissionEngine(PermissionContext(mode=PermissionMode.DEFAULT))
engine.add_rule(PermissionRule(
tool_name="Bash", # the rule is scoped to one tool, not the whole agent
rule_content=None,
behavior=PermissionBehavior.ALLOW,
source="user-always-allow",
))
decision = await engine.check_permission(bash, {"command": "rm -rf /etc"})
# decision.behavior ASK -> the call parks, it does not proceed
# decision.suggested_rules what the human is being offered to click
# decision.bypass_immune True here: no allow rule silences it, only BYPASS mode does
判题The check
python lab.py run c4
它断言了什么What it asserts
PermissionMode is a switch, not a prompt: default/accept_edits/explore/bypass/dont_askrm under DEFAULT -> ask with 1 suggested rule(s), behavior=allowcorpus with no rules: 7/8 ask, the one allow is the read-only ls -aone 'always allow Bash' silences: `rm x.txt`, `LD_PRELOAD=/tmp/e.so rm x`and cannot silence 5 bypass_immune asks: `/bin/rm -rf x`, `\rm -rf x`, `env rm -rf x`, `ls $(rm -rf /)`, `rm -rf /etc`BYPASS answers the immune ask too -> allow: it is no gate, not dev modeDONT_ASK -> deny (not 'stop asking and carry on'); EXPLORE denies rm and keeps lsa tool-name allow rule frees lookup_order and still asks on refund_orderservice layer, different axis: the default policy and the refund gate both deny cross-owner edits, the BYPASS-shaped block answers yes to the same question
会踩的坑Where it hurts
用 BYPASS 模式做演示,然后忘了切回来。BYPASS 不是「开发模式」,是「没有闸门」。
Demoing in BYPASS mode and forgetting to switch back. BYPASS is not development mode; it is no gate at all.
费曼闸门Say it back
为什么 safety ask 要设计成「免疫于 allow 规则」?如果免疫,会不会有别的什么被卡死?
Why make safety asks immune to allow rules? If you grant immunity, what else gets wedged shut?
页面只存通行码的哈希。判题没真跑过,这里就解不开。The page stores a hash, not the code. Nothing unlocks until the command really ran.