1. 威胁模型变了:AI编码代理真正让人睡不着觉的,是"上下文越界"
过去一年,我所在的技术团队陆续把 AI 编码代理接进了日常开发流。最开始大家都很兴奋,代码补全、自动重构、跨模块排查问题,效率确实肉眼可见地提升。但等用户数上来、代理的权限放大之后,我意识到一个比"模型回答得对不对"更棘手的问题:代理在推理时到底接触到了哪些信息?
举个我亲历的例子。团队里有人让代理帮忙排查一个线上接口超时问题,代理按照惯例读了config/目录里的环境配置、翻了最近两周的 git 提交记录,还把 CI 构建日志流拉出来分析。半小时后问题解决了,但我做代码审查时发现,代理写进 PR 描述里的调试结论,居然带着测试环境的数据库连接串,甚至把deploy.yml里的一个云服务访问令牌原样复制到了评论里。没造成事故,但那一刻我浑身发冷——代理自己根本意识不到它在泄露什么。
这个场景折射出一个新的安全现实:当我们讨论 AI 编码代理的安全时,讨论重心已经不再是模型层面会不会胡说八道,而是它运行时所处的上下文范围到底有多大,以及这些上下文在流动、拼接、沉淀的过程中,机密信息是否被无感带出。
1.1 传统软件供应链安全解决不了这个问题
过去做软件安全,大家习惯盯这三件事:依赖库有没有已知漏洞、制品有没有被投毒、部署管道的凭证有没有被窃取。应对手段也很成熟,SBOM、制品签名、密钥管理服务,一套组合拳下来覆盖得七七八八。
但 AI 编码代理不是传统软件组件。它不是一个跑完就退出的构建步骤,而是一个长时间驻留、能主动调用工具、能自由读写代码库和工作流数据的智能体。它的输入极广——代码文件、目录结构、git 历史、PR 评论、issue 描述、构建日志、环境变量、shell 历史,几乎整个研发过程中的文本信息都会被纳入它的视野;它的输出也很多样——代码补丁、PR 总结、命令参数、对话回答,任何一段输出都可能成为机密信息的出口。
传统的供应链安全工具很难感知这种"代理级"的数据流动。它们能告诉你哪个依赖包有 CVE,但看不到代理在一个 prompt 周期内把哪几个文件拼进了上下文。威胁模型变了,需要的边界机制也变了。
1.2 "上下文边界"到底是什么
我在内部推动这件事时,第一件事就是和大家对齐概念。上下文边界(Context Boundary),指的是 AI 代理在一次或一组任务中,系统允许它观察、读取、引用的信息范围,以及它能够触发、执行、写入的动作范围。
它不是防火墙,也不是单纯的网络隔离——因为代理和开发者经常跑在同一台开发机或同一个内部网络里,物理边界的意义有限。真正有效的边界,是把"数据可见性"与"动作授权"在逻辑上切开:
- 数据可见性:代理能读到哪些文件、哪些元数据、哪些历史记录;
- 动作授权:代理能执行哪些命令、调用哪些 API、修改哪些资源。
理想状态下,这两个维度应当分开控制。但绝大多数团队的现状是:代理拿到一把"仓库只读令牌",于是整个仓库(包括.env样例、部署脚本、本地配置)都亮在它面前。权限给得越宽,上下文就越大,机密的暴露面也就越大。
所以,我提出的核心观点是:AI 编码代理上生产之前,必须先给它画一条机密安全的上下文边界。这条边界决定了它能看什么、能摸什么,以及最重要的一点——它看不到什么。
2. 机密安全不等于密钥管理:代理时代要守住的是语义上下文
聊"机密安全",不少人第一反应是密钥管理:把 token、密码、证书放进保险箱,应用运行时到保险箱里取。这个思路本身没错,但对 AI 编码代理来说,只做密钥托管远远不够。
原因在于,代理的上下文容器里存的不是一份份凭证,而是一大堆承载机密语义的文本。比如:
config/secrets.yml里有加密后的 token,这可能不算直接泄露;- 但
README.md里写了一句"线上环境密钥由admin@example.com在 KMS 中管理"——这句话本身不包含密钥,却把寻密路径告诉了代理; - 更危险的是
docker-compose.override.yml里直接写了测试库密码,或者.env.example里保留了真实的云服务 access key 前缀。
这些内容在密钥管理服务里可能不算"密钥",但对代理来说,它们都是上下文的一部分,代理一旦读到,就相当于把它放进了自己的推理记忆。后续任何生成任务——写文档、补注释、生成配置——都可能把这段记忆再拼进输出里。
2.1 机密安全的三层递进:发现、策略、强制
我在部署实践里,把"机密安全的上下文边界"拆成三个递进动作,代理接入层按这个顺序一步步做:
第一层,发现(Discovery):系统性地扫描可能进入代理上下文的所有信息源,不只是代码仓库当前分支,还包括 git 历史、提交备注、issue、工单、构建日志、容器镜像环境变量。把其中疑似包含密钥、口令、个人数据、内部系统地址的内容标记出来。
第二层,策略(Policy):基于标记结果,制定"什么能进代理上下文、什么需要额外审批才能进"的策略。策略要按目录、文件类型、内容模式三个维度交叉定义,而不是简单地"全仓库可读"。
第三层,强制(Enforcement):把策略嵌入到代理启动、上下文拼装、工具调用的全过程。凡是策略判定不该进上下文的,物理上不让它出现——不是在对话里提醒一句"请勿泄露",而是让代理在读取阶段就得不到这些内容。
2.2 一个便于理解的生活类比
打个比方,一个住家管家帮你打理家务。你不需要把保险箱密码写在便利贴上贴冰箱,也不希望管家整理书桌时翻到你的体检报告。你真正想要的是:管家可以打扫客厅和厨房,但书房第二个抽屉是锁着的,保险箱是锁着的,衣帽间的门是关着的。这个"锁"和"门",就是边界。光跟管家说"别乱翻东西"没用,得靠物理锁和房间隔断来兜底。
代理也一样。模型能力再强,也不该依赖"它的道德感"来守住机密,而要靠机制上就不让它看见。这就是机密安全的语义化落地——从管密钥,变成管上下文里承载机密的文本。
3. 上下文边界应该画在哪四层:账号、权限、记忆、工具连接
既然要建边界,就得画清楚边界线画在哪。我实践下来,至少要在四个层面同时画线,只画一条往往会被绕过去。
3.1 账号角色边界:给代理一个"最小身份的孤儿账号"
很多团队刚开始接入代理时,直接让代理使用开发者的个人账号去读写仓库、调用云服务。这个做法看起来方便,实际隐患很大:代理的行为和个人身份绑在一起,审计分不清是人在操作还是代理在操作;而且个人账号往往有团队角色赋予的宽泛权限,代理拿到手,上下文范围立刻被放大到整个人在组织内的权限总和。
我的建议是:给代理单独创建服务账号,权限严格对标"执行当前任务所需的最小集"。比如只允许读某些代码目录、只允许在限定的分支上提 PR、只允许调用白名单内的云 API,而且这个账号不能有创建新凭证、修改成员权限等管理类操作。
账号边界搞定了,后面所有的策略和审计才有干净的底座。没有独立的代理身份,你连"代理到底碰过哪些文件"都说不清楚。
3.2 权限边界:用路径粒度代替全库授权
仓库里不是所有内容都该成为代理的上下文。以我现在的团队为例,我们把仓库分成三类区域:
| 区域类型 | 示例目录 | 代理默认访问策略 |
|---|---|---|
| 可安全读取区 | src/、tests/、docs/、examples/ | 允许读取,无需审批 |
| 受限读取区 | deploy/、.github/workflows/、infra/ | 允许读取,但需触发审计,并做内容擦除 |
| 禁止进入区 | .env、secrets/、private/、带密钥的 fixture 文件 | 默认禁止,除非显式解锁 |
这个分层不是凭感觉定的,而是依据内容是否可能包含机密语义来划分。infra/里一个看似无害的k8s-config.yaml,可能指定了内部服务名、命名空间、副本数,这些信息对代理定位问题时有用,但对防御敏感信息流出,就要打上更高的标记。
关键动作是:把目录分类映射成代理读取时的路径规则,并且这个规则要作用在存储层或中间层,而不是靠对话提示。换句话说,代理尝试读取secrets/token.yml时,应该直接得到一个空结果或访问拒绝,而不是通过回调再去咨询大模型"要不要读"。
3.3 会话记忆边界:让一次任务的记忆不被无限复用
AI 编码代理普遍支持多轮会话和长期记忆。这是个好功能,但对机密安全是个潜在的坑。
举个例子:代理在处理任务 A 时,正常读取了生产环境的错误日志,日志里包含一段内部服务的调用地址。任务 A 结束后,这个会话进入了记忆库。一周后你让代理处理任务 B——一个和 A 无关的前端重构任务——代理可能基于记忆库里的旧上下文,在 PR 描述里引用那个内部服务地址。
我的做法是:按任务粒度隔离会话,敏感任务使用"一次性上下文"模式,任务结束后立即丢弃记忆切片;只有经过授权且明确不含敏感内容的会话才允许写入长期记忆。这相当于给代理的"记忆库"也装了一道闸门。
3.4 工具连接边界:所有外部调用走统一网关
现代编码代理几乎都会接外部工具:GitHub API、JIRA、Slack、云服务控制台、代码搜索索引等等。每接一个工具,就多一个数据出入口。如果代理可以直接带着个人凭证去调这些工具的 API,那上下文边界基本形同虚设——它完全可以不走仓库,直接通过工单系统把敏感信息带出来。
我现在的强制要求是:代理的所有外部工具调用,必须经过一个统一代理网关。网关负责三件事:
- 改写凭证:把代理请求中的真实令牌替换成最小权限的临时令牌;
- 记录流量:每次调用的参数、返回值全文落盘,方便事后审计;
- 内容过滤:在返回结果返回给代理之前,先用正则和敏感词库做一轮"读前擦除"。
网关这层是我目前认为性价比最高的一道防线,因为拦截点是唯一的、可控的、可上策略的。
4. 六个落地动作,把"上下文边界"从概念变成配置
概念讲清楚了,接下来是实操。我按照团队实际接入代理的流程,把构建机密安全上下文边界拆成六个可落地的动作,每一步都有对应的工具和配置示例。
4.1 动作一:对代码仓库和历史做全面机密集市扫描
第一步永远是摸清家底。我推荐在仓库 CI 里加入密钥扫描工具,如 Gitleaks 或 TruffleHog。别只扫当前分支,要加上--history参数,把 git 历史里的密钥也翻出来。
# Gitleaks 扫描全量历史 gitleaks detect --source . --log-opts="--all" --report-format json --report-path gitleaks-report.json # TruffleHog 扫描 git 历史 trufflehog git file://. --results=verified,unverified --json > trufflehog-results.json扫描结果会标记出疑似密钥的位置、类型、所属文件。我通常会再做一步人工分类:哪些是真实的仍在使用,哪些是历史遗留,哪些是测试样本。分类结果直接决定后续策略文件的编写依据。
4.2 动作二:把目录策略写进代理的配置文件
大多数编码代理框架都支持自定义文件读取规则,本质是给代理设定一个类似"允许列表"和"拒绝列表"的机制。以我自研的内部封装为例,策略文件长这样(伪配置):
context_boundary: version: "1.0" read_allow: - "src/**" - "tests/**" - "docs/**" - "examples/**" read_deny: - "**/.env" - "**/secrets/**" - "deploy/*.yml" - "**/*.pem" - "**/id_rsa*" tool_allow: - "github:read_only" - "jira:read_only" - "cloud:metadata" tool_deny: - "cloud:write" - "slack:post"这个文件要在代理启动时强制加载,并且不能被代理自行修改。配置加载完成后,我还要做一次端到端验证:故意让代理尝试读取被拒绝的文件,确认它拿不到真实内容。
4.3 动作三:在上下文拼装层做"读前擦除"
即使有目录策略,仍会有漏网之鱼——比如一个合规文件里偶尔夹带一段密钥串。所以我额外在代理的上下文拼装层加了一道读前擦除(Redaction Before Context)机制。
具体实现思路是:拦截代理准备读取文件内容时的返回流,先跑一组规则:
import re SENSITIVE_PATTERNS = [ re.compile(r'AKIA[0-9A-Z]{16}'), # AWS Access Key re.compile(r'ghp_[0-9A-Za-z]{36}'), # GitHub Token re.compile(r'-----BEGIN (RSA|EC) PRIVATE KEY-----'), re.compile(r'password\s*=\s*["\']?[^"\'\s]+'), re.compile(r'eyJhbGciOiJIUzI1NiJ9\S+'), # JWT-like token ] def redact_content(raw: str, extra_patterns: list = None) -> str: patterns = SENSITIVE_PATTERNS + (extra_patterns or []) for pat in patterns: raw = pat.sub('[REDACTED]', raw) return raw凡是命中规则的字符串,在内容进入代理上下文之前就被替换成[REDACTED]占位符。这样代理能知道"这里有段凭证类内容",但读不到原文,不会把它引用到后续输出中。
4.4 动作四:外部工具令牌全部换成临时动态凭证
代理调用外部服务时,不要让它持有长期有效的静态令牌。我强烈建议接入动态凭证机制:代理每次需要调用外部 API 时,由网关向密钥管理服务申请一个短期令牌,有效期控制在 10~15 分钟,使用范围被 ACL 限定。
# 示例:获取一个临时云访问凭证 aws sts get-session-token --duration-seconds 900 --query 'Credentials'动态令牌的好处不在于比静态令牌更"安全",而在于可溯源、可快速销毁。如果代理的某个会话被怀疑泄露了凭证,你可以在几分钟内吊销该动态令牌,而不是惊慌失措地去改一个已泄露的长期密钥。
4.5 动作五:会话日志在归档前自动清洗
代理的工作日志是重要的审计依据,但它本身也是机密信息的集散地——prompt 里可能包含代码片段、配置内容、甚至密钥。我落实了一条规矩:任何会话日志在落盘之前必须做脱敏清洗。
清洗流程分两步:第一步套用与动作三相同的正则规则,把日志中的疑似密钥打码;第二步根据目录策略,把被判定为"禁止进入区"文件的内容整段替换为摘要。清洗完成后,日志才允许进入检索和审计系统。
4.6 动作六:敏感文件被访问时实时告警
最后一道动作是建立实时告警。任何代理对"受限读取区"或"禁止进入区"路径的访问尝试,都应该触发审计事件。我现在的告警维度包括:
- 访问了
deploy/下的哪个文件; - 尝试读取
.env多少次(哪怕被策略拦截); - 代理在对话中是否生成了疑似密钥模式的文本;
- 外部工具调用中是否出现未在白名单内的 API。
告警不一定要立刻阻断,但一定要让安全团队"看得见"。很多机密泄露的初始征兆,恰恰是一次看似无害的越界读取。
5. 我踩过的三个坑,以及最后沉淀下来的边界原则
再好的设计,落地时也会遇到意外。我把这一路上踩过的坑挑三个最典型的写出来,希望能帮你避免重复交学费。
5.1 坑一:只扫了当前工作区,没扫 git 历史
第一次部署时,我自信满满地在 CI 里加上了 Gitleaks 扫描,只扫了当前分支的文件。结果没过两周,就有同事报告说代理在分析一个旧功能时,从git log的输出里读到了两年前的 AWS Access Key,还把它拼进了注释里。
根因:仓库当前文件虽然干净了,但 git 历史里仍然沉淀着曾经提交过的密钥。代理在排查问题时,有权读取git log的输出——对代理来说,历史提交信息也是合法上下文。
修正办法:给代理的 git 读取权限加规则,禁止它在上下文中包含超过 N 条的历史 commit 信息,同时对git log输出做和文件内容一样的擦除处理。仓库侧的历史密钥当然也要清理,但那是另一个持续的工程,不能指望一次做完。
5.2 坑二:擦除规则太激进,代理"失明"了
为了安全,我把擦除规则写得特别严格:只要一行文本里出现了password、secret、token等关键词,就把整行替换成[REDACTED]。结果代理在处理一个auth.py文件时,把get_token_from_cache这个函数名也当成敏感信息擦掉了,导致它在生成测试用例时根本不知道该模块提供了什么接口,输出质量断崖式下降。
教训:擦除规则要有"上下文感知",至少要做模式匹配,而不是粗暴的关键词命中。awkward_password = "123456"要擦,但def password_check()这种函数定义显然不能擦。我后来加了白名单机制:常见编程语言的关键词、函数命名模式、文档注释结构统统进白名单,先过白名单再过黑名单。
5.3 坑三:边界设得太死,代理变成了只会说"做不到"的废物
过度收紧也有副作用。有一段时间,我把"受限读取区"的访问审批流程设得特别繁琐,每个目录的读取都要人工批。结果团队抱怨代理像被绑住了手脚,排查问题只会在报错日志里打转,连"去看一下部署配置里的超时时间"这种本来很容易的事都做不了。
反思:上下文边界的核心目标不是"让代理什么都看不到",而是"让该看到的看得到、不该看到的看不到",边界不能替代代理完成判断。后来我把策略改成按任务的敏感等级动态放宽:任务涉及部署排障时,自动给代理加一个短期(30 分钟)的受限区读取授权,同时全程审计;任务只是普通的前端样式调整,则严格按基线策略执行。
5.4 我最后的边界原则
踩了这些坑之后,我的原则变成了三句话:
第一,对代理的信任,要像对待刚入职的临时员工。能力可以强,权限必须最小化;所有机密都不在它的默认视野里,需要时再单独打开。
第二,边界越多,代理反而越专注。很多人担心边界会拖累效率,但实际情况相反——代理拿到的上下文更干净,干扰更少,生成质量反而更稳定。它不用从一千个文件中猜哪些是可信来源,因为策略已经替它划好了圈子。
第三,机密安全是工程问题,不是提示词问题。别指望在 system prompt 里写"不要泄露密钥"就万事大吉,要在存储层、读取层、拼装层、调用层分别埋点,让机密在机制层面就不存在进入上下文的通道。
6. 下一步:把上下文边界扩展到模型推理之外
写完上面的落地动作,你可能会觉得边界已经够多了。但我的实际体会是,上下文边界不能只停留在"代理读取数据"这个环节,还得延伸到模型推理之外的两处:代理产出的部署链路,以及代理与同事协作时的信息传播。
先说部署链路。代理不只是写代码,现在很多代理还能直接调起构建任务、应用部署、回滚操作。这就意味着,一个拥有 CI 触发权限的代理,如果上下文边界把某个基础设施配置项错误地放进了它的视野,它可能在生成部署脚本时把这个配置硬编码进去,制造一个带着机密的新制品。所以,代理能触发的构建任务本身也要隔离开——测试环境的构建用一套临时凭证,生产环境的构建必须由人手动批准,代理只能提交请求,不能直接执行。
再说协作传播。代理经常会把会话结论同步到项目群、工单、评论区。它的"输出"本身也是一种上下文扩散。我在内部规定:代理对外同步信息之前,要再过一遍脱敏管线。这不是简单地扫密钥字符串,而是检查输出里是否包含内部服务名、非公开接口路径、错误日志中的环境变量等敏感语义。这个检查我目前用规则加人工抽检结合的方式做,还没有做到完全自动化,但方向是对的。
归根结底,AI 编码代理的机密安全问题,本质是一个工程化问题,而工程化问题的答案永远不在单点,而在系统性的边界设计。划清边界,既是对代码库和业务机密的保护,也是对代理自身效率的保护——给它一个干净、可控、可审计的上下文,它才能成为真正可信赖的编码伙伴。