Hotdry.

Article

检测LLM代理'跳过代码阅读'行为的运行时监控与强制审查护栏

构建检测LLM代理危险跳过代码阅读行为的运行时监控机制,通过决策执行分离、结构化验证与熔断策略防止基于不完整上下文的安全漏洞引入。

2026-05-23ai-systems

当 LLM 代理在编码任务中被赋予文件系统访问权限时,一个隐蔽但致命的风险正在形成:代理可能在未完整阅读相关代码的情况下,基于部分上下文或幻觉执行破坏性操作。这种模式被称为 "危险跳过代码阅读"(dangerously skip reading code),其本质是将决策与执行混为一谈,让概率模型直接控制关键操作。

风险本质:幻觉驱动的自主破坏

OWASP 将 "过度依赖 LLM 输出" 列为 LLM 应用的第 9 大安全风险。当代理拥有 shell 访问权限、云凭证和自主执行权时,问题变得尤为严重 —— 代理可能幻觉出一个理由来运行命令、修改文件或执行代码,而这种破坏甚至不需要攻击者参与。研究表明,代理可以 "自信地发明一个执行命令的理由",然后在没有人工检查的情况下造成损害。

最具代表性的场景是代理在编码任务中的行为链:第一步,代理误读日志信息;第二步,得出存在安全威胁的结论;第三步,通过修改防火墙规则来 "修复" 问题;第四步,锁定合法用户。每一步对模型而言似乎都符合逻辑,但复合结果却是灾难性的。

三种失败模式

1. 幻觉动作

代理生成与用户意图或工具观察结果无关的命令。例如,用户要求 "清理项目目录中未使用的文件",代理却广泛解读该指令,删除包含生产配置的系统目录。关键风险在于:没有注入攻击,没有恶意输入,仅仅是模型推理错误,但它拥有执行该推理的权限。

2. 脆弱解析与误读

代理误读工具输出并基于错误数据采取行动。当 API 返回 HTTP 500 错误页面时,代理可能将 HTML 内容解析为有效数据,进而执行systemctl restart --all之类的危险操作。任何处理真实 API 响应的代理都会遇到格式错误、意外格式或错误响应,如何处理这些边缘案例决定了它是安全失败还是危险失败。

3. 失控循环与资源耗尽

代理在重试失败操作时陷入无限循环,可能在周末期间产生数千美元的 API 费用,或触发影响其他用户的速率限制。如果代理拥有云凭证,它可能在每次迭代中生成资源("让我尝试更大的实例"),使成本呈指数级增长。

防御架构:为不可靠性而设计

决策与执行分离

永远不要让模型直接控制执行器。LLM 输出意图("删除文件 X"),然后由确定性代码在执行前验证该意图是否匹配用户请求并符合策略。

# 脆弱:LLM直接执行
def agent_action(llm_output: str):
    if llm_output.startswith("EXECUTE:"):
        command = llm_output.split("EXECUTE:")[1]
        os.system(command)  # LLM说什么就执行什么

# 安全:执行前验证
def agent_action(llm_output: str, user_request: str):
    intent = parse_intent(llm_output)
    
    # 验证意图是否匹配用户请求
    if not intent_matches_request(intent, user_request):
        return "动作与请求不匹配,跳过。"
    
    # 检查策略
    if intent.action in DANGEROUS_ACTIONS:
        return f"动作 '{intent.action}' 需要审批。"
    
    # 通过受控接口执行
    return safe_execute(intent)

结构化输出验证

不要让模型自由生成命令。使用结构化输出模式约束模型可表达的内容,明确定义允许的动作及其参数验证规则。

ALLOWED_ACTIONS = {
    "read_file": {"params": ["path"], "validation": validate_path},
    "write_file": {"params": ["path", "content"], "validation": validate_path},
    "search": {"params": ["query"], "validation": validate_query},
}

def execute_tool_call(action_name: str, params: dict):
    if action_name not in ALLOWED_ACTIONS:
        raise ValueError(f"未知动作: {action_name}")
    
    schema = ALLOWED_ACTIONS[action_name]
    schema["validation"](params)  # 类型和值检查
    
    return tools[action_name](**params)

限制自主窗口

不要让代理无限期运行而不设置检查点:

  • 迭代限制:每个任务的最大工具调用次数,超过 N 次后停止并请求人工输入
  • 时间限制:每个任务的最大执行时间,如果代理在 5 分钟内未完成,很可能出了问题
  • 成本限制:每个任务的最大 token 花费,在失控循环变得昂贵之前熔断
class AgentRunner:
    MAX_ITERATIONS = 25
    MAX_DURATION_SECONDS = 300
    MAX_COST_USD = 5.0

    def run(self, task):
        start = time.time()
        iterations = 0
        total_cost = 0.0

        while not task.complete:
            iterations += 1
            if iterations > self.MAX_ITERATIONS:
                return "任务超出迭代限制,停止。"
            
            if time.time() - start > self.MAX_DURATION_SECONDS:
                return "任务超出时间限制,停止。"
            
            result = self.execute_step(task)
            total_cost += result.cost
            
            if total_cost > self.MAX_COST_USD:
                return "任务超出成本限制,停止。"

运行时监控策略

行为基线

追踪代理通常执行的操作。如果它通常读取文件并生成摘要,但突然开始执行 shell 命令,即使触发不明显也要标记。

动作 - 意图关联

记录用户的原始请求和代理执行的每个动作。定期审计动作是否真正对应请求,差距表明可能存在幻觉。

错误率追踪

监控代理动作失败的频率。失败率飙升可能表明代理正在追求与现实不符的幻觉目标。

双模型验证

对于高风险动作,使用第二个模型(或更简单的基于规则的系统)验证主模型的输出。主模型提出动作,验证模型检查:该动作是否符合用户请求?是否具有潜在破坏性?理由是否成立?

实施检查清单

  • LLM 输出在任何工具执行前经过验证
  • 破坏性动作需要显式用户确认
  • 设置迭代、时间和成本的熔断机制
  • 结构化输出模式约束工具调用
  • 动作 - 意图关联被记录并可审计
  • 建立行为基线并持续监控
  • 多步骤关键任务需要人工检查点

结论

LLM 是一个在采取现实世界动作的系统中不可靠的组件。再多的提示工程、微调或模型改进都无法消除这一点。唯一安全的方法是架构性的:分离决策与执行、在每一步验证、限制自主性、持续监控。模型会犯错,构建那些错误不会造成不可逆伤害的系统。


参考来源

  • Rafter Security: "When Your AI Agent Is the Vulnerability: Hallucinations and Unsafe Autonomy"
  • OWASP LLM Application Security Risks

ai-systems

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com