资讯动态

AI Agent技能文件安全:隐藏目录并非防护,权限管控与审计实践

发布时间:2026/9/2 2:15:05 来源:尧图企业网站定制
在 AI Agent 项目中技能文件Skill Files通常存放模型可以调用的一组工具描述、提示词模板、脚本或执行逻辑。很多开发者会把技能文件当成普通业务代码一样管理却忽略了它另一个属性技能文件往往包含对系统权限、内部接口、数据格式甚至业务密钥的描述一旦被正常调用链路读取或返回就可能在未授权场景下造成敏感能力泄露。“隐藏的 Agent 技能文件可被正常使用窃取”描述的就是这类风险文件明明放在隐藏目录里外部看不到路径但通过 Agent 的正常输入、工具调用或日志输出文件内容仍可能被完整读取出来。文章会从技能文件在 Agent 系统中的角色出发拆解风险链路再给出可落地的权限控制、配置隔离、审计监控和排查方法。适合正在做 Agent 开发、Agent 框架集成、或负责 AI 应用安全评审的开发者。1. 技能文件为什么值得单独做安全设计1.1 技能文件解决了什么问题理解这个安全问题先要理解 Agent 技能文件到底是什么。在 Agent 开发中“技能”通常指一类可以被大模型按需调用的预定义能力。比如一个天气查询技能模型收到“明天杭州会不会下雨”时不是直接自己回答而是调用天气查询函数再把函数返回结果组织成自然语言。技能文件就是描述这种能力的载体。常见形态包括YAML / JSON 文件定义技能名称、描述、参数、调用方式。Python / JavaScript 脚本实现技能的实际执行逻辑。Prompt 模板记录模型调用该技能时的提示词和约束。MCP / 插件配置文件在 Agent 框架中声明远程工具入口。技能文件在运行期的作用是“让模型知道有什么技能可用以及如何调用”。因此它必然会被 Agent 运行时加载进上下文。这个特点决定了技能文件不仅有代码属性还有配置属性同时还是模型上下文的一部分。1.2 技能文件与普通代码文件的差异普通业务代码编译或打包后运行运行时不一定需要读取源码。技能文件则不同它常常在运行期以纯文本或结构化文件形式被读取然后直接拼接进提示词或工具注册表。可以把技能文件和普通文件做一个对比对比维度普通代码文件Agent 技能文件运行期是否需要被读取通常不需要编译后执行通常需要运行时加载可见路径可通过代码仓库管理可能由用户输入或远程框架动态加载内容敏感度源码可能敏感包含工具描述、参数结构、内部地址、密钥引用泄露途径源码泄露、逆向日志、模型输出、错误信息、缓存、上下文回显访问控制设计常规代码仓库权限还需要模型调用侧、服务端、存储侧多层控制技能文件的特殊性在于它既可以被当作文件系统资源访问又可以被模型当作“知识”使用。如果模型在对话过程中复述了技能文件中的内容这种泄露甚至不依赖文件系统权限只要模型上下文里包含技能原文就可能被诱导输出。1.3 技能文件成为敏感资产的三个原因第一个原因是技能描述可能透露内部系统结构。比如技能文件里写了“调用内部订单中心http://10.0.0.5:8080/api/query使用internal_token请求头”这就相当于把内部接口和认证方式暴露给了所有能读取该技能文件的人。第二个原因是技能文件里经常夹带密钥。开发时为了快速跑通开发者可能把 API Key、数据库密码、内部服务 Token 直接写在 YAML 里。这样技能文件一旦泄露等于密钥同步泄露。第三个原因是技能文件的内容会进入模型上下文产生二次扩散。模型不会把技能描述当作绝密内容用户通过精心构造的提示词可能让模型复述工具定义、参数说明甚至执行脚本。隐藏文件路径并不能阻止这个方向的风险。2. 一个最小技能文件与不安全访问模型2.1 最小技能文件示例为了把问题讲清楚这里构造一个最小技能文件。假设它负责查询内部健康检查接口name: internal_health_check description: 查询内部服务健康状态仅限内部运维使用 parameters: - name: service_id type: string required: true description: 服务唯一标识 command: curl -s http://10.0.0.10:8080/health/$SERVICE_ID这个文件放在项目目录下的.agent_skills/internal_health_check.yaml目录名以点开头在 Linux 下默认不可见。很多开发者认为“隐藏目录”已经足够安全因为普通用户不会去翻点目录。但从安全设计角度看隐藏目录只是降低被发现概率不构成访问控制。2.2 不安全访问模型隐藏目录 通用工具读取下面这段代码展示了一个不安全但常见的实现方式Agent 允许模型调用一个通用文件读取工具技能文件也存放在隐藏目录中。工具函数没有校验读取路径而是直接把文件内容返回给模型。import os import yaml from pathlib import Path SKILL_DIR Path(.agent_skills) def read_skill_file(file_path: str) - str: 不安全的技能加载方式 直接根据传入路径读取文件并返回内容。 真实项目中不要这样实现。 full_path SKILL_DIR / file_path if not full_path.exists(): return 技能文件不存在 return full_path.read_text(encodingutf-8)这里的问题很明显file_path由模型或用户输入拼接生成只检查了文件是否存在没有校验路径是否被限制在SKILL_DIR范围内。如果模型被诱导传入../config/secrets.yaml那么即使技能目录藏着文件敏感配置仍然可能被读取。2.3 运行结果与暴露现象当模型调用read_skill_file并传入参数internal_health_check.yaml时返回内容可能如下name: internal_health_check description: 查询内部服务健康状态仅限内部运维使用 command: curl -s http://10.0.0.10:8080/health/$SERVICE_ID正常业务场景中模型只需要知道“有这个技能、参数是什么”就够了不一定需要看到完整的command命令。但在这个实现里模型和调用者都能看到脚本原文内部服务地址、请求方式、参数规则全部暴露。更关键的是这个读取行为是通过 Agent 的正常调用完成的没有突破任何系统边界。隐藏目录并没有阻止内容被读取只是让“没有路径的人”暂时找不到它。这正是一个典型的“隐藏文件被正常使用窃取”的安全风险场景。2.4 对风险现象的正确理解这里要强调一个边界上面示例的目的不是教如何窃取文件而是说明“隐藏目录 通用读取工具”为什么不能满足安全要求。实际排查中看到技能文件被完整回显到对话结果里就应该立刻意识到访问控制失效。判断标准很简单如果用户或模型能够通过一次正常调用的输入参数让技能文件内容出现在返回结构中那么无论目录名多隐蔽这个设计都是不安全的。技能文件必须被当作“需要按身份授权访问”的敏感资源而不能当作“藏起来就安全”的普通资源。3. 从正常使用到内容暴露的完整链路拆解3.1 调用链路上有几次内容可见技能文件从存储到被模型使用通常会经历四个环节加载、解析、执行、回传。每个环节都可能产生内容暴露。加载阶段Agent 运行时读取技能文件原文。如果这一步打了日志文件原文就会进入日志系统。解析阶段框架把 YAML 或 JSON 转成内部结构。如果解析失败错误信息可能打印出原始文件内容。执行阶段技能脚本本身可能打印敏感信息。回传阶段模型把技能执行结果或者工具定义重新组织成回答如果模型把技能描述直接复述出来用户就看到了完整内容。以下是一条典型链路用户输入 - 模型选择技能 - 技能加载器读取文件 - 技能描述进入提示词 - 模型调用技能 - 执行结果返回 - 模型生成回答在这条链路上至少有三个点可能泄露技能文件内容加载器返回原文、模型复述技能描述、执行脚本打印内部信息。3.2 隐藏目录为什么不能替代访问控制隐藏目录属于“可用性设计”不是“安全设计”。它的作用是让正常用户少看到无关文件延长发现时间。但攻击者或模型一旦通过路径遍历、日志泄露、错误回显、Git 历史等方式拿到路径隐藏目录不会产生任何阻止能力。一个典型判断标准是文件系统权限是否限制到了“只有特定用户可读”。如果文件权限是0644所有用户都能读那么隐藏目录只是心理安慰。在生产环境应该使用服务账号、独立用户、容器只读层等手段限制技能文件的读取范围。3.3 常见暴露出口技能文件内容可能通过以下出口泄露暴露出口典型表现检查方式应用日志加载技能文件时打印完整 YAML检索日志中的name:、description:等关键字段模型上下文回显用户询问技能定义模型复述文件内容构造对话检查模型是否输出技能脚本工具返回结果读取函数直接把文件内容作为返回值查看工具函数是否返回完整文本而非摘要错误信息YAML 解析异常时抛出原始文本查看异常堆栈中是否包含文件内容Git 历史密钥或技能文件被提交到仓库git log --all --full-history -- 技能文件路径缓存文件内容和执行结果被缓存到 Redis/Memcached查看缓存 key 和 value 是否包含敏感信息3.4 风险等级评估不同类型的技能文件风险等级不同。开发时可以先按内容敏感度给技能文件分类技能文件内容风险等级场景示例无敏感信息低公共天气查询技能内部服务地址中内部 API 调用技能内部接口认证信息高带 Token 的运维脚本数据库连接信息高数据查询技能完整业务密钥极高直接存储云厂商 Key 的技能文件对于中风险以上文件不能只做“限制访问”还要做“最小化内容”也就是尽量不让技能文件本身包含敏感信息而让密钥来自外部配置。4. 把技能文件的访问控制做成安全边界4.1 技能文件存储层隔离首先要把技能文件从普通静态目录里拆出来。不要放在static/、public/、docs/这类会被 Web 服务直接托管的目录里。推荐使用独立目录并通过文件系统权限限制为服务账号可读。在 Linux 环境下可以这样设置# 假设技能目录位于 /opt/my-agent/skills sudo mkdir -p /opt/my-agent/skills sudo chown agent-service:agent-service /opt/my-agent/skills sudo chmod 750 /opt/my-agent/skills750表示只有agent-service用户可读可执行同组用户可读其他用户不可访问。这比0644的默认权限严格得多。如果技能目录需要被多个服务读取可以把多个服务加入同一用户组再使用chmod 750。4.2 技能调用层做身份与授权文件系统权限只能控制“谁能读文件”不能控制“模型在什么场景下可以使用技能”。因此需要在应用层增加授权判断。以示例代码为例可以给技能注册表增加权限标记AUTHORIZED_SKILLS { internal_health_check: {roles: [ops, admin]}, public_weather_query: {roles: [user, ops, admin]}, } def is_authorized(user_role: str, skill_name: str) - bool: allowed_roles AUTHORIZED_SKILLS.get(skill_name) if allowed_roles is None: return False return user_role in allowed_roles在 Agent 加载技能之前先校验当前调用者的身份。只有通过校验技能文件才会被读取。这样即使文件路径暴露没有对应角色的人也无法正常调用。4.3 内容不落地技能逻辑与密钥分离最好的安全状态是技能文件里没有密钥。技能文件可以写“调用什么服务”但服务凭据应该来自环境变量或密钥管理服务。推荐把密钥引用写成占位符name: internal_health_check description: 查询内部服务健康状态仅限内部运维使用 command: curl -s http://10.0.0.10:8080/health/$SERVICE_ID headers: Authorization: Bearer ${INTERNAL_API_TOKEN}在运行期通过环境变量注入export INTERNAL_API_TOKEN生产环境不要这样写在命令行里如果让技能文件只保留参数结构和执行逻辑把密钥放在外部配置中心或云密钥管理服务中那么即使技能文件被读取攻击者拿到的也只是占位符不是真实凭据。4.4 运行沙箱限制读写行为除了文件权限和授权校验还要限制技能脚本在运行期能访问的资源。如果技能文件是 Python 脚本不要直接在高权限进程中执行。推荐使用独立进程、容器或 Serverless 沙箱运行技能代码并设置禁止访问进程环境变量中的敏感项。禁止访问宿主文件系统只开放临时目录。禁止无限制外联只允许访问白名单域名或内网服务。限制 CPU、内存、网络超时等资源。沙箱的价值是即使某个技能文件被恶意构造它也无法读取系统上的其他敏感文件。5. 让技能文件使用过程可审计、可追溯5.1 记录结构化技能调用日志技能文件泄露往往不是一次性事件而是多次异常调用的累计结果。没有日志就无法排查是谁、在什么时间、通过哪个技能读取了哪个文件。推荐为技能调用单独记录结构化日志。建议字段如下日志字段示例值说明request_idreq_8f3a2c一次调用链路的唯一标识user_idu_1024调用者身份skill_nameinternal_health_check被调用的技能input_params{service_id:order}输入的参数摘要file_accessed.agent_skills/internal_health_check.yaml实际访问的技能文件output_length328返回内容的长度result_statusok/error调用结果日志记录要遵循最小化原则不记录完整密钥或完整的文件原文避免日志本身成为新的泄露出口。5.2 敏感技能使用告警当检测到以下行为时应触发告警并通知安全负责人短时间内多次读取同一个高敏感技能文件。普通用户角色尝试加载admin角色才能使用的技能。技能返回结果中包含疑似密钥字符串。对话输出中出现技能文件原始 YAML 的片段。可以用简单的规则扫描模型输出import re SENSITIVE_PATTERN re.compile(r(api[_-]?key|secret|token)(\s*[:]), re.I) def check_skill_output(output_text: str) - bool: return bool(SENSITIVE_PATTERN.search(output_text))这里只做演示。生产环境建议接入专门的密钥检测服务并把告警事件写入 SIEM 或内部审计平台。5.3 事件排查路径如果怀疑技能文件已经泄露按以下顺序排查先确认技能文件是否被提交到 Git 历史。使用命令git log --all --full-history -- .agent_skills/再查文件系统权限是否有问题ls -l .agent_skills/然后查应用日志中是否出现技能文件原文grep -rn internal_health_check /var/log/接着查模型输出看是否有用户诱导模型复述技能定义。最后查缓存和临时文件确认技能内容是否进入 Redis、Memcached 或临时目录。这套路径的价值在于先确认“有没有泄”再确认“从哪里泄”最后才能决定是回收密钥、收紧权限还是下线技能。6. 技能文件安全生命周期检查清单6.1 开发阶段的检查项开发技能文件时就按以下规则执行默认把技能文件放在独立目录不用点开头目录作为安全手段。不在技能文件中写入真实密钥统一使用占位符。不在示例 YAML 中复制生产环境配置。使用git-secrets或pre-commit钩子扫描提交内容。对技能文件进行代码评审重点检查是否包含内部地址和账户信息。# pre-commit 钩子简单示例 #!/usr/bin/env bash if grep -rE (api[_-]?key|secret|password|token) .agent_skills/; then echo 检测到技能文件包含疑似敏感信息 exit 1 fi6.2 测试与发布阶段的检查项不同环境使用不同配置文件避免把生产环境的技能文件复制到测试环境测试环境使用模拟技能目录内容只包含测试接口。生产技能文件通过部署系统单独下发不跟随代码仓库直接发布。发布前检查技能目录权限是否被重置。发布后立即检查一次日志确认没有加载敏感技能导致原文打印。对新建技能进行安全评审确认它需要的最小权限是什么。6.3 运行阶段的检查项运行阶段要持续做检查而不是只在发布时检查一次开启技能调用审计日志日志字段按照前文结构化格式记录。每月轮换一次技能调用所需的密钥和 Token。监控高敏感技能文件的访问次数异常时告警。定期扫描模型输出查找技能定义回显。对不再使用的技能及时下线并删除对应文件。6.4 扩展方向Agent 安全治理技能文件安全只是 Agent 安全治理的一部分。下一步可以关注以下方向技能签名对技能文件做数字签名防止内容被篡改。策略引擎在技能调用前通过可控策略判断用户、模型、输入是否满足条件。上下文隔离对高敏感技能内容做脱敏只让模型看到必要参数不显示完整脚本。异常行为检测结合用户历史行为识别“正常调用中夹带文件读取”的异常模式。对这些方向感兴趣可以从小处入手先给现有技能文件补上权限校验和审计日志再逐步引入沙箱和策略引擎。不要一上来就搭建复杂平台很多风险在基础层就能被控制住。技能文件安全问题最终归结为一个判断不要把可见性当作安全性不要用隐藏目录替代授权不要把调用权限和读取权限混为一谈。在 Agent 项目中技能文件应该被当作敏感配置来管理配合最小化内容、外部密钥、沙箱执行和审计日志才能真正降低“正常使用导致内容泄露”的风险。实际项目里最该做的一件事是先把现有技能文件扫描一遍找出包含真实密钥和内部地址的文件然后立刻修正。

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

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

免费获取报价