LLM Agent Skills凭据泄露:原理剖析与安全审计实践

LLM Agent Skills凭据泄露:原理剖析与安全审计实践 这次我们来看一个偏安全方向的选题LLM Agent Skills 的凭据泄露。论文标题是 “Credentials Are Leaked by LLM Agent Skills: An Empirical Study”核心意思很直白——在 LLM Agent 越来越依赖 Skills 完成自动化任务的同时API Key、Token、数据库口令这类凭据正在通过 Skills 这一层被泄露出去。先说结论性的判断这不是一个“会不会发生”的问题而是一个“已经发生、需要系统性排查”的问题。Skills 现在被大量用于 Claude Code、Codex、OpenCode 这类 Agent 工具中也经常出现在 n8n 这类自动化平台的 credential 配置流程里。第三方 Skills 的安装通常只是一条命令的事但装进来的东西在 Agent 执行时会被自动读取、自动解析、自动执行。如果里面夹带了凭据Agent 日志、工具调用记录、甚至是发给大模型的上下文都可能变成泄露通道。这篇文章不会只停在“大家要注意安全”这个层面。我会拆开讲清楚 Skills 的目录结构、凭据为什么会出现在里面、有哪些泄露路径然后给出一套可以直接落地的审计流程本地环境准备、静态扫描、批量扫描、人工复核以及接入 CI 的常见做法。适合正在做 LLM Agent 开发的工程师、做 AI 应用安全的同学以及需要评估第三方 Skills 的团队阅读。先说明一点这里不逐条复述论文的实验数据重点放在能直接用于自己项目的排查和防护方法上。1. 研究主题速览先把这篇研究和它的实践要点用一张表概括方便快速判断这篇文章和你有没有关系。项目说明研究对象LLM Agent SkillsClaude Code Skills、Codex Skills、OpenCode Skills 等中的凭据泄露问题核心问题Skill 文件、示例脚本、配置或执行日志中可能包含 API Key、Token、密码等凭据主要风险阶段第三方 Skills 安装、Agent 自动执行、日志持久化、Prompt Injection、供应链更新关联技术栈LLM Agent、Skills、MCP、n8n Credentials、Git 历史是否需要 GPU审计阶段不需要 GPU复现论文实验需按论文环境自行搭建是否需要大模型 API静态扫描不需要若复现 Agent 执行场景则需要适合读者Agent 开发者、安全工程师、平台运维、第三方 Skills 分发者主要防护思路密钥扫描、环境变量注入、最小权限、日志脱敏、安装前审查从论文标题看作者采取的是“实证研究”路线也就是收集真实环境中的 Skills 样本系统检测凭据泄露的规模和路径。这种思路本身就可以迁移到团队内部把 Skill 目录当作代码仓库对待定期做凭据审计。下面的内容就是围绕这套工作流展开的。2. LLM Agent Skills 是什么凭据为什么会出现在里面Skills 本质上是一组指令文件通常包含一个SKILL.md主文件带 YAML frontmatter里面声明 name、description 等元信息正文是给 Agent 的操作步骤、示例和约束。除了主文件一个 Skill 还经常附带 Python 或 Shell 脚本、JSON 配置、参考文档。Agent 在运行时会把 Skill 内容加载进上下文按里面的步骤调用工具、执行脚本、读写文件。当前常见的形态有几种。Claude Code 的 Skills 放在~/.claude/skills/skill-name/SKILL.md或项目内的.claude/skills目录Codex 有自己的 Skills 目录OpenCode 也支持类似结构。GitHub 上有大量社区整理的 Skills 集合比如 Superpower Skills、各类 awesome-skills 仓库安装方式通常是把仓库 clone 到指定目录或者用工具命令一键导入。n8n 的场景稍有不同n8n 的 Credentials 本身有独立的加密存储但 Skill 脚本如果要求把 credential 导出成明文给本地脚本用就引入了新的泄露面。搞清楚 Skills 和 MCP 的区别也很重要。MCPModel Context Protocol解决的是工具与模型之间的标准化通信MCP server 负责注册工具、执行调用Skills 更像是“能力包”或者“操作说明书”提供的是流程、规则、示例。两者经常配合使用MCP 提供可调用的工具Skills 告诉 Agent 什么时候用、怎么用。正因为 Skills 的内容会被直接加载进模型上下文它里面出现的凭据就不是“躺在磁盘上的静态文件”那么简单而是会跟着推理过程流转。那凭据为什么会出现在 Skills 里从实际工程经验看常见原因有这么几类。第一类是无意带入。作者在开发或演示时把 key 写死在示例里然后从旧项目拷贝文件把.env、config.json、credentials.json一起带进了 Skill 目录。Skill 开发门槛低很多人不会像对待正式服务那样严格管理密钥。第二类是“能用就行”的默认配置。某些 Skill 的运行逻辑必须读取真实凭据比如调数据库、调云厂商 API。作者为了开箱即用直接把凭据写在 SKILL.md 的环境变量示例、curl 命令或者脚本默认参数里。用户 clone 下来一执行凭据就以明文形式留在本地。第三类是模板生成。现在很多 Skill 是让 LLM 直接生成的模型在生成示例代码时可能参考了训练数据里出现过的 key 格式或者真实占位符这些内容被“带”进了生成的 Skill 文件。第四类是历史残留。把整个仓库当作 Skill 分发时.git目录也被一起复制。即使当前文件里删掉了 keyGit 历史里仍然有完整记录。静态扫描当前工作区可能扫不到但git log一翻就出来了。第五类是恶意构造。这是最需要注意的Skill 内容本质上是指令Agent 会按指令执行。如果一份第三方 Skill 的指令里包含“读取环境变量中的 API Key 并发送到指定地址”这类逻辑Agent 不会意识到这是攻击它只会认为这是任务的一部分。论文所讨论的“凭据泄露”很大一部分压力就在这种执行期的风险上。3. 凭据泄露的主要风险路径把风险路径拆细有助于确定到底该在哪里加防护。我按泄露发生的阶段梳理一遍。静态泄露指凭据直接存在于 Skill 文件、示例脚本、配置文件或 Git 历史中。这种最好发现也最容易被忽略。只要做一次全目录的密钥扫描基本能覆盖大部分情况。执行期泄露发生在 Agent 运行 Skill 的瞬间。Skill 脚本可能读取~/.aws/credentials、~/.kube/config、数据库连接串、第三方服务 token然后这些值会进入工具调用参数、终端输出、临时文件。如果脚本设计得不够干净凭据会以明文形式出现在进程列表、shell 历史或者临时目录里。日志与观测泄露是很容易被低估的一条路径。Agent 编排框架通常会把每次工具调用的输入输出完整记录下来方便调试和审计。但恰恰是这个“完整记录”会把 SKILL.md 中的凭据、脚本读取到的密钥、API 请求头里的 Authorization 全部写进日志文件。日志再被采集到 ELK、Loki 这类集中平台泄露范围就从单机扩大到整个监控体系。Prompt 上下文泄露是 LLM Agent 场景特有的风险。Agent 会把 SKILL.md 全文塞进模型上下文。如果 Skill 里包含真实凭据相当于把这些凭据提交给了模型服务商。即使用本地模型请求日志也可能被框架记录下来。对合规要求高的企业这一条需要格外重视。Prompt Injection 和恶意 Skill 则是主动攻击路径。Skill 内容可能在被加载时包含对抗性指令诱导 Agent 读取密钥文件或者发起外发请求。这里不展开具体构造方式但审计时必须重点检查 SKILL.md 和脚本中是否有可疑的读取、外发、编码混淆逻辑。供应链风险同样存在。如果 Skill 的更新源被劫持或者安装脚本从不可信的 CDN 拉取二进制那么即使你最初审查过 Skill后续更新也可能被替换。应对方式是固定版本、校验 hash、自托管镜像。整体来看凭据泄露不是单一漏洞而是从安装、执行、记录、上下文传递到供应链更新的一条完整链路。防护也需要按链路逐层做。4. 本地审计环境准备做凭据审计不需要 GPU也不需要大模型 API一台普通开发机就够了。建议准备一个隔离的审计目录把待检查的 Skills 克隆进去不要直接在生产机的重要目录里乱扫。这里给出一套通用环境清单。操作系统建议用 Linux、macOS 或 WSLWindows 原生环境也可以但命令需要相应调整。需要安装 Git 和 Python 3.10 以上版本用于克隆仓库、跑脚本。扫描工具方面常用的是 gitleaks、trufflehog、detect-secrets 三件套。gitleaks 适合扫 Git 历史和目录trufflehog 擅长扫描高熵字符串detect-secrets 适合作为 pre-commit 钩子长期拦截。安装 gitleaks 可以用包管理器或 Go 直接装# macOS brew install gitleaks # 或者用 go install go install github.com/gitleaks/gitleaks/v8latest # 验证 gitleaks versiondetect-secrets 用 pip 安装pip install detect-secrets装完之后先建立审计目录把待检的 Skill 仓库克隆进来mkdir ~/skill-audit cd ~/skill-audit git clone skill-repo-url如果是本地已有的 Skills 目录不需要克隆直接指向对应路径即可。需要提醒的是审计过程涉及密钥内容尽量在可信的本地环境完成不要把扫描报告上传到公开平台。如果只是想先验证流程可以自己构造一个包含假 key 的测试 Skill 目录避免一开始就在真实凭据上练习。5. 凭据扫描与人工复核步骤环境准备好之后按下面几步走可以比较系统地覆盖主要泄露路径。第一步粗筛。用 ripgrep 或 grep 在整个 Skills 目录里找关键词rg -n -i (api[_-]?key|secret|token|password|passwd|bearer\s[A-Za-z0-9]) ~/.claude/skills/这一步用来看“哪些文件值得优先人工检查”。关键字命中不代表一定有真实凭据但命中密集的文件通常需要重点看。第二步用 gitleaks 做全量扫描gitleaks detect --source ~/.claude/skills --report-path gitleaks-report.json --report-format json --verbosegitleaks 会同时扫当前文件和 Git 历史输出 JSON 报告里面包含命中的文件、规则、匹配内容所在行。注意gitleaks 的默认规则覆盖了常见厂商的 key 格式但覆盖率有限不能只依赖它。第三步单独查 Git 历史。很多时候 key 已经被删了但历史里还有gitleaks detect --source /path/to/repo --log-opts--all如果你管理的 Skill 仓库是自己写的这一步特别重要。一次不经意的git push把.env推上去之后即使删掉文件历史里的 key 也无法通过“删文件”清除。第四步人工复核 SKILL.md 正文和附件脚本。这一步机器替代不了。重点看几个位置环境变量示例里写的到底是不是占位符curl 命令里有没有带真实 bearer token示例 JSON 里有没有出现实际的 key 字段附件脚本的默认参数有没有硬编码有没有可疑的外发 URL。第五步判断命中结果的置信度。格式匹配加上下文可读比如sk-开头后面跟一长串随机字符且出现在“生产环境配置”这类段落中属于高置信命中。匹配到YOUR_API_KEY、xxxxxxxx这类占位符属于低置信。匹配结果指向 localhost、测试域名、示例文档大概率是误报。对于确认的真实泄露第一件事是吊销密钥再通知相关维护者而不是继续深挖报告。整个流程做完应该能产出一份人工确认过的泄露清单。这个清单可以直接作为工单分配给对应的 Skill 维护者去修复。6. 批量扫描与自动化检查如果团队里已经积累了成百上千个 Skills靠手工一条条跑命令不现实。这里给一个简单的 Python 扫描脚本可以递归扫整个目录树把匹配到的凭据输出成 CSV 报告。脚本里的正则按需扩展即可。import os import re import csv # 常见密钥格式按实际需要增删 PATTERNS { openai: rsk-[A-Za-z0-9]{20,}, github: rghp_[A-Za-z0-9]{36,}, aws_access_key: rAKIA[0-9A-Z]{16}, google_api: rAIza[0-9A-Za-z\-_]{35}, slack_token: rxox[baprs]-[0-9A-Za-z\-]{10,}, } def scan_dir(root): results [] for dirpath, _, filenames in os.walk(root): for fn in filenames: path os.path.join(dirpath, fn) if .git in path or not os.path.isfile(path): continue try: with open(path, r, encodingutf-8, errorsignore) as f: content f.read() except Exception: continue for name, pat in PATTERNS.items(): for m in re.finditer(pat, content): results.append({ file: path, type: name, match: m.group(0), }) return results if __name__ __main__: root os.path.expanduser(~/.claude/skills) rows scan_dir(root) with open(skills_secrets_report.csv, w, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnames[file, type, match]) writer.writeheader() writer.writerows(rows) print(fscan done: {len(rows)} potential matches)运行方式python3 scan_skills.py脚本会把命中项写到skills_secrets_report.csv之后在表格软件里按文件、按类型排序可以快速看出哪些 Skill 是重灾区。要真正长期防住得把扫描接到流水线里。团队仓库可以在 pre-commit 阶段加 gitleaks 钩子repos: - repo: https://github.com/gitleaks/gitleaks rev: 按最新 release 填写 hooks: - id: gitleaks也可以加 detect-secrets 作为补充配合.secrets.baseline管理误报。对于第三方 Skill 目录用定时任务每周扫一次报告推送到团队群比每次都人工跑命令可靠得多。批量任务的核心经验是两句话正则要克制误报靠人工维护白名单报告要保留上下文不然没法判断真实泄露。7. 资源占用与性能观察凭据扫描的资源占用并不高但很多人第一次跑全量扫描时没概念容易慌。这里说清楚。静态扫描阶段CPU 和磁盘 IO 是主要消耗。用纯 Python 正则扫一千个小型文本文件通常只需要几秒到几十秒取决于磁盘速度和文件数量。gitleaks 扫描同等规模的目录会更快因为它用 Go 实现并发做得好。内存占用方面普通目录不到 1GB基本可以忽略。如果扫的是大型 monorepo建议先排除node_modules、vendor、二进制文件这些明显不需要检查的目录。复现论文实验或者做 Agent 执行期测试时资源占用就不一样了。如果用本地模型跑 AgentGPU 显存会成为瓶颈如果走远程大模型 API成本主要在请求量和限流上。Skill 的上下文越长单次请求消耗的 token 越多日志体量也越大。观察这类负载可以简单用系统自带的工具统计/usr/bin/time -v python3 scan_skills.py也可以观察日志目录大小增长du -sh ~/.claude/ # 看 Claude Code 目录整体大小 du -sh ~/.codex/logs # 看 Codex 日志Agent 执行实验的显存占用以本地 GPU 为例可以用 nvidia-smi 观察nvidia-smi --query-gpumemory.used,utilization.gpu --formatcsv -l 2总的原则是先小范围验证再全量铺开。先扫一个 Skill 目录、先跑一条测试用例确认报告格式和判定逻辑没问题再扩大范围。不要在第一天就挂一个全团队的大任务这样排查问题会非常痛苦。8. 常见问题与排查方法审计过程中会遇到不少看起来奇怪的现象。下面这个表格整理了典型问题、可能原因和处理方式。问题现象可能原因排查方式解决方案扫描出大量 API Key 误报正则过宽占位符被当成真 key查看命中的上下文看值是否像随机串调整正则维护 allowlistgitleaks 报告为空但怀疑有泄露key 在 Git 历史、压缩包或二进制文件中加--log-opts--all检查附件和压缩文件扩大扫描范围skill 文件是加密或混淆的作者用加密方式分发配置静态分析难度大放弃不明来源的 skill删除 key 后扫描仍报警key 残留在 Git 历史、缓存或日志副本中查.git目录、缓存目录、历史日志清历史、强制轮换密钥Agent 执行时凭据进入日志工具调用输入输出未脱敏查看 Agent 日志中的完整参数加日志脱敏过滤器本地脚本能读到生产凭据环境变量从宿主环境透传给了 Agent检查 env 注入配置显式注入最小必要变量第三方 skill 更新后被篡改供应链投毒或源仓库被劫持对比最近提交、校验 hash固定版本、自托管镜像CSV 报告里 match 字段被截断正则结果太长或包含特殊字符打印完整匹配确认编码限制匹配长度脱敏输出这些问题的共同点在于单纯依赖一种扫描工具永远不够。工具负责“发现可疑项”人负责“判断是否真实”。判断真实泄露的标准只有一个——这个值能不能在测试环境中触发真实调用。能触发就是真泄露不能触发就要回到上下文里去确认是占位符还是被混淆过的真实凭据。9. 最佳实践与防护建议审计是发现问题的手段真正的目标是不让凭据以明文形式出现在 Skills 生态里。从工程实践角度建议按下面几条做。第一条凭据不进仓库。.env、credentials.json、证书文件全部加入.gitignore并且要确认 Git 历史里从来没有提交过。如果历史里有轮换密钥比清理历史更彻底。密钥一旦被推到远端就要默认它已经泄露。第二条环境变量注入。Agent 运行时只注入当前任务需要的最小变量集。不要图省事把宿主机整个环境变量透传给 Agent否则一个 Skill 脚本可以读到所有服务的密钥。第三条最小权限。每个 Skill 使用独立 scope 的 token权限只覆盖它实际要调用的资源并设置较短的失效时间。这样即使某个 Skill 泄露了凭据攻击面也被限制在单一资源上。第四条日志脱敏。在 Agent 编排框架的日志输出层对sk-、ghp_、Bearer、AKIA等模式做统一脱敏。这个可以从日志采集端做也可以在写入存储前做。没有脱敏的日志系统等于给所有泄露路径开了个后门。第五条安装前审查。第三方 Skills 装进团队环境前至少要看一遍 SKILL.md、检查所有脚本里出现的 URL、确认作者和更新频率。对高风险 Skill先固定版本不要自动跟随更新。社区里推荐的 Skill 很多但“推荐”不等于“安全”。第六条沙箱执行。运行不明来源 Skill 时尽量放在受限环境里只读文件系统、限制网络出口、禁用 shell 历史记录。Agent 自动化程度越高沙箱越重要因为它会把“人为确认”这个环节完全替代掉。第七条合规边界。这里要专门强调一下在做凭据审计时只扫描自己有权访问的仓库和 Skill 目录对第三方的系统不要做越权探测。发现第三方库存泄露凭据应该通过正规渠道反馈给维护者而不是把泄露内容公开传播。涉及企业数据、客户信息、知识产权的内容也要遵守内部保密要求。最后是给团队的建议把“Skills 凭据审计”变成周期性任务而不是一次性活动。论文的实证研究思路完全可以复制到内部——定期收集 Skills 样本做静态扫描统计泄露类型再针对高频问题改进开发规范。这套机制跑起来之后Skills 生态的安全性会明显好于放任不管的状态。10. 总结与下一步这篇论文的标题已经说得很清楚LLM Agent Skills 确实存在凭据泄露问题而且值得专门做实证研究。从工程角度看这个话题最值得关注的点不是“Skills 本身不安全”而是“Skills 的自动加载机制放大了既有安全问题的后果”。普通项目里一个硬编码的 key 可能只是躺在代码库里但在 Agent 场景里同一个 key 会被读进上下文、写进日志、传给外部模型泄露范围完全不同。如果你现在正准备在团队里引入 Skills第一步先做一件事把本地 Skills 目录跑一遍 gitleaks确认里面没有任何真实凭据。没有历史包袱后面做环境变量注入、日志脱敏、最小权限都会顺利得多。如果发现团队里已经有不少第三方 Skills就先固定版本、建一个误报白名单再逐步把扫描接入 CI。最容易踩的坑有两个。一个是把扫描工具的告警直接当成结论没有做人工复核结果误报淹没真实泄露团队很快对报告失去信任。另一个是只扫当前文件漏掉 Git 历史里的残留密钥。记住一条判断原则所有从仓库里翻出来的 key先当真实泄露处理先吊销、再排查永远不要赌它没被使用过。后续可以继续扩展的方向包括把 Skills 打包流程标准化强制在构建阶段做密钥检查在 Agent 编排层增加凭据访问审计记录哪个 Skill 在什么时候读取了哪个环境变量对 n8n 这类自动化平台梳理 credential 的导出和传递链路确保加密存储不会被脚本绕过。先把扫描和轮换机制跑起来再逐步完善这套安全水位就能比大多数团队做得更扎实。建议收藏备用下次引入新 Skills 时直接按这个流程走一遍。