diff --git a/guard/guard.go b/guard/guard.go index b2d67c4..fd437d9 100644 --- a/guard/guard.go +++ b/guard/guard.go @@ -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.