我做了快十年开发和运维,最近两年又把大部分精力投到AI应用上,结果发现一个让我特别头疼的现象:很多团队花大价钱买防火墙、上WAF、做渗透测试,却把最要命的API密钥、数据库密码、云服务凭证当成明文贴在代码里。尤其是AI应用,简直就是凭证泄漏的重灾区。标题里说“99%”可能有点夸张,但我真的在不少客户的仓库里见过OpenAI的Key、向量数据库的连接串、对象存储的Secret直接躺在Git提交历史里,权限还开到最大。这篇文章我就想认认真真聊清楚一件事:AI应用里的凭证管理到底哪里出了问题、会怎么裸奔、又该怎么系统性地收口。
这个话题适合谁看呢?正在用LangChain、LlamaIndex、Coze这类框架做AI应用的人,公司在给大模型应用做安全评审的技术负责人,以及那些把“快速上线Demo”当成第一优先级、还没考虑过密钥泄漏后果的独立开发者。涉及的也不只是“把Key放到环境变量里”这种入门动作,还包括怎么扫描存量泄漏、怎么清理Git历史、怎么用密钥管理服务做动态取用,以及怎么把检查机制固化到CI流程里。看完之后,你至少能给自己手上正在跑的项目做一次全面体检。
1. 先聊清楚:AI应用里的“凭证裸奔”到底是怎么发生的
1.1 一个被讲烂但永远有人踩的词:凭证管理不当
凭证(Credential)不光是密码,更泛指一切能让程序“证明自己有权访问某资源”的凭据:API Key、Access Token、服务账号的JSON密钥、数据库连接字符串里带的用户名密码、云平台的临时安全凭证,甚至某些内部系统的签名密钥。凭证管理不当,最常见的形式就是把它们当成普通配置,硬编码在源码或配置文件里,完事还随手把仓库设成公开。
放到AI应用的语境里,这个问题的放大效应特别明显。传统Web应用可能只需要一把数据库密码,顶多加一个支付宝/微信支付密钥。但一个典型的AI应用,至少要具备以下几类凭证:调用大模型服务的API Key(可能是OpenAI、Claude、国产模型多路供应商),向量数据库或对象存储的连接凭证,私有化知识库或业务系统的访问令牌,还有为了采集训练数据、调用第三方数据服务而申请的各类Token。凭证数量一多,管不过来的团队就会选择图省事,全部塞进一份.env文件、一份application.yml,或者更离谱,直接写成示例代码里的字符串常量。
对于AI应用而言,凭证泄漏的后果往往不只是数据泄露这么简单。AI应用通常会串联很多自动化动作,比如调用外部工具、读写文件、发邮件、付钱下单之类的Agent能力。一旦密钥被拿走,攻击者就相当于获得了这套自动化流程的“操作权”,可以直接指挥你的AI替你花钱、替你发消息、替你删数据。这不是普通Web漏洞可以比拟的。
1.2 为什么偏偏是AI项目最容易“裸奔”
我复盘过不少AI项目,发现它们普遍存在三个特征:迭代速度极快、复制粘贴比例极高、安全review极度缺失。
迭代速度快,意味着开发者会优先把功能跑通,不会考虑“这个Key能不能写死在代码里”这种延迟满足的事。某AI创业团队为了快速验证一个RAG问答效果,直接在Notebook里写完向量化脚本,顺手把OpenAI API Key写死在里面,之后Notebook被分享给外部合作方,Key就这么漏了。我见过不止一次类似的场景。
复制粘贴比例高,是AI开发者的日常。很多人会直接去GitHub上搜项目模板或示例代码,拿到手发现代码里居然带着作者的API Key,就以为这是“可用的默认配置”,连替换都不做。有些Key甚至还能用,因为原作者使用了免费额度的账号,根本不设防。这类泄漏属于“传染式泄漏”,你抄我、我抄他,一条真实的Key可能潜伏在几十个复刻仓库里。
至于安全review缺失,更是常态。传统开发团队好歹有Code Review流程,会有人盯着“你的密码别写到代码里”这件事。但AI项目的Owner往往就是两三个全栈工程师,甚至是一个人从模型选型干到前端部署,整个开发过程没有第二双眼睛盯着凭证。大家默认“能跑就行”,而“能跑”和“安全”在很多AI原型项目里天然冲突。
1.3 先搞懂攻击者拿到凭证后会干什么
很多开发者觉得“就算Key泄漏了,最多被人调用几次,扣点费而已”。我建议你千万别这么想。让我按攻击者的视角给你摆一摆拿到凭证后的操作路径,你大概就知道这事的严重程度了。
第一类路径是直接用你的资源:用你的云账号密钥启动GPU实例去挖矿、用你的对象存储凭证把桶里的训练数据全拖走、用你的模型服务API Key去跑批量任务,刷爆你的账单。第二类路径是横向移动:AI应用很少只有一个凭证,它往往同时持有数据库密码和模型服务密钥。攻击者拿到其中一个,就会以此为跳板去翻配置中心、环境变量、K8s的Secret,尝试拿更多权限。第三类路径最隐蔽也最致命:如果是Agent类应用,凭证往往绑定了“工具调用”权限,攻击者可以用你的身份指挥Agent调用邮件、支付、数据库写入等工具,这个叫凭证接管,本质上比数据库被拖库还要难防范,因为每一次调用看起来都是合法行为。
所以你在评估AI应用的凭证风险时,不要只想着“少一点费用损失”,要想一想“这套凭证能让别人替我做多少事”。想清楚这一点,你就知道为什么凭证管理不能靠自觉,必须靠机制。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 泄漏面盘点:AI应用里的凭证是从哪些渠道漏出去的
我替团队做安全巡检时,一般会按六个泄漏管道逐项排查。每个管道对应不同的泄漏场景,修复手段也完全不一样,所以先把路数分清楚。
2.1 硬编码到代码仓库:最低级但最常见
这是老生常谈,但AI项目里仍然大量存在。形式包括:Python脚本里直接写openai_api_key = "sk-xxx",Jupyter Notebook里写死云数据库连接串,前端代码里嵌了后端接口的认证Token,示例配置文件里留着真实密钥没替换。
我见过最典型的一个案例是某团队用Streamlit快速搭了一个AI对话Demo,为了方便,把OpenAI Key写在一个叫config.py的文件里,然后整个目录推到了GitHub公开仓库。Key直到三个月后收到账单才发现被盗刷。事后检查时发现,那条Key在泄漏后的24小时内就被爬虫捕获并开始调用,但因为用量不大,账单上没有立刻体现异常。
为什么AI开发者特别喜欢把Key硬编码?除了“图省事”,还有一个心理因素是“这不是生产环境,只是一个demo”。但GitHub的代码爬虫不会区分demo和生产,它扫到Key就抓走,然后立刻在暗网上批量验证。这个坑我强调过无数次:凡是被推到远端仓库的代码,就要默认它可能被公开。
2.2 日志与监控链路:AI编排工具的信息“顺风车”
第二个泄漏点特别容易被AI原生产品忽视:大模型调用链路上的日志。很多应用会在调试阶段打印完整的请求体、响应体和中间步骤。问题在于,现在AI应用经常用ReAct模式或Agent模式,模型在推理过程中可能会把某一步获取的凭证信息当作普通文本输出到日志里。
举个例子,一个Agent被赋予了读取数据库Schema的能力。它在执行任务时,内部调用的SQL连接串可能带用户密码。这部分信息一旦被记录进LangSmith或Langfuse这类追踪工具,甚至只是print到控制台,凭证就进了日志系统。很多团队的日志是明文存储且全员可查的,一旦内部账号被攻破,日志就成了凭证金矿。
更麻烦的是,现代AI Agent的思维链输出(Chain of Thought)往往包含很多中间变量。如果程序在设计Prompt时不小心让模型“看到”了连接凭证或者Token,模型输出复盘时可能将其复述出来。这种泄漏在普通应用里根本不存在,却是AI应用独有的一种“非典型泄漏面”。
2.3 AI编程助手与训练语料:新时代的泄漏放大器
现在很多开发者在用AI编程助手写代码。它确实能提升效率,但也带来了一个非常危险的问题:如果编辑器里的代码包含真实密钥,这些代码片段可能会被IDE的补全插件自动发送到模型服务端做上下文分析,等于把凭证主动交到了第三方手里。
如果是开源的、自己部署的代码模型还好,可大多数人用的是云端SaaS服务,这就意味着凭证进了第三方的日志库。这个问题很难完全杜绝,因为你写代码时总会无意中打开一个包含密钥的配置文件。规避办法只能是:本地开发时把真实凭证和代码目录隔离,或者至少不要打开真实的生产配置文件当参考。
另外,训练语料泄漏也很要命。有人会把公司内部代码库直接拿去做模型微调,如果仓库里有硬编码密钥,微调后的模型就可能学会“在回答里生成密钥格式”,不小心把含密钥的代码片段输出给询问者。更极端的情况是模型在生成演示代码时,直接把训练语料里的真实Key当作示例输出来,我之前在某个公开的模型测试里就遇到过这种事。
2.4 公开的示例代码与教学项目:凭证的“一传十”
网上有大量AI应用Demo项目,包括聊天机器人、RAG知识库、Agent工作流等。其中相当一部分项目作者在发布时会做清理,把密钥改成占位符;但也有相当一部分作者没这个习惯。结果就是,我随便搜一个“AI Chatbot + LangChain + Supabase”的教程代码,能在配置文件里翻到作者留下的真实服务地址、匿名Key甚至管理员密码。
很多人会辩解:“这种教学项目里的Key不值钱,没什么权限。”但云服务的Key有时是按项目或按账号维度生成的,哪怕作者只开了最小的权限,泄漏了也等于给了攻击者一个进入账号体系的入口。如果你的项目被fork后又被改造成商业项目,这些Key就成了定时炸弹。
对于这种情况,我建议做AI教学或开源项目分享的人,发布前一定要用工具扫一遍仓库。发布教程时也要刻意用占位符。这一个动作至少可以避免你的读者“顺手抄走一个真Key”。
2.5 模型上下文注入里的凭证泄露
新的攻击链值得单独摆出来:Prompt注入。攻击者通过构造输入,诱导模型输出系统Prompt里的敏感内容,或诱导模型调用某个工具并返回不希望被返回的密钥。这属于非传统泄漏方式,而且很难仅靠“不把凭证写进代码”来规避,因为它发生在运行时。
比如你给AI Agent配置了一个获取用户信息的工具,为了让它“更聪明”,你在系统Prompt中写了一句“你可以用Bearer Token xxx去调用内部用户服务”,模型在执行低权限操作时,可能被恶意用户通过迂回问法套出这段Token。这就是把凭证放到了模型上下文里引发的泄漏。
正确的做法是:运行时凭证只在代码层面做注入,不发到模型上下文;能由执行引擎代管的凭证就不让模型感知。像OpenAI的函数调用设计里,你完全可以让后端代码替Agent完成鉴权,把Token存在内存变量里,而不是写进Prompt。这是新一代AI工程师需要快速建立的“上下文安全边界”意识。
2.6 K8s ConfigMap、CI环境变量等隐式泄漏
最后一个泄漏渠道比较隐蔽:凭证没在源代码里,却静静躺在K8s的ConfigMap、CI流水线的明文环境变量或Docker镜像的环境层中。容器镜像一旦被push到公开Registry,镜像里的环境变量是可以被拉取的。旧ConfigMap的明文备份如果进入了对象存储,也会成为情报来源。
AI应用上云后这类情况尤其突出。很多MLOps平台要求你配置“模型服务密钥”,配置界面可能直接明文展示;CI在跑模型评测时需要用到服务账号,指令里直接拼上Token;训练镜像里为了省事把数据源密码做成了环境变量并打了tag推到公开仓库。这些都是真实发生过的事,绝不是我编的。
到这一步你应该发现了:AI应用凭证泄漏不是单一环节的问题,而是从开发、训练、部署到运行全链路都可能发生。所以下面要聊的检测和修复方案,也不能只盯一个点。
3. 存量代码体检:怎么快速找出正在“裸奔”的凭证
如果你现在接手一个已经跑了好几个月的AI项目,第一件事不是去补规范,而是先把存量泄漏揪出来。这个过程可以比喻成给房子做一次全面的管线检修。你只有知道漏点在哪,才知道接下来堵哪里。
3.1 工具选型:别自己写正则瞎扫,直接上专业扫描器
很多人会尝试自己写正则去匹配sk-或者AKIA之类的关键词,但我劝你省省力气。凭证扫描这件事有成熟的工具,站在巨人的肩膀上效率至少提升十倍。
我常用的工具是Gitleaks和TruffleHog。Gitleaks的特点是轻量、规则丰富,支持自定义规则,可以扫本地目录也可以扫Git历史。TruffleHog更擅长做深度内容检测,它不只是简单匹配密钥格式,还会验证某些密钥是否真实有效。两个工具结合使用效果最好。
针对AI项目,还需要额外扫几类特殊的东西:模型供应商的Api Key(OpenAI的sk-开头、Anthropic的sk-ant-开头等)、HuggingFace的Token(hf_开头)、向量数据库/数据库连接串、云厂商的服务账号JSON。Gitleaks社区规则里已经包含了大量这类模式,但模型供应商的Key更新换代很快,所以强烈建议你针对自家常用的服务商单独写扩展规则。
至于扫描范围,我的习惯是整仓库扫描加全量Git历史扫描。为什么一定要扫Git历史?因为很多开发者发现泄漏后喜欢“直接把这行代码删掉再提交一次”。但Git的历史里永远躺着旧版本,只要攻击者拉取历史提交,一样能拿到那个Key。所以你的扫描器必须能回溯每一个历史版本,而不是只看当前HEAD。
3.2 本地扫描实操:一条命令看清家底
假设你要扫描本地一个名为ai-rag-project的仓库,可以这么干。先安装Gitleaks并用adhoc模式扫描,它能按你当前的目录内容查一遍,不依赖Git历史:
bash复制# 安装(macOS / Linux)
brew install gitleaks
# 或去 GitHub Releases 下载对应平台二进制
# 只扫当前工作区内容
gitleaks dir /path/to/ai-rag-project
# 扫整个Git历史
gitleaks git /path/to/ai-rag-project
扫描结束后,Gitleaks会输出报告,包含发现的密钥类型、文件路径、行号和匹配到的前缀。千万别只关心扫描报告里有多少个“high”,你要做的是把每一个发现都拉出来看一眼,确认它是不是真实凭证、是否仍然有效、有没有权限过大。
除了Gitleaks,我还经常用TruffleHog做二次验证,特别是针对云厂商凭证和数据库连接串的验证:
bash复制# 用 trufflehog 扫 Git 仓库
trufflehog git https://github.com/your-org/your-ai-repo --only-verified
--only-verified的意思是只输出经过真实有效性验证的密钥,这样能过滤掉大量误报。不过注意,这个验证过程会向对应服务商发起认证请求,如果你的凭证本身有效,对方服务商会记录到一次来自你本机的认证尝试。在敏感环境操作前,你需要提前评估是否允许触发这种检查。
3.3 扫描的重点对象:Notebook、配置文件与Docker镜像
本地扫描有一个容易漏掉的对象是Jupyter Notebook文件。.ipynb本质是一个JSON,里面所有输出格都保存为纯文本。有时候你在Notebook里跑了一段带密钥的代码,输出里可能直接渲染出密钥文本,哪怕后面的代码已经把变量删了,输出单元格仍会保留。
所以扫Notebook时,我会直接用jq把.ipynb里的outputs和cell source全部抽出来,再喂给Gitleaks:
bash复制# 把 ipynb 转成纯文本再做扫描
find . -name "*.ipynb" -exec jq -r '.. | strings' {} \; > /tmp/notebooks_dump.txt
gitleaks dir /tmp/notebooks_dump.txt --log-opts=""
有人会觉得麻烦,但这是很多真实泄漏的来源。我在给某客户做扫描时,就是在他们的experiments/目录下的一个Notebook输出里翻到了完整的云数据库密码。
Docker镜像也需要重点关注。镜像分层时会保留构建时写入的环境变量。如果你想查一个已构建镜像里是否藏了密钥,用docker history和docker inspect就能看到明文环境变量。对于已经push到远端仓库的镜像,建议用Skopeo或Crane把镜像拉下来再逐层检查环境变量层。
bash复制# 查看镜像的环境变量层
docker history --no-trunc your-registry/your-ai-image:latest | grep -iE "key|secret|token|password"
这类扫描只要查出一个环境变量里带真Key,基本就能确认镜像需要重新构建并轮换凭证。
3.4 别只扫本地仓库,还有这些地方要同步排查
如果你以为扫描完Git仓库就万事大吉,那就低估了凭证泄漏的隐蔽程度。按我多年的排查经验,除了代码仓库,以下四个地方也必须纳入体检范围。
对象存储桶是第一个需要看的地方。很多AI项目会把配置中心、模型产物的备份放在S3或阿里云OSS上,桶里的文件如果设置成了公共读,扫描器根本不需要任何凭证就能把里面的密钥配置文件拉下来。修复的办法就是用云厂商的桶策略审计工具检查公共访问权限,同时加一条扫描任务,定期检测桶内是否存在包含密钥的敏感文件。
协作工具和文档也是重灾区。飞书、Notion、语雀、Confluence里的AI架构设计文档、API对接说明,经常直接把服务商的Api Key贴在里面方便团队复制。这类泄漏虽然不在代码库,但一旦文档被分享出组织,密钥同样等于公开。我在团队里强推过一条规则:文档里只允许贴密钥的“前四位后四位”用于辨认,完整密钥必须通过团队密码管理器单独分享。
第三方AI应用配置后台同样需要排查。在Coze或Dify这类低代码AI平台上,很多开发者会直接在编排界面里填入大模型密钥或数据库密码。平台自身可能有加密存储,但如果你在配置页面创建了多个Agent,且不同Agent复用了同一个“万能Key”,相当于扩大了泄漏面。建议按Agent的最小权限单独创建服务账号,并定期审查平台上的凭证绑定关系。
最后是源码管理平台的私有仓库权限。攻击者不一定需要仓库公开,只要他能拿到组织里某个成员被泄露的账号,就能通过私有仓库读到所有硬编码密钥。排查方法是审计GitHub/GitLab的成员账号和第三方OAuth应用的权限范围,把不需要代码读取权限的应用全部撤销。
4. 修复实战:从“裸奔”到“穿好衣服”的整改方案
扫描发现问题之后,要立刻进入整改环节。按照风险从高到低、改动从急到缓的顺序,我给你拆成四个步骤:轮换、移除、重构、托管。这四个动作一个不能少。
4.1 紧急止血:发现泄漏后的第一件事是轮换,不是删除
只要确认任何一个凭证曾经被推到过远程仓库、发到过聊天工具,或者出现在日志系统里,你就要默认它已经泄漏。删除提交记录或者改文件权限没有用,因为任何在你删除之前访问过仓库的人都已经拿走了它。
真正有效的手段是立刻在服务商端吊销旧凭证,然后生成新凭证。以OpenAI API Key为例,你要登录后台找到对应的Key,先点撤销。注意操作顺序:先撤销旧的,再创建新的,而不是先建新的再撤销旧的,不然会出现一段“新旧凭证同时有效”的空窗期,反而扩大风险。其他云服务的Access Key处理逻辑也一样:禁用其实是最稳妥的第一步,禁用后观察一段时间,确认业务没有报错再删除,避免删完发现某个没记录到的旧服务还在用它,把整条业务链路打断。
轮换之后还要做一件事:更新所有引用旧凭证的位置。这里我特别提醒一下,如果你之前把凭证存在多个团队成员的.env里,你要想清楚这些人是不是都在用最新的值。建议把“凭证轮换”做成一个全团队同步事件,在群公告里明确标注“某服务旧Key已失效”,避免有人用了半天没排查出来。
4.2 把明文密钥从代码中剥离:引入环境变量与多环境配置
轮换完密钥,如果代码里还是直接写死凭证,那就等于治标不治本。下一步要做的是把所有硬编码的凭证从代码里剥出来,放进环境变量。
这里我展示一下最常见的两层改造。改造前的错误代码长这样:
python复制# 错误示范,千万别学
openai_api_key = "sk-1234567890abcdef"
database_url = "postgresql://admin:mypassword@db-host:5432/ai_vectors"
改造后正确做法是把密钥交给启动进程的环境变量:
python复制import os
openai_api_key = os.getenv("OPENAI_API_KEY")
database_url = os.getenv("DATABASE_URL")
在本地开发时,可以用.env文件管理这些变量,但.env文件必须加入.gitignore,并且提交一份.env.example作为占位模板。很多框架的加载机制是默认读取项目根目录的.env,只要你的.env不会上传到Git,就已经挡住了一半风险。
但是请注意:.env文件在本地开发机上仍然是明文存储,而且团队成员之间经常用网盘或聊天工具互相传.env,这也会造成二次泄漏。所以对真实的生产凭证来说,本地只适合保存“开发环境专用凭证”,生产凭证应该全部放进专门的密钥管理服务里,见后文。
4.3 升级做法:用密钥管理服务(KMS/Vault)管好AI应用凭证
AI应用如果只是单机Demo,环境变量已经够用;但只要涉及多人协作和生产部署,就应该用专业的密钥管理服务。市面上的选择包括云厂商自带的Secrets Manager(如AWS Secrets Manager、阿里云的KMS、腾讯云的凭据管理系统)或开源的HashiCorp Vault。核心思路都一样:让应用在运行时动态地从密钥仓库里取凭证,而不是在构建/部署时把凭证烧进镜像或环境变量里。
我给出一个在Kubernetes中通过Secrets Store CSI驱动注入密钥的例子。它的好处是Pod内部看起来仍然像读文件或读环境变量,但真实凭证并不会出现在部署清单里:
yaml复制apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: ai-app-aws-secrets
spec:
provider: aws
parameters:
objects: |
- objectName: "prod/openai_api_key"
objectType: "secretsmanager"
应用代码侧更简单:从环境变量或指定路径读取到的是已经被拉取好的真实密钥,应用本身不需要感知KMS的调用细节。配合Vault时还可以做动态密钥和租约:密钥能被设置有效期,到期自动失效,应用需要定期刷新,这样即使某一次密钥被拖走,攻击者拿到手的也可能只是一个几分钟后就会过期的临时凭证。
对于普通Python服务,如果你不想引入重量级K8s方案,也可以用boto3或云厂商SDK直接拉取Secret到内存。我给出一个小示例:
python复制import boto3
from botocore.exceptions import ClientError
def get_secret(secret_name: str, region_name: str = "us-east-1") -> str:
session = boto3.session.Session()
client = session.client(
service_name="secretsmanager",
region_name=region_name,
)
try:
response = client.get_secret_value(SecretId=secret_name)
return response["SecretString"]
except ClientError as e:
# 记录日志但不要打印真实密钥
raise RuntimeError(f"Failed to fetch secret {secret_name}: {e.response['Error']['Code']}") from e
有些团队会担心“每次启动都去拉密钥会拖慢速度”,其实这种担心不必要。Secrets Manager有缓存机制,你可以在应用内存里做一层TTL缓存,比如每5分钟重新拉取一次。只要别每次请求都去调用KMS,性能开销可以忽略。
4.4 清理Git历史中的敏感数据
如果凭证已经进入了Git历史,仅靠“删掉当前文件再提交”是无效的。你必须用工具重写历史,让旧提交里的密钥彻底消失。这时我会用git filter-repo,它比BFG Repo-Cleaner功能更全,而且官方推荐用它替代filter-branch。
下面是一段典型的重写流程:
bash复制# 安装 git-filter-repo
pip install git-filter-repo
# 先做一次全量备份,注意要连带所有分支和tags
git clone --mirror https://github.com/your-org/your-ai-repo.git repo-backup.git
# 进入需要清理的仓库
cd your-ai-repo
# 用 replace-text 模式,把所有已知密钥替换成占位符
git filter-repo --replace-text <(echo "sk-1234567890abcdef==>OPENAI_API_KEY_PLACEHOLDER")
# 强制推送到远端(部分平台可能需要临时开启 force push 权限)
git push origin --force --all
git push origin --force --tags
执行完这一步后,你要同步做以下事项:在GitHub/GitLab后台检查是否有旧提交被fork缓存,让所有协作者重新clone仓库并废弃旧副本,另外有条件的话在平台后台触发一次全仓库扫描,确认历史记录已不再包含目标密钥。清完历史后,仍然要记得做轮换。只要密钥曾经被推送过,就做好它可能泄漏然后被某些爬虫数据服务缓存的准备。
4.5 给AI应用代码加一层运行时防护毛刺
解决掉代码里的密钥之后,还可以进一步做“运行时防泄漏”:不直接记录模型请求的headers或body,对输出做脱敏。很多框架会在内部打印请求数据结构,如果不处理,刚才已经从环境变量里安全引入的Token,又会通过日志打出来。
我在基于LangChain的应用里会配置一个回调处理器,把所有可能被打印的敏感字段过一遍脱敏正则:
python复制import re
from langchain.callbacks.base import BaseCallbackHandler
SENSITIVE_PATTERNS = [
re.compile(r"sk-[A-Za-z0-9_-]+"),
re.compile(r"Bearer\s+[A-Za-z0-9._~+/=-]+"),
re.compile(r"(?i)(password|passwd|secret|token)\s*[=:]\s*\S+"),
]
class RedactCallbackHandler(BaseCallbackHandler):
def _redact(self, text: str) -> str:
for pattern in SENSITIVE_PATTERNS:
text = pattern.sub("[REDACTED]", text)
return text
def on_llm_start(self, serialized, prompts, **kwargs):
safe_prompts = [self._redact(p) for p in prompts]
# 然后再交给原生日志逻辑
这段代码解决的问题是:当你的Agent调用大模型时,如果Prompt或响应里不小心包含凭证,日志里也只会出现[REDACTED],不会把真实密钥写进监控。总结一句话——运行时日志是凭证泄漏的最后一环,不堵住这里,前面的整治都可能白费。
5. 把防线固化下来:让“凭证裸奔”从源头断根
做完一次整改还不够,AI项目迭代太快,光靠“下次注意”等于没说。要让凭证安全真正落地,你必须把检查做成自动化、嵌入到团队工作流里,让每次提交、每个PR、每次构建都自动过一遍凭证扫描。
5.1 把密钥扫描装进Pre-commit和CI流水线
最基础的防线是在开发者本地准备提交代码之前,先跑一次pre-commit钩子。Gitleaks官方提供了pre-commit钩子配置,你把下面这段塞进仓库的.pre-commit-config.yaml即可:
yaml复制repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.4
hooks:
- id: gitleaks
这套钩子会在git commit时检查暂存区内的变更,一旦发现疑似密钥就拒绝提交。当然它没法保证每个团队成员都装了pre-commit,所以在服务端CI上再加一道关卡更重要。以GitHub Actions为例:
yaml复制name: secret-scan
on: [push, pull_request]
jobs:
gitleaks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0是关键。如果不拉全历史,扫描器只能看到最新一次提交,拿不到历史泄漏告警。配上这个参数,任何一次推送后CI都会对全量历史做扫描,历史里一旦出现新的危险密钥,构建会直接失败。现在很多云平台也内置了类似的安全扫描,但我会建议你在代码仓库这一层就把它防御住,不要依赖平台侧事后通知。
5.2 设立最小权限原则:让泄漏的凭证没那么“值钱”
即便不幸发生了泄漏,如果凭证权限被限制在最小范围,攻击者的损失不会太严重。所谓最小权限,落到AI应用场景,至少包括以下几个原则。
模型服务密钥按环境拆分。开发、测试、生产分别用完全不同且额度受限的Key,不要图方便一把Key全环境共用。开发Key可以限制单日调用量和可用模型范围,生产Key则只允许绑定生产环境的出口IP。对象存储凭证只给具体前缀路径的读写权限,不要给整桶的管理员权限。给Agent使用的数据库账号,只开放该Agent实际需要的表和SQL操作权限,不直接使用高权限管理账号。如果Agent有外部API调用能力,对应的Token只授权本次任务必需的作用域。
很多应用架构上其实允许你做得更细。例如在调用云端模型服务时后端的Key已写入Header,前端接收到的模型响应则经过API网关过滤。凭证对最终用户完全不可见。你可以将这个设计迁移到自己的AI应用里,让用户无法直接通过前端脚本或网络监控获取后端模型的直连地址或密钥。甚至可以选择接入网关或代理层,让模型API的调用在网关侧完成鉴权和审计。这样即使某个渠道漏了,也漏不到最核心凭证。
5.3 给AI团队建立“凭证卫生”公约
工具和权限的设计之外,团队的“卫生习惯”也一定要跟上。我几乎在每次AI项目复盘里都会强调这几条规则,现在也分享给你,可以直接复制到团队Wiki里作为约定。
规则一:任何密钥不以明文形式出现在聊天软件、文档、PPT、群公告中。想分享密钥时使用团队密码管理器(如1Password、Bitwarden、云厂商的凭据管理)生成分享链接。规则二:所有示例代码里的密钥必须用占位符;开源项目在发布前必须跑一遍Gitleaks。规则三:每个云服务创建密钥时必须填写用途标签和负责人,方便追踪。规则四:收到“仓库泄漏告警”邮件时要当作最高优先级处理,先轮换、再定位、后复盘。规则五:AI编程助手使用时,不要在对话中粘贴包含真实密钥的代码文件,如果必须在上下文里提供,优先做脱敏或引用不含密钥的文件名。
我见过太多团队把这几条当耳旁风,结果在审计时被迫加班排查。这些规则不用多,坚持执行就够管用了。真正让项目保持安全的核心,是让安全行为成为肌肉记忆。
6. 常见问题与排查技巧实录
最后整理几个AI项目凭证管理里我经常碰到的高频问题。这些问题虽然不大,但每一个都能让线上服务或团队协作突然踩坑,值得你收藏备查。
| 常见问题 | 典型排查思路 | 预防/解决方案 |
|---|---|---|
| 凭证已经删除并重新提交,但告警还是不停 | Git历史中有旧提交,平台缓存未清理,或有人fork了仓库 | 使用git filter-repo重写历史,同时轮换真正的密钥,不要只处理代码 |
| API Key在代码里找不到,但账单异常增长 | Key可能存在镜像环境变量、CI配置、日志系统或Notebook输出里 | 按漏点清单完整排查;查看服务商后台的调用记录定位调用来源 |
| Gitleaks误报太多,团队不想跑扫描了 | 自定义规则可能缺上下文,或把测试用的假密钥当成了真密钥 | 定期维护allowlist;在扫描器里按文件/路径忽略已知的测试密钥;用TruffleHog --only-verified过滤 |
| 微调模型在生成代码时输出了真实密钥 | 训练语料中包含硬编码密钥,模型学会了这种模式 | 清理训练集、过滤含密钥的代码库;微调前对数据做凭证脱敏 |
| Agent运行时报错,日志里打印了Token被识别为敏感信息 | 程序的日志脱敏把自己真实可用的Key盖掉了 | 在脱敏规则中增加上下文白名单,但要优先保证脱敏,让Key从配置读取而不是写死在日志逻辑里 |
| 部署环境里找不到有效凭证,服务启动失败 | 环境变量没同步更新,或Secret在KMS中被误删 | 引入“凭证配置漂移检测”,对比生产环境与KMS中的密钥版本,检查部署脚本 |
| 团队共享开发Key,某成员离职后不确定谁还在使用 | 一把Key多人复用,无法定位责任 | 强制一人一Key并在服务商后台按用户审计;离职或转岗时立即吊销对应个人Key |
| 发现LangSmith/ Langfuse上有完整请求链路记录含敏感Header | 集成追踪工具时未脱敏 | 在SDK初始化时配置redact_headers,或在服务端开启数据脱敏规则 |
6.1 排查小技巧:如何快速判断一个Key是否真的是泄露了
如果你在日志或仓库中发现某个形似密钥的字符串,想提高排查效率,可以先用离线规则快速判断它是真凭证还是占位符。格式上典型凭证会有明显前缀和长度特征,比如OpenAI的sk-加一长串字符、HuggingFace的hf_等。如果匹配到关键字,不要直接在公网查询,更不要在开源搜索引擎中粘贴,因为部分恶意爬虫会记录这类搜索内容。正确做法是登录到对应服务商的后台,在日志审计页面或配额页面尝试查找该Key的最后调用记录。如果后台能显示调用IP和调用时间,顺藤摸瓜就能判断泄漏是否已经被利用。
6.2 排查小技巧:一套轻量的季度凭证巡检脚本
每个季度,我会给团队里每个项目跑一次轻量巡检脚本,检查仓库、CI、文档、镜像等位置是否出现新的密钥。这套巡检不一定要上商用产品,先动用Gitleaks加一个简单的Python脚本就能解决。脚本的核心逻辑是:拉取项目最新代码到临时目录,运行扫描得到JSON报告,再将报告与上季度的报告做diff,只需要重点关注新增的风险项。新增项如果是“文件被改动引入的”,就要立刻进入整改;如果是“上次已经标记允许的测试密钥”,可以排到下一轮清理。
至于巡检发现的旧密钥自动清理,我建议谨慎自动化。有些老项目里的密钥可能还在被某个服务使用,直接自动轮换容易引发中断。更稳妥的做法是:脚本输出一个“建议轮换清单”并交给各服务负责人确认,再由密钥仓库自动生成新值并推送部署。
6.3 排查小技巧:模型输出中含密钥的终极防范
最后聊一下模型生成代码中含密钥的痛点。在微调、RAG检索这种场景中,你要从源头清洗语料。在代码基线入库前,先用正则和语义过滤把所有看起来像凭证的Token替换成占位符。如果RAG知识库里已经存在含密钥的内容,检索器最终返回给模型前要加一层脱敏过滤:无论用户问什么,答案里出现合规密钥格式的内容都先做拦截。代码解析器可以用tree-sitter把变量赋值语句抽出来再跑凭证检测,这个方案比单纯字符串匹配可靠不少。
我在团队里实现过一个快速过滤函数,效果不错:
python复制import re
def redact_sensitive_in_text(text: str) -> str:
patterns = [
re.compile(r"(?i)\b(sk|pk|ghp|gho|hf|AKIA|eyJ)[A-Za-z0-9_\-\.]{10,}"),
re.compile(r"(?i)('|\")?(api[_-]?key|secret|token|password)('|\")?\s*[:=]\s*('|\")[^\s]{8,}('|\")"),
]
for pattern in patterns:
text = pattern.sub("[REDACTED_CREDENTIAL]", text)
return text
这个小函数被我用在RAG的检索后置过滤、Agent工具的响应脱敏和模型输出展示层。一旦它拦截住某段内容,日志中只会输出[REDACTED_CREDENTIAL],有效避免了下游把模型生成结果直接写入日志时的二次泄漏。
写在最后
从我做过的AI项目安全改造来看,凭证管理不是一次性工程,更像长期卫生习惯。你可能花一个周末把存量仓库洗得干干净净,但只要后续开发流程里没有自动化卡口,两周后新的硬编码Key就会悄悄回归。别问我为什么知道,这类返工我经历过太多轮了。
如果你现在就想去查一下自己的AI项目,我的建议是从Gitleaks扫描开始,先跑一遍全量Git历史,再按六条泄漏管道逐项摸底。发现问题后不要慌,处理顺序永远是先轮换密钥,再改代码,最后清理历史。等你把整套流程跑顺了,你会发现在凭证上踩坑的精力成本其实非常低——低到完全有理由把它变成所有新项目的默认安全基线。
