AutoGen源码审计:多智能体编排框架的安全边界与风险画像

发布时间:2026/10/27 3:11:13
AutoGen源码审计:多智能体编排框架的安全边界与风险画像

1. 项目背景与审计目标

1.1 AutoGen到底是什么,它解决的编排问题

微软开源的 AutoGen 是当下关注度很高的多智能体编排框架之一。这篇评测做的是源码静态审计,也就是不依赖跑通某个 demo,而是直接把源代码当证据摊开来看。先说结论:AutoGen 的核心编排思路很直接,它把智能体抽象成一群能够互相收发消息的对象,再用 GroupChat 和 GroupChatManager 把多轮对话组织起来,对话既是智能体之间的通信协议,也是任务推进的驱动力。

这种设计的优势在于灵活。传统任务流喜欢用固定的图或链去编排,Agent A 完成后必须交给 Agent B,流程是写死的。AutoGen 相反,它把“谁在什么条件下和谁说话”交给运行时的对话历史和大模型判断,相当于把编排决策从代码里抽离出来。好处是场景越复杂,这种动态路由越省事;坏处是决策结果不可控,代码里能看到的地方就那么几个关键函数,一旦某个环节出问题,排障链路会非常长。

做过智能体应用的人应该都有体会:框架本身用起来很简单,但真正进入生产环境,隐患往往藏在默认行为里。比如消息历史会不会无限膨胀,speaker 选择失败时如何兜底,代码执行器到底在什么权限下执行代码,这些在 README 里不会写清楚。所以我这次把审计重点放在了几条核心路径上:GroupChat 对话循环、代码执行器、工具调用注册、依赖与配置安全,每一个结论都尽量给出文件路径、行号和调用链,方便任何人复现验证。

1.2 L3评测的定位与审计边界

我平时做开源评测会分三个档位:L1 只看文档和示例,L2 把 demo 跑通,L3 则是源码级静态审计。这篇属于 L3,意味着所有判断都必须落到具体代码上,而不是“某个版本很稳定”这种模糊描述。L3 适用的场景是技术选型、安全评审、给团队做框架导入之前的风险摸底,仅仅靠 README 判断一个框架是否可信,在智能体领域几乎是行不通的。

本次审计对象是 microsoft/autogen 仓库的 v0.2.28 tag,Python 3.10 环境。固定版本是为了保证证据可复现,因为 AutoGen 的模块组织在不同版本之间发生过明显调整,比如部分执行器和 Agent 的路径在后续版本里已经变化。如果你现在用的是新版本,具体行号可能对不上,但审计思路和检查点完全通用。

需要说明,L3 静态审计不等于安全认证。我关注的是代码库本身暴露的风险边界,不包含运行时模型输出的恶意内容检测,也不包含具体业务场景中的数据合规。换句话说,我判断的是“这个框架默认帮我们做了什么,还留了什么口子”,至于口子会不会被利用,取决于你的使用方式。

提醒:任何“证据级结论”都必须绑定 commit 号或版本号,否则时间一长就无法追查。这也是为什么我第一步就是固定源码版本。

2. 审计环境搭建与可复现证据设计

2.1 固定版本与前置环境准备

可复现的第一步是锁版本。我直接克隆官方仓库,切到 v0.2.28 tag,然后用 git rev-parse 把 commit 指纹记录下来。这一步别省,后续写审计报告时,任何结论后面都要挂上这个 commit 指纹,别人照着做才能拿到完全一致的源码。

git clone https://github.com/microsoft/autogen.git cd autogen git checkout v0.2.28 git rev-parse HEAD > audit_commit.txt python -m venv .venv source .venv/bin/activate pip install -e .

用虚拟环境主要是避免污染系统 Python,同时保证运行时导入的包确实来自这份源码。pip install -e .是可编辑安装,比直接pip install autogen好,因为它让 Python 进程指向仓库内源码,静态分析和实际运行看到的是同一份文件。审计期间我还导出了完整的依赖树,方便后续 pip-audit 做供应链比对。

有些读者可能会问,既然做静态审计,为什么还要安装依赖?因为像 Bandit、Semgrep 这类工具在扫描时需要解析 import 关系,如果依赖缺失,很多符号解析会被跳过,审计结果会失真。所以环境搭建不是可有可无,它是证据链的一部分。

2.2 工具链选择与静态审计规则

我试过单一工具扫全库,效果不好,误报率很高。这次采用组合工具的方式,各自负责不同维度:

工具侧重点本次用途
BanditPython 危险 API 扫描找出 eval、exec、subprocess、临时文件等高风险点
Semgrep自定义模式匹配针对 AutoGen 特有函数和调用链写规则
pip-audit依赖漏洞审计检查锁定版本是否存在已知安全公告
ruff代码质量与复杂度快速定位明显反模式、无用分支、未处理异常
grep / vim人工精读追踪具体函数调用关系和数据流

命令大致是这样:

bandit -r autogen -f html -o bandit.html semgrep --config p/ci autogen pip-audit -r requirements.txt ruff check autogen

Bandit 适合第一轮筛选,它会直接标记出subprocess.run、eval、pickle.loads这类危险调用。Semgrep 的价值在于可以写 AutoGen 专属规则,比如排查所有execute_code的调用点。这里给出一个示意规则,实际使用时可以把它写进 semgrep.yml:

rules: - id: autogen-execute-code-call languages: [python] message: found execute_code invocation severity: WARNING pattern: execute_code(...)

Semgrep 的好处是模式匹配基于语法树,不会因为变量名不同就漏掉。示例中我把所有 execute_code 调用点都拉出来之后,再逐个确认调用方是否处于沙箱环境,是否经过用户输入控制。CodeQL 我也试了,但完整搭建需要时间,并且对仓库索引要求高,所以这次只选核心目录跑,没有全量铺开。

2.3 证据级审计记录规范

工具扫描只是噪音来源,真正有价值的是人工确认。我定了一个记录模板,每条记录包含这几列:审计编号、文件路径、行号、函数调用链、问题描述、风险等级、规则来源、状态。比如一条典型记录长这样:

编号文件路径行号调用链风险等级
A-001autogen/code_utils.py约 210 行附近execute_code -> subprocess.run高
A-002autogen/agentchat/groupchat.py约 480 行附近GroupChatManager._run_chat -> select_speaker中
A-003autogen/oai/openai_utils.py约 120 行附近config_list -> get_config_list低

每一条我都会标注是“确认问题”还是“误报”,误报也要记录理由,否则下次审计又要重新判断一遍。这个习惯强烈建议保留,因为静态审计的产出不是“代码有没有 bug”,而是一份可追溯的决策材料。团队里其他人拿到这份记录,不需要重新读代码就能知道当时为什么做这个判断。

3. 核心源码静态审计过程

3.1 GroupChat对话编排路径审计

多智能体编排的核心在autogen/agentchat/groupchat.py。我先读GroupChatManager的 run 主循环,再读GroupChat的消息存储和 speaker 选择逻辑。整体设计很清晰:所有发言都追加到GroupChat.messages里,GroupChatManager在每轮循环里根据历史消息决定下一个说话者。选择方式由speaker_selection_method控制,默认值是auto,也就是把本轮历史消息和候选 agent 列表交给大模型,让模型返回一个 name。

这里的“为什么”值得展开。auto模式的好处是无需人工预先定义对话路径,模型能根据上下文自行判断下一步应该让“程序员 Agent”还是“审查 Agent”发言。但坏处同样明显:speaker 选择是外部模型调用,存在超时、返回格式异常、连续选择同一个 agent 导致死循环等风险。源码里虽然有max_round这类轮次保护,但如果你业务中单轮对话本身就包含大量工具调用,轮次上限并不等于 token 上限,消息历史仍然会持续膨胀。

再往下读,GroupChat本身用asyncio.Lock保护并发入口,避免多个任务同时驱动同一个 chat 造成消息错乱。这层保护是必要的,也做得比较稳。但select_speaker在auto模式下依赖的模型输出并没有结构化的硬校验,代码里更多是重试机制,一旦模型连续返回非法 speaker,整个 chat 就会在重试中空转。静态看下来,最稳妥的用法是手动将候选 speakers 数量控制在小范围,并在外层增加超时和兜底逻辑,不要把编排稳定性完全押在模型返回值上。

3.2 代码执行器与工具调用的安全边界

AutoGen 的代码执行能力是我重点盯的部分。框架支持通过code_execution_config把生成代码写到临时目录,再调用本机 Python 解释器执行。源码路径主要在autogen/code_utils.py和autogen/agentchat/conversable_agent.py。我沿着execute_code往下追,看到它首先会把代码内容写入临时文件,然后通过subprocess.run调用解释器。这个机制本身没什么问题,问题是它在默认配置下拥有当前操作系统用户的完整权限。

如果你让 AutoGen 的智能体去处理不可信的输入,比如一个陌生用户上传的任务描述,而你的代码执行器又没有限制在 Docker 或独立容器里,那这段生成代码就等同于本地任意代码执行。这个结论不是从文档猜出来的,是审到subprocess.run调用链后确认的。框架提供的DockerCommandLineCodeExecutor是相对安全的替代方案,能通过容器隔离减少本机暴露面,但引入 Docker 也意味着需要额外的资源管理和镜像权限控制。

工具调用方面,AutoGen 的register_function允许把 Python 函数注册给模型调用。这里风险在于:一旦模型被提示注入诱导,它可能在合法函数名之内传了恶意参数。源码里并不容易区分“用户的恶意输入”和“模型基于上下文的合理输出”,因为函数调用参数完全来自模型生成。审计结论是:给智能体注册工具时,尽量让函数内部做输入校验和鉴权,不要默认函数参数是可信的。这不算 AutoGen 的缺陷,而是所有 Tool Calling 框架的共性安全边界。

3.3 依赖供应链与配置安全审计

供应链审计用的是pip-audit,针对v0.2.28锁定版本的依赖做比对。当时审计时发现某个传递依赖的版本声明范围过宽,触发了中等级安全告警,后续在 requirements 里固定子依赖版本后告警消除。这类问题在快速迭代的开源项目里很常见,不一定是 AutoGen 独有的,但引入框架时应该把依赖锁定纳入 CI,而不是交付到生产后再发现。

配置安全我关注两点:一是 API Key 的读取,二是日志输出。AutoGen 的config_list支持从环境变量读取 key,我在源码里没有看到硬编码密钥,这点是干净的。但框架在调试场景下可以通过verbose=True打印完整对话内容,一旦业务 Key 或用户敏感信息出现在消息体里,日志就可能把数据带出去。静态审计时我专门查了一圈日志调用点,没有看到自动脱敏逻辑。所以实际项目中如果要打开详细日志,必须在上层自己实现脱敏。

依赖协议方面,仓库使用 MIT 协议,对商业使用比较友好。但从源码层面看,框架包含多个可选依赖,例如额外安装不同模型 SDK 会拉入不同依赖树。建议在集成前用pip-audit对最终生成环境重新扫描一次,因为你的实际依赖树大概率比仓库默认的 requirements 更宽。

3.4 异常恢复与资源生命周期审计

除了主流程,我还审计了异常恢复路径。AutoGen 对话循环里比较核心的是GroupChatManager的 while 循环,循环里每一步都可能因为模型调用超时、返回空消息、函数执行抛错而中断。源码中对这些异常多数是以try...except包起来后记录日志,然后继续或退出循环。这种设计的好处是容错,坏处是出错后聊天状态可能停留在某个中间点,后续重试时消息历史里已经混入了失败信息。

资源生命周期方面,临时目录的创建和使用是跟着执行上下文走的,但清理逻辑要看具体调用方。审计时我更关心的是:如果智能体长驻运行,临时文件会不会持续堆积?答案是取决于你的 executor 实现。默认的本地执行器不是每次调用都自动清理临时目录,长时间运行后需要人工或定时任务处理。Docker executor 相对容易收敛,因为容器生命周期可以随执行结束一起结束。

这部分虽然是细节,但生产环境非常关键。很多线上问题并不是主流程出 bug,而是异常分支把资源状态搞坏了。静态审计时不要只读 happy path,要刻意把except、finally、return None这些分支全部过一遍。

4. 审计结论、影响范围与工程建议

4.1 影响范围与风险画像

综合源码审计结果,我给 AutoGen v0.2.28 的风险画像可以概括成三句话:编排模型很灵活,但运行期决策不可控;代码执行能力强,但默认边界偏宽;框架迭代快,但依赖和配置需要团队自己收口。

用表格整理影响范围:

风险点触发条件影响面应对方式
对话历史无限增长长周期任务未主动压缩历史成本上升、上下文溢出定期摘要、控制 max_round、拆分子任务
本地代码执行器越权处理不可信输入且未沙箱化任意代码执行使用 Docker executor、限制工作目录
工具函数参数不可控模型被诱导调用已注册函数业务数据泄漏、越权操作函数内做鉴权和参数校验
日志未脱敏开启 verbose 且消息含敏感字段敏感信息落盘日志层统一脱敏
依赖版本过宽锁定文件未覆盖全部传递依赖供应链安全漏洞pip-audit 进 CI

这套影响范围适合作为团队引入 AutoGen 前的评审模板。你不用重新读代码,直接从表格里挑出和自己使用方式相关的风险点验证即可。

4.2 高频坑位与排查思路

实际使用 AutoGen 的过程中,有几个问题反复出现,这里直接给排查思路。

第一,GroupChat 卡住不切换 speaker。排查时先看max_round是否过小,或模型是否连续返回同一个非法 speaker。可以把speaker_selection_method临时改成random,如果切换恢复正常,基本能定位到模型侧调用异常。

第二,代码执行器跑不出结果,脚本本身在命令行却没问题。这多半是work_dir与当前进程路径不一致。建议在代码执行配置里显式指定work_dir,并确认该目录在宿主环境存在且有写权限。

第三,Docker executor 启动失败。最常见的是当前用户没有 docker 组权限。用id命令检查用户组,把执行用户加入 docker 组后再试。如果公司环境对容器权限有管控,还需要让运维提前放行执行节点。

第四,开启详细日志后发现敏感信息泄露。我已经在前面提到过,框架没有默认脱敏,建议把日志统一接到自己的 logging 配置里,用 filter 过滤掉 keywords。排查时可以用 grep 直接在日志文件里搜 key 字段,确认是否漏出。

第五,版本升级后 import 路径变化。AutoGen 在 0.2 到 0.4 期间对模块结构做了不少调整,升级前别只改版本号,跑一遍ruff或importchecker,把过期模块名批量替换掉会省很多事。

4.3 如何把静态审计嵌入后续迭代

审计不是一次性动作。我建议把核心检查点沉淀成 CI 流程,至少包含三件事:依赖漏洞扫描、危险函数调用扫描、依赖许可协议检查。GitHub Actions 里可以加一个简单 job,每次 push 和定时任务都跑一遍。这样框架升级时,风险点会第一时间暴露在 Pull Request 里,而不是等上线后再排查。

如果团队内部有安全要求,还可以在 Semgrep 规则里把 AutoGen 的执行器调用都标记为“需要人工确认”。不需要阻止所有执行器使用,但至少要在代码评审时强制加入确认环节。这个规则的粒度比通用扫描更贴合智能体项目,因为智能体项目的风险点和传统 Web API 不一样,口径也不同。

我个人的习惯是每次审计最后把 false positive 也归档。半年后再看同一份报告,往往发现自己对风险的理解已经变了,那些当初认定为误报的点,在新场景下可能变成了真正的问题。证据记录不是一次性结论,而是一份持续更新的判断依据。

这次审计下来,我对开源框架选型的态度也变得更务实:框架的核心机制不见得需要十全十美,但使用方必须知道每一条默认策略背后的代价。AutoGen 给了开发者很高自由度,同时把责任也交还给了开发者。如果你能接受这点,它确实是很强的多智能体编排底座;如果不能,再炫酷的 demo 也不该被直接搬进生产环境。