
1. 项目背景为什么 Agent 需要“护栏”做 Agent 开发有一段时间的人应该都有过这种体验Agent 跑着跑着突然去读了一个不该读的本地文件或者请求了一个内网地址更有甚者把用户敏感信息原封不动地吐给了外部服务。这些不是段子是真实踩过的坑。我最初做 Agent 项目的时候想的很简单——把大模型接入工具调用让 Agent 能读写文件、发起 HTTP 请求就够了。可真上了生产环境才发现LLM 的“自由发挥”能力远超想象。它可能因为提示词里一句模糊的话就把/etc/passwd读出来拼进回复里也可能因为某个网页重定向就帮你把内网管理后台的接口扫了一遍。这个项目——“给 Agent 上护栏文件三级访问、SSRF 拦截与带校验位的响应脱敏”——就是为解决这类问题而做的。核心是给 Agent 加上三层安全机制文件系统做三级访问控制黑名单、白名单、授予访问防止 Agent 越权读取、写入本地文件SSRF 攻击拦截从 DNS 解析到端口探测全面防护防止 Agent 被诱导访问内网与云元数据服务响应脱敏与校验位保护在数据出 Agent 之前做敏感信息过滤并添加校验位确保数据未被篡改这三件事单独拎出来每一项都是独立的安全话题但放在 Agent 场景里它们是一个整体——你既不能让 Agent 手脚被绑死也不能让它失控乱跑。护栏的本质是“可控的自由”而不是“一刀切的封锁”。这适合谁看一类是做 AI Agent 开发、正在设计工具调用安全策略的工程师另一类是做安全评估、需要给 Agent 应用做渗透测试和加固的从业人员。无论你用的是 LangChain、Coze 还是自研编排框架这套思路都能直接搬过去用。2. 文件三级访问控制从“裸奔”到“分级授权”2.1 设计思路为什么用“三级”而不是“一刀切”先说为什么要做三级而不是简单搞一个“允许读/不允许读”的开关。Agent 处理文件的需求差异非常大系统启动文件、配置文件它几乎不该碰用户主动上传的文档、项目目录下的资料它需要完整读取还有一些场景比如读取当前工作目录下的临时文件你又不想给 Agent 全盘权限。如果只做“允许/禁止”两级要么管得太松要么管得太死都不好用。我采用的分级方案是一级黑名单路径规则绝对禁止二级白名单路径规则默认禁止放行指定目录三级授予访问本次会话内动态放行这个设计的核心逻辑是——黑名单保底白名单划线授予访问兜底现场需求。黑名单负责挡住那些无论什么情况都不该碰的路径白名单设定 Agent 的工作边界而授予访问解决的是“规则之外但确实需要读一次”的场景比如用户临时丢了一个文件让 Agent 分析。2.2 黑名单规则先堵死高危路径黑名单规则优先级最高命中即拦截不做任何二次询问。需要注意一点黑名单不能只写“/etc/passwd”这种精确路径因为 Agent 完全可以拼接路径绕过。比如 Linux 下/etc/./passwd、//etc//passwd、/etc/passwd/这种写法都能踩到同一个文件。我在实现里做了三步归一化import os def normalize_path(path): # 第一步展开环境变量和用户目录 path os.path.expandvars(path) path os.path.expanduser(path) # 第二步转换绝对路径 path os.path.abspath(path) # 第三步解析所有 . 和 .. path os.path.realpath(path) return pathos.path.realpath会把软链接、相对路径全部解析成真实路径这样 Agent 想用../../etc/passwd或者/tmp/link_to_passwd软链接这类方式绕过就直接失效了。黑名单里我放的东西分这么几类系统关键文件/etc/passwd、/etc/shadow、/etc/sudoers、/root/.ssh/*、~/.bash_history密钥与凭证相关.env、*.pem、*.key、*.p12、id_rsa、id_ed25519、credentials.json、token.txt数据库文件各种 SQLite 文件、.db、.sqlite3包管理相关package-lock.json不是重点重点是 npm/yarn/pip 的缓存目录里可能有带 token 的配置Agent 自己的配置与日志防止 Agent 读取自己的系统提示词或完整运行日志被套话套出去就麻烦了这段规则的价值在于即使 Agent 被提示词注入诱导去“读取系统信息”第一层防线就能把绝大多数高价值目标直接堵死。2.3 白名单规则划定 Agent 的工作边界白名单的设计思路是默认拒绝一切文件访问除非路径落在被明确允许的目录/文件列表里。规则分三种粒度目录级允许读取整个项目目录比如/data/workspace扩展名级允许读取.txt、.md、.csv等无害类型但禁止.py防止 Agent 读取源码后把逻辑泄露出去、禁止.env哪怕它在工作目录里具体文件级比如用户明确说“帮我读一下./docs/README.md”就把这个具体文件加白这里容易踩的坑是跟随软链接逃逸白名单。你允许 Agent 读/data/workspace但目录里有个logs - /var/log的软链接Agent 就可以通过它读到系统日志。我的做法是在白名单校验时也走realpath并且对路径的每一级做检查def check_whitelist(path, whitelist): path normalize_path(path) # 逐级检查父目录防止软链接逃逸 current path while True: if current in whitelist: return True parent os.path.dirname(current) if parent current: break current parent return False这样即使中间某级是软链接也能保证最终解析后的路径在白名单范围内。2.4 授予访问动态授权用完即走黑名单和白名单解决了“常态”问题但 Agent 应用里总有临时情况。比如用户上传了一个 excel 到临时目录你不可能预先知道这个临时文件叫什么名字。这种情况下最优雅的方案是“授予访问”机制——运行中外部系统给 Agent 发送一条授权指令让它有权读取特定文件。这个动作的关键点在于临时与最小化有明确的过期时间比如 30 秒或本次工具调用完成即失效一次只授予一个文件/目录不搞批量授予记录审计日志谁在什么时间因什么原因给 Agent 授予了哪个文件的访问权在 LangChain 这类框架里可以把它实现成一个工具装饰器class FileAccessManager: def __init__(self): self._grants {} def grant(self, path, expires_in30): normalized normalize_path(path) self._grants[normalized] time.time() expires_in def check(self, path): normalized normalize_path(path) if normalized in self._grants: if time.time() self._grants[normalized]: return True else: del self._grants[normalized] return False2.5 文件访问控制的优先级判定实际操作中检查顺序非常重要我总结的判定优先级是优先级规则动作1路径是否命中黑名单直接拒绝记录告警2路径是否命中白名单含授予访问允许访问记录审计3是否匹配默认拒绝规则直接拒绝记录审计顺序不能反。如果先查白名单再查黑名单一个既在黑名单又在白名单里的路径就会被放行——这种冲突情况在配置多的时候非常容易发生。黑名单永远第一这是底线。还有一个小细节值得说所有文件操作统一走一个受限的“文件工具”而不是让 Agent 直接拿到操作系统原生的读文件能力。在框架层拦截比在模型层引导可靠得多这是 Agent 安全设计里最核心的原则之一。3. SSRF 拦截别让你的 Agent 变成内网扫描器3.1 Agent 场景下 SSRF 的危险性SSRFServer-Side Request Forgery服务端请求伪造最早是 Web 应用里的老问题但在 Agent 场景下它有了新的攻击面——Agent 本身就是个会主动发起请求的程序而且它发起请求的依据是用户输入和模型推理结果不可预测性比传统 Web 应用高得多。典型攻击路径长这样用户在对话框里输入一段包含图片 Markdown 的文本Agent“好心”帮你获取这个图片来识别内容。如果这个 url 指向的是http://127.0.0.1:6379那么 Agent 就会帮你去访问 Redis如果指向http://169.254.169.254Agent 就是在帮你探测云元数据服务在部分环境里可以读取到实例的角色临时凭证。更隐蔽的是一些 Agent 框架会把“网站内容总结”做成一个工具用户只需要给一个链接Agent 就会请求并总结。攻击者构造一个指向内网服务的链接丢给 AgentAgent 就变成了攻击者的跳板。所以 SSRF 防护在 Agent 里不是可选项而是必选项。3.2 四层防护DNS IP 端口 协议我这边的实现分四层层层递进第一层协议限制只允许http:和https:其余协议一律拒绝。file://、ftp://、gopher://这些在 Agent 场景里没有使用场景。这个用 Python 的urllib.parse就能解析from urllib.parse import urlparse def check_protocol(url): parsed urlparse(url) if parsed.scheme not in (http, https): raise SSRFError(fProtocol not allowed: {parsed.scheme}) return parsed第二层DNS 解析与 IP 检测把主机名解析成 IP判断是公网还是内网。内网地址段包括地址段用途10.0.0.0/8私有地址172.16.0.0/12私有地址192.168.0.0/16私有地址127.0.0.0/8本机回环169.254.0.0/16链路本地地址云元数据0.0.0.0/8本机::1IPv6 回环fc00::/7IPv6 唯一本地地址这里有一个很关键的坑DNS 重绑定。攻击者可以让同一个域名第一次解析返回公网 IP第二次解析返回内网 IP。你检测的时候是公网 IP但 Agent 实际请求的时候解析到了内网 IP。我的解决方案是解析两次比对结果。或者更彻底一点——获取 IP 后直接用这个 IP 去发起请求不重新走 DNS 解析import socket import requests def safe_request(url, timeout5): parsed check_protocol(url) # 解析出 IP ip socket.gethostbyname(parsed.hostname) if is_private_ip(ip): raise SSRFError(fBlocked private IP: {ip}) # 用 IP 替换主机名并带上 Host 头 headers {Host: parsed.hostname} transformed_url parsed._replace(netlocf{ip}:{parsed.port}).geturl() response requests.get(transformed_url, headersheaders, timeouttimeout) return response这样从检测到请求用的是同一个 IPDNS 重绑定攻击就被绕开了。第三层端口限制就算 IP 是公网的理论上也可以访问到内网端口映射到公网的服务。更常见的情况是Agent 被诱导去访问公网服务器上的6379、3306、27017等端口这在某些环境下还是能探测到敏感服务。我维护了一个危险端口列表22、3306、6379、27017、9200、11211、5000部分环境中的调试端口等。默认情况下只允许访问 80、443、8080、8000、3000 这类常规 Web 端口。当然这是可配置的但建议默认收紧。第四层重定向跟随控制requests默认会跟随重定向这就带来了一个绕过风险攻击者用一个公网 URL 通过检测但这个 URL 会 302 重定向到内网地址。class RedirectBlock(requests.HTTPAdapter): def send(self, request, **kwargs): response super().send(request, **kwargs) while response.is_redirect: next_url response.headers[Location] parsed urlparse(next_url) ip socket.gethostbyname(parsed.hostname) if is_private_ip(ip): raise SSRFError(fRedirect blocked to private: {next_url}) # 继续解析重定向... return response这四层做完Agent 发起 HTTP 请求的基本安全边界就立住了。3.3 代理逃逸检测两个容易忽视的洞很多开发者做了上面四层之后觉得已经稳了。但实际上还有个绕法——通过 HTTP 代理。如果 Agent 环境配置了全局代理那么你检测到的 IP 是代理服务器的 IP而代理服务器才是真正发起请求的地方。请求到代理服务器之后再由代理转发相当于在检测环节和实际请求环节之间插了一层。有一种情况更隐蔽内网环境通常有 HTTP 代理攻击者可以利用配置在环境变量里的HTTP_PROXY/HTTPS_PROXY达到某种效果。严格来看如果目标是防 SSRF上述四种检测加上不跟随重定向到内网已经覆盖绝大多数打了 CTF 的人能想到的招。真要追到极致还应该支持关闭代理session requests.Session() session.trust_env False # 禁用环境变量中的代理配置 session.mount(http://, RedirectBlock()) session.mount(https://, RedirectBlock())trust_env False这个参数天然就是干这个的。我自己曾经在这个坑上吃过亏——本地联调的时候HTTP_PROXY指向了内网代理Agent 请求外网图片时先经过代理又跳内网直接打穿了校验。加了这一行之后就好很多了。另外IPv6 场景也得补上。很多人只过滤了 IPv4 的私网地址但攻击者完全可以用 IPv6 链路本地地址fe80::访问同一网段的服务或者用 IPv6 映射 IPv4 的地址::ffff:127.0.0.1。建议在实现里把ipaddress库的判断逻辑同时覆盖 IPv4 和 IPv6。3.4 CTF 实战视角SSRF 打 Redis 的经典套路SSRF 打 Redis 可以说是 CTF Web 方向的一道经典大题。攻击思路简单总结是目标服务存在 SSRF可以访问内网内网某个机器上开着未授权访问的 Redis6379 端口利用 Redis 的写文件能力往目标机器的 web 目录写 webshell或者写 SSH 公钥实现登录。在 Agent 场景里如果 Agent 能被诱导去请求gopher://有些框架允许非 HTTP 协议攻击者就能直接构造 Redis 协议数据攻破内网。这也就是我在 3.2 里把gopher://协议直接禁掉的直接原因——只允许 HTTP(S) 可能不够优雅但确实能挡掉这一类攻击。如果必须允许更多协议那协议解析和端口访问控制要做得比我上面实现的更细——比如只允许请求 Redis 的只读命令禁止CONFIG、EVAL等危险命令。不过在 Agent 应用里这种需求几乎不存在最省事也最安全的方案就是一刀切。4. 响应脱敏与校验位数据出去前的最后一道门4.1 为什么要做响应脱敏文件访问和 SSRF 防护解决的是“Agent 能不能拿到数据”的问题但 Agent 拿到数据之后还有个更隐蔽的问题——它会怎么处理和输出这些数据。LLM 天然是个信息聚合和复述器。你给它一份包含手机号的 CSV 让它做数据汇总它完全可能在回复里直接列出完整手机号你让它总结一段包含身份证号的日志它也极有可能原样输出。如果用户的提问本身就包含敏感信息模型还可能把 A 用户的信息复述给 B 用户如果共享了上下文或会话复用。响应脱敏就是解决这个问题的在 Agent 把最终结果输出给调用方之前先把敏感信息过滤掉。4.2 三类脱敏策略掩码、模糊化、实体替换我的实现里把脱敏策略分成三类按场景选择掩码处理保留部分信息其余打星号。手机号保留前 3 后 4身份证保留前 4 后 4银行卡保留前 6 后 4。这种方式适合既要展示部分信息用于校验、又不能完全暴露的场景。import re def mask_phone(text): pattern re.compile(r(?\d{3})\d{4}(?\d{4})) return pattern.sub(****, text) def mask_id_card(text): pattern re.compile(r(?\w{4})\w{10}(?\w{4})) return pattern.sub(**********, text)模糊化处理把精确值替换为区间或类别。比如把“月收入 23800 元”替换为“月收入2万-3万区间”把“体重 76.3kg”替换为“体重75-80kg”。这种方式适合数据分析场景保留统计价值的同时隐藏精确值。实体替换用占位符替换识别出的实体。比如把真实姓名替换为“[姓名]”把邮箱替换为“[邮箱]”。这种方式适合日志分析和调试场景保证格式完整同时不泄露具体内容。实体识别如果只用正则效果会有限。中文姓名、复杂地址这类实体建议接入一个轻量级 NER 模型。我用过LAC百度开源和spaCy实际效果都还可以。如果不想引入额外模型基于词典的匹配加正则兜底也能覆盖大部分场景。4.3 校验位怎么防止响应被“顺手改掉”“校验位”这个概念类似于网络传输里的奇偶校验或者金融卡号的 Luhn 算法——在原始数据之外加一段派生信息用于验证数据完整性。Agent 场景里做校验位背景是这样的Agent 的输出可能经过多个环节传递Agent 核心 → 工具层 → 缓存 → 前端展示。任何一个环节如果做了截断、篡改、缓存污染下游拿到的数据和 Agent 实际生成的都可能不一致。更严重的是在共享 Agent 或者多人共用的系统里响应内容如果被注入或篡改可能带来安全风险。我的做法是对 Agent 的最终响应内容计算一个哈希值拼在响应结构体里。下游收到响应后重新计算哈希不匹配就说明数据被改过需要预警或丢弃。import hashlib import hmac secret_key byour-secret-key def calc_checksum(content: str) - str: 基于内容 密钥计算校验位 return hmac.new(secret_key, content.encode(utf-8), hashlib.sha256).hexdigest()[:8] def package_response(content: str, session_id: str): checksum calc_checksum(content session_id) return { content: content, checksum: checksum, session_id: session_id, } def verify_response(response) - bool: expected calc_checksum(response[content] response[session_id]) return hmac.compare_digest(expected, response[checksum])几个关键设计决策用 HMAC 而不是普通哈希普通 SHA-256 只能防意外篡改防不了恶意篡改。HMAC 带密钥攻击者没法在不知道密钥的情况下算出一个合法的校验位。加盐session_id把会话 ID 拼进校验内容同一段内容在不同会话里校验位不同防止跨会话的重放。校验位放在响应结构体的外层字段不放 content 里面避免污染内容本身。截断到 8 位校验位太长会增加传输开销8 位十六进制32 位熵足够检测意外篡改。如果要防恶意篡改建议至少 16 位以上或者干脆全量 SHA-256。线下实际的防篡改能力取决于密钥管理。如果你的密钥放在前端或者 Agent 进程内那么前端用户可以逆向出密钥然后伪造校验位。所以校验位防护的核心在于——密钥必须存放在攻击者不可读的地方。在服务端场景这是一个立刻可以落地的防护在纯前端场景它更像一个约定聊胜于无。4.4 三层脱敏场景输入侧、输出侧、日志侧响应脱敏不能只做最终输出三个环节都要覆盖输入侧Agent 读取文件、获取工具结果时先脱敏一次。防止敏感数据进入上下文后被模型“记住”并在后续轮次中泄露。这一步也减少了模型隐式记忆带来的数据留存问题。输出侧Agent 生成最终回复后再做一次规则检测和脱敏。这是兜底防止模型在推理过程中“创造性”地拼出了新的敏感信息。日志侧所有带有敏感信息的请求/响应日志落盘前统一走脱敏管道。这一步最容易被忽略——攻击者可能攻击不了 Agent但能攻击日志系统。日志里的对话记录往往是敏感信息的富矿。一个从实战中总结出来的小细节脱敏顺序上先做实体识别再做正则匹配最后做自定义关键词过滤。因为实体识别可能把“张三”识别成人名但漏掉“张三丰”正则能抓手机号、身份证这类强模式自定义关键词则可以补充业务场景里的特殊词汇比如内部项目代号、客户名。4.5 校验位的几种实际应用场景校验位除了防篡改还有个实用的场景——感知上游是否拿到了完整响应。我做 Agent 的时候遇到过一个问题Agent 工具返回的内容太长被传输层截断成了不完整的 JSON下游解析时报错却找不到原因。后来干脆在工具返回结构里也加了长度和校验位下游解析时先校验长度不一致直接报“响应不完整”排查效率立刻上来了。另外一个场景是Agent 输出的缓存策略。如果响应被缓存复用那么计算校验位时把“上下文指纹”比如用户问题、当前时间窗口一起算进去就能避免把过期响应发给用户。这样虽然不如完整安全评估全面但成本低、效果好适合快速落地。5. 实战把三套护栏整合进 Agent 框架5.1 整体架构三层过滤器模式我最终落地的结构是这样的用户输入 → 工具调用层 ├── 文件访问控制器黑名单 → 白名单 → 授予访问 ├── HTTP 请求控制器协议 → DNS/IP → 端口 → 重定向 └── 输出脱敏管道实体识别 → 正则掩码 → 关键词过滤 → 校验位 ↓ 最终响应给用户关键设计是这三层防护互相独立、串联执行。文件访问控制器管不了 HTTP 请求HTTP 控制器管不了输出内容但每个环节的输出都给下游一个“已校验”的上下文标记。如果任何一环拒绝了请求直接抛出让 Agent 能理解的错误信息而不是静默丢弃。5.2 让 Agent 能理解“为什么被拒”在 Agent 系统里安全策略要生效不能只是“拒绝”还得让 Agent能理解为什么被拒这样它才能调整自己的行为。否则会出现很尴尬的场面Agent 尝试读/etc/passwd被拦了然后它又尝试读/etc/Password、/ETC//passwd……来回试五六次。我的做法是安全组件抛出的错误信息不只是“Permission denied”而是带上规则类别和原因[访问被拒绝] 文件 /etc/passwd 命中了黑名单规则系统关键文件受保护不可读取。请更换路径。这样模型能理解边界下次就直接换思路了。比直接报“403 Forbidden”然后看着 Agent 撞墙重试要高效得多。5.3 具体代码整合示例以 LangChain 风格为例如果用的是 LangChain直接把安全控制器包成 Tool 装饰器即可class SafeTool: def __init__(self, tool_name, func, access_controller, ssrf_controller, auditor): self.tool_name tool_name self.func func self.access_controller access_controller self.ssrf_controller ssrf_controller self.auditor auditor def invoke(self, args): # 参数中可以包含 file_path 或 url 字段 if file_path in args: if not self.access_controller.check_read(args[file_path]): self.auditor.log_rejected(file, args[file_path]) return 错误文件访问被安全策略拒绝请检查路径是否正确或申请授权。 if url in args: if not self.ssrf_controller.check_url(args[url]): self.auditor.log_rejected(url, args[url]) return 错误URL 被安全策略拒绝请检查地址是否合法。 result self.func(**args) return result这里的核心是安全检查在 Tool 执行之前做而不是之后。如果放在之后Agent 已经把敏感数据读进上下文了拒绝输出也只是亡羊补牢。轻量、快速、无感知。5.4 审计日志安全护栏的“摄像头”所有拦截事件都应该落审计日志。我自己项目的审计日志字段如下字段示例用途timestamp2024-01-15 10:24:11事件发生时间session_ida8f3-7e2c-4b91关联具体会话agent_namefile_reader是哪个工具触发的actionfile_read / http_request具体动作target/etc/passwd访问目标rule_hitblacklist_etc_passwd命中了哪条规则decisiondeny最终决策llm_message“我需要读取系统密码文件来分析安全状况”模型当时的意图如有审计日志的价值有两个一是事后追溯安全事件发生之后能完整还原 Agent 的“作案过程”二是策略调优依据——如果发现某条规则误杀了太多正常请求就该调整了。6. 常见问题与避坑指南6.1 文件访问控制常见问题问题Agent 读文件报“文件不存在”但文件明明存在。大概率是路径归一化后不在白名单内。比如你授予的是/data/workspace但文件实际在/data/workspace/sub/../../workspace2下归一化后变成了/data/workspace2当然就不在白名单里了。这不是 bug是白名单的正确行为——但也说明你授权时应该用一个更宽泛的目录。问题Windows 路径大小写不敏感Linux 路径大小写敏感。如果你在 Windows 上开发部署到 Linux路径匹配的坑非常多。建议统一用Path.resolve()处理路径并且在做规则匹配前把所有路径统一到小写再对比至少在 Windows 上要这样。6.2 SSRF 防护常见问题问题有些 CDN / 云函数的 IP 会被误判为内网或保留地址。某些云服务商给网站分配的是内网映射后的对外 IP严格来说这不常见但确实有。如果误判率太高可以把这些域名加一个白名单绕过层但要注意白名单绕过必须只针对域名不能针对 IP否则攻击者很容易用同 IP 不同域名绕过。问题两级 DNS 解析第二次解析结果变了。这就是前面说的 DNS 重绑定。处理办法我已经在 3.2 里介绍了用解析后的 IP 直接发请求不要用requests.get(url)拿原始 URL 再次解析。实现上要注意设置Host头否则目标服务器会返回 404 或无法识别虚拟主机。6.3 响应脱敏常见问题问题脱敏脱得太狠把正常信息也掩掉了。比如邮箱脱敏规则把userexample.com中间部分掩掉了导致用户无法正常使用验证码拼出来的“部分邮箱”。这类问题没有完美解只能通过“分类分级”策略缓解——不同场景用不同强度的脱敏规则。管理端日志用强脱敏用户可见的输出用弱脱敏。问题校验位总是对不上排查发现是换行符被改了。Windows 和 Linux 换行符差异\r\nvs\n会导致传输前后内容字节不一致。计算校验位前统一规范化换行符或者校验时做一次兼容处理。这个坑是我真实踩过的——排查了半小时最后发现是环境差异。6.4 高价值避坑清单个人总结安全组件自身的异常不能吞掉。如果安全拦截器自己抛异常了宁可让 Agent 调用失败也不能放行。Fail-closed失败即关闭而不是 fail-open失败即放行。不要在生产环境临时改规则。规则变更要走代码发布流程并且自动跑一遍已有的绕过用例测试。很多时候改一条白名单规则旁边就开了一个新绕过口子。每条安全规则上线前先跑一遍攻击样例集。把常见绕过路径路径穿越、软链接、DNS 重绑定、重定向跳转、双写 URL、unicode 混淆等写成自动化测试用例每改一次规则都跑一遍。我用了一个启发式的思路把以前踩过的坑和网上收集的绕过案例全录进去目前大概维护了 60 多条用例效果不错。Agent 的 Tool 返回信息要脱敏后再喂给模型。很多人只做最终输出的脱敏但问题在于如果敏感信息进入了模型上下文它可能通过隐含推理在后续轮次中泄露。上下文里没有敏感信息输出自然安全得多。7. 扩展思考AI Agent 安全的发展趋势做 Agent 越久越觉得安全不是加几个拦截器就能一劳永逸的。我把自己的实践和相关观察整理了一下几个方向值得持续关注更细粒度的权限模型。目前的三级文件访问本质上还是静态的。Agent 场景理想的方案是基于任务意图的动态授权——Agent 自己“计划”好要用什么工具、访问什么资源再由安全模块做一次“意图审核”后动态下发最小权限。这个方向有一些框架在演进包括一些大厂开源的 Agent 权限控制系统但成熟度还参差不齐。更智能的 SSRF 防护。域名信誉库、动态威胁情报接入后Agent 的 HTTP 请求拦截可以从“规则匹配”升级为“风险评分”。如果访问的域名是已知的钓鱼或恶意域名直接拦掉如果是新注册域名降级权限或强制人工确认。针对提示词注入的响应层防御。现在 Agent 安全最大的黑天鹅不是文件/网络/数据这三类传统问题而是prompt injection提示词注入——攻击者把恶意指令藏在网页、文档、邮件里诱导 Agent 执行非预期操作。响应脱敏和校验位能挡住一部分“数据出库”但挡不住“指令注入”。目前没有银弹常见的缓解手段包括严格隔离外部内容与系统指令、对工具调用的置信度打分、关键操作二次人工确认。回到我这次分享的三件套——它们不是完备的安全体系但把底层的“文件和网络访问”与“数据出库”两条主干线堵上了。对于大多数 Agent 项目这已经足够撑起第一道防线。往上层走权限模型、威胁情报、prompt 注入检测是接下来的仗我还在持续摸索中。一个好的启发是Agent 的安全设计不能脱离 Agent 的自主性来谈。目标不是做一只毫无行动能力的笼中鸟而是让它在一个清晰边界内自由探索——就像一位经验丰富的安全工程师知道哪些楼能进哪些门不该推开。