fix(guard): 拦截文案标明为平台管控而非目标防御

拦截 deny/ask 回给 agent 的 tool_result 原文案(禁止执行/用户拒绝)
读起来与目标侧 WAF/403 无异,渗透 agent 会当成目标防御去改写命令、
换 payload 尝试绕过。统一经 systemBlockMessage 加平台权威框架,说明
此为平台管控、操作被禁止,引导换方向而非规避。
This commit is contained in:
Autumn-27 committed 2026-09-10 11:30:28 -04:00
1 parent 01760aa345
commit 9ad88d5ce8
1 file changed
+20 -3
+20 -3
View File
@@ -103,7 +103,7 @@ func (g *Guard) applyIntercept(ctx context.Context, ev hook.Event, cmd string) h
case "deny":
// 观测:deny 命中不阻塞审批,直接记一条 denied(历史/任务拦截页可见)。
g.interceptor.Log(ctx, intercept.ConvIDFromContext(ctx), dec, ev.ToolName, ev.Input, "denied")
return g.block(ev.ToolName, dec.Message, "")
return g.block(ev.ToolName, systemBlockMessage(dec.Message), "")
case "allow":
// Record explicit rule and model approvals so review details remain auditable.
g.interceptor.Log(ctx, intercept.ConvIDFromContext(ctx), dec, ev.ToolName, ev.Input, "allowed")
@@ -113,17 +113,34 @@ func (g *Guard) applyIntercept(ctx context.Context, ev hook.Event, cmd string) h
// immediately without creating a pending record — avoids orphaned DB entries
// and makes execOne complete fast, reducing the race against drainSynthetic.
if ctx.Err() != nil {
return g.block(ev.ToolName, "工作已取消,拦截规则阻止执行", "")
return g.block(ev.ToolName, systemBlockMessage("工作已取消,平台安全管控阻止执行"), "")
}
convID := intercept.ConvIDFromContext(ctx)
if !g.interceptor.HandleAsk(ctx, convID, dec, ev.ToolName, ev.Input) {
return g.block(ev.ToolName, "用户拒绝或审批超时", "")
return g.block(ev.ToolName, systemBlockMessage("人工审批未通过(用户拒绝或审批超时)"), "")
}
return hook.Result{}
}
return hook.Result{}
}
// systemBlockMessage frames an intercept block as an ARTEX platform-governance
// decision so the agent does not mistake it for a target-side defense.
//
// The bare reasons ("禁止执行此工具" / "用户拒绝") read exactly like a WAF/403 on
// the target, so a pentest agent's instinct is to bypass them — rewrite the
// command, swap the payload, re-encode, retry. That is both futile (the platform
// blocks the class of action, not one string) and wrong (it's a policy decision,
// not an obstacle to defeat). This prefix states plainly that the block comes
// from the platform, is not the target's protection, and that the operation is
// forbidden — so the agent pivots to another approach instead of evading it.
// Audit/history rows keep the raw reason (see Interceptor.Log); only the
// model-facing tool_result carries this framing.
func systemBlockMessage(reason string) string {
return "【ARTEX 平台管控·非目标防御】此调用被平台拦截。" +
"原因:" + reason + "。此操作被禁止。"
}
// judgeSubject builds the text the LLM fallback judge evaluates: the shell
// command for shell tools, or the raw tool input JSON for others (Write/Edit/…)
// so the model sees what is being written/requested.