API Key 泄露后别只删代码:从止损、轮换到审计的完整应急手册

📅 2026/7/2 8:28:44
API Key 泄露后别只删代码:从止损、轮换到审计的完整应急手册
把 API Key 误提交到 Git 仓库、贴进工单、截进群聊往往只需要几秒。真正危险的不是这次手滑而是团队把“删除那一行”误当成“事故已经结束”。一把已经公开过的 Key即使随后从最新代码中消失仍可能留在 Git 历史、Fork、构建日志、镜像层、缓存、聊天记录和他人的本地克隆中。只要它仍然有效拿到副本的人就仍可能调用接口。GitHub 的泄露凭据处置文档也明确建议把泄露的 Secret 视为已被攻破。简单删除代码、再提交一次或者重新创建仓库都不能让原凭据自动失效。因此API Key 泄露后的正确目标不是“把字符串藏回去”而是完成四件事让旧凭据失效。恢复合法调用。确认是否已被滥用。降低下一次泄露的概率。本文给出一套适用于 AI API、云服务、支付接口、数据库令牌和内部服务 Token 的通用处理方法。具体控制台名称、审计字段和轮换能力应以凭据发行方的当前官方文档为准。一、先记住最重要的顺序撤销优先于删历史发现泄露后很多人的第一反应是删文件、改提交、强推分支。这些动作能减少后续暴露却不能阻止已经复制走的 Key 被继续使用。真正的止损动作发生在凭据发行方撤销旧 Key或者先创建替代 Key、完成切换再撤销旧 Key。可以把处置顺序记成一句话先让旧钥匙开不了门再清理散落的钥匙照片最后检查门有没有被打开过。对高风险凭据——例如生产环境、公开仓库、管理员权限、可产生费用或可读取敏感数据的 Key——默认按“已被第三方获得”处理不要等待出现异常账单后再行动。公开互联网存在自动化 Secret 扫描暴露窗口越长风险越高。推荐的总流程是记录发现时间、泄露位置和凭据类型但不要在新工单里再次粘贴完整 Key。判断凭据权限、环境、有效状态和依赖范围。立即撤销或执行受控的“新旧并行—切换—验证—撤销”。检查调用日志、账单、资源变更和异常来源。清理仓库、日志、制品、聊天和文档中的副本。修复根因增加 Secret 扫描、最小权限和短期凭据。撤销、轮换、删除不是同一件事撤销让旧凭据立即失效。轮换创建并部署新凭据同时淘汰旧凭据。删除从暴露载体中移除敏感字符串。三者都可能需要但安全优先级不同。二、前十分钟建立一个不扩散秘密的事故记录应急响应需要证据但收集证据不能制造新的泄露。事故群、工单和截图里只记录可识别但不可使用的信息例如凭据发行方凭据名称末四位或指纹所属环境首次发现时间暴露位置仓库可见性当前负责人。不要粘贴完整 Key不要把含 Key 的终端历史直接上传也不要在公共 Issue 中贴原始请求头。若必须证明两个位置出现的是同一把 Key可以在本地计算带盐哈希或者使用供应商提供的凭据 ID再记录摘要。一个最小事故卡片可以写成incident_id: SEC-20260701-001 discovered_at: 2026-07-01T11:20:0008:00 secret_type: API key provider: provider name secret_identifier: key id or last 4 chars environment: production exposure_location: repository/file/path:line repository_visibility: public | private | unknown first_known_exposure: commit time or message time current_status: active | revoked | unknown owner: team incident_lead: person or role同时应当冻结无关修改。事故期间反复改 Base URL、权限、模型名和部署配置会破坏审计基线。除止损必需动作外所有变更都应该绑定事故编号、时间、执行者和验证结果。三、快速定级不是所有 Key 的爆炸半径都一样定级的目的不是拖延撤销而是决定需要多快、多大范围地行动。至少回答下面六个问题维度低风险信号高风险信号可见性未发送、本地未提交公共仓库、公开网页、多人群聊环境隔离测试环境生产环境权限只读、单资源管理员、跨项目、写入或删除权限有效性已过期或已撤销当前仍可用价值无真实数据、无账单能力可读敏感数据、可产生费用、可修改资源暴露时长数秒且被预提交钩子拦截已存在数小时、数天或更久如果无法确认仓库是否曾公开、Key 是否仍然有效、日志是否完整就把“未知”按照较高风险处理。未知不是安全证据。还要识别 Key 的下游消费者例如生产服务定时任务CI/CD个人脚本监控任务数据管道移动端配置灾备环境。没有依赖清单时撤销可能造成停机但因为害怕停机而一直不撤销又会放大安全风险。解决方法不是无限延迟而是由安全负责人和服务负责人共同选择轮换策略。四、止损方案直接撤销还是先轮换再撤销1. 能立即撤销时直接撤销这种方式适合以下场景Key 已经公开暴露Key 权限较大依赖范围明确业务可以短暂停止。撤销后应当使用旧 Key 发起一个无副作用的最小请求确认它已经返回认证失败。不要只相信控制台按钮已经变灰系统需要证据证明旧凭据不能继续使用。验证请求必须避免产生写操作也不要把旧 Key 写回 Shell 历史。可以临时从受控环境变量读取并在验证完成后立即清除会话变量。不同平台的认证失败状态和错误体可能不同应以发行方文档为准。2. 不能立即停机时执行受控轮换当大量服务共享同一凭据时直接撤销可能导致生产中断。此时可以采用短时间双凭据窗口创建权限不高于旧 Key 的新 Key。把新 Key 写入 Secret 管理系统而不是源代码。按照消费者清单逐个更新并重新部署。通过请求 ID、成功率和认证错误确认新 Key 已生效。在预先设定的最短窗口结束时撤销旧 Key。再次验证旧 Key 失效并监控是否仍有服务尝试使用它。双凭据窗口不是“以后再说”。它必须有明确的截止时间、负责人和自动提醒。否则团队很容易留下两把长期有效的 Key攻击面反而会扩大。轮换时不要顺便扩大权限。新 Key 应采用最小权限、单一环境、单一应用和明确有效期。若平台支持作用域、来源限制、IP 限制、预算或配额告警可以在不影响业务的前提下启用。但不能把这些控制写成任何供应商都必然支持的功能。3. 共享 Key 需要拆分如果开发、测试、生产和多个应用共用一把 Key事故调查几乎无法判断异常调用来自哪里。应当把轮换当成拆分机会每个环境、服务或工作负载使用独立身份。这样既能缩小爆炸半径也能在审计中把异常调用定位到具体消费者。五、审计回答“它有没有被用过”而不是只看账单撤销完成后第二个核心问题是凭据在暴露窗口内是否被未经授权地使用。审计窗口应当从“首次可能暴露”开始而不是从“被发现”开始。如果某次提交在三天前进入公共仓库今天才收到告警调查至少要覆盖这三天并为时钟偏差和日志延迟留出余量。建议从四类证据交叉检查。1. API 调用证据检查以下信息请求时间接口路径模型或资源状态码请求 ID来源网络User-Agent用量错误类型。不要仅仅依赖 IP。合法流量可能经过动态出口攻击者也可能使用云代理。更可靠的是寻找多维异常例如陌生地区从未使用过的接口异常时间段突然升高的请求速率与部署记录不一致的调用模式。2. 资源与权限变更检查是否出现以下操作创建新 Key创建新用户创建新项目创建新规则修改回调地址修改权限删除数据下载敏感对象。具备写权限的 Key 泄露后影响可能不只是一笔调用费用。3. 费用与配额查看用量、账单、配额和失败重试的变化。没有费用异常不能证明没有发生滥用。攻击者可能只做少量探测或者访问不会直接计费的资源。4. 本地与供应链证据搜索以下位置Git 仓库历史CI 日志构建制品容器镜像层包管理缓存对象存储备份文档系统工单聊天消息开发者本地克隆。Key 可能从一个位置泄露却在另一个位置被复制扩散。审计输出应区分三种结论确认存在滥用。未发现滥用证据。现有证据不足。不要把“日志里没看到”写成“确定未被使用”尤其是在日志保留期不足、字段缺失或时钟不一致时。六、Git 泄露为什么新增一个删除提交还不够Git 保存的是历史。把config.js里的 Key 删除并提交只会让最新版本看不到它旧提交仍可能包含完整字符串Fork、缓存和本地克隆也可能保留副本。正确顺序仍然是先撤销或轮换。随后根据敏感性和传播范围决定是否重写历史。GitHub 文档提醒历史重写会改变提交哈希、破坏签名、影响开放中的拉取请求并要求协作者重新同步。已经存在的克隆也不会自动清除。因此历史重写是降低持续暴露的措施不是替代撤销的魔法橡皮擦。如果决定重写历史应当由仓库管理员统一执行并通知所有协作者确定 Secret 出现的文件、路径、分支和标签。在备份和受控环境中使用git-filter-repo等工具清理。暂停普通推送强制更新受影响引用。清理或重新创建受污染的 Fork、缓存与构建产物。要求协作者重新克隆或按照统一步骤处理本地历史。再次运行 Secret 扫描确认没有其他副本。不要在没有协调的情况下随意执行push --force。它可能打断团队工作却仍然无法触及已经被复制出去的内容。七、日志、截图和构建产物也要清理Key 不只会出现在代码中。最常见的二次泄露源包括开启set -x后输出的 Shell 命令打印完整请求头的 HTTP 调试日志CI 环境变量转储Dockerfile 中的ARG、ENV或构建层异常堆栈、Sentry 附件和 APM Span教程截图、录屏和终端历史.env备份、压缩包和临时目录聊天机器人对话、工单附件和知识库页面。清理日志时要兼顾取证完整性。不要让个人直接删除所有审计日志。应当由有权限的负责人先保存受控证据再对可公开访问或不应长期保留的副本进行脱敏、下架或缩短保留期。同时记录清理对象、时间、执行者和依据。应用日志默认不应记录以下内容AuthorizationCookieURL 查询串中的完整 Token密码数据库连接串私钥完整请求正文。调试时记录请求 ID、状态码、目标主机、路径模板、响应大小和耗时通常已经足够定位大多数接口问题。一个简单的日志脱敏函数可以采取“允许列表”而不是“发现什么就替换什么”functionsafeRequestLog(req){return{requestId:req.headers[x-request-id],method:req.method,host:req.hostname,path:req.route?.path??unknown,contentType:req.headers[content-type],contentLength:req.headers[content-length],// 不记录 authorization、cookie、正文和完整查询串};}允许列表的好处是新增加的敏感请求头不会因为漏写正则而自动进入日志。八、恢复服务验证新 Key而不是凭感觉宣布完成新 Key 部署后至少执行四类验证正常业务请求成功并且使用的是新凭据。旧 Key 的最小无副作用请求明确失败。所有消费者都停止使用旧 Key。日志、错误页面和追踪系统没有记录新 Key 明文。不要只在开发电脑上测试。CI、容器、定时任务和生产工作负载可能读取不同的 Secret 版本。应当为每个消费者记录部署版本、Secret 版本或配置哈希并使用一次真实但低风险的健康检查进行验证。如果撤销后仍有认证失败应当先找出尚未迁移的消费者不要重新启用旧 Key。重新启用旧 Key会让已经关闭的攻击窗口再次打开。九、根因复盘从“谁提交的”转向“为什么系统允许提交”把事故归结为某个人粗心无法防止下一次发生。更有效的复盘问题包括为什么应用需要把长期 Key 放进开发者可见的文件为什么.env、示例配置或调试输出进入了版本控制为什么预提交、CI 或平台 Push Protection 没有拦截为什么生产和测试共用一把 Key为什么轮换时找不到消费者清单为什么日志能够看到完整认证头为什么 Key 没有过期时间、权限边界或用量告警根因通常不是单点而是一条控制链同时缺失Secret 存储不规范代码审查没有检查自动扫描未启用凭据权限过大监控不完善应急手册未演练。十、预防体系把长期静态 Key 变成例外1. 使用 Secret 管理系统生产凭据应由专用 Secret 管理系统、云密钥库或受控运行时注入。源代码只保留变量名和示例值例如AI_API_KEYYOUR_API_KEY.gitignore是必要措施但它不是安全边界。它只能阻止符合规则的未跟踪文件被加入无法保护已经提交、被强制添加或写进其他文件的 Secret。2. 启用提交前与服务端扫描本地预提交扫描反馈快CI 扫描覆盖更多入口托管平台的 Secret Scanning 和 Push Protection 能在服务端提供额外防线。GitHub 的 Push Protection 会在推送时检查已支持的 Secret 模式并阻止推送组织还可以按照需要配置自定义模式。任何绕过操作都应当记录理由并接受审查。扫描器会产生误报也可能漏掉自定义格式。正确做法是维护规则、测试样本和例外审批而不是因为一次误报就关闭整条防线。3. 最小权限与分环境每个 Key 只获得完成任务所需的权限并绑定单一环境和明确负责人。避免“万能生产 Key”被复制给多个团队。OWASP 的 Secret 管理建议强调细粒度访问控制、生命周期管理、撤销和轮换。这些控制能显著缩小泄露后的爆炸半径。4. 优先使用短期身份如果平台支持工作负载身份、OIDC 或动态 Secret应优先使用短期令牌替代长期静态 Key。以 GitHub Actions 的 OIDC 为例工作流可以向云服务交换短期访问令牌从而减少在 CI 中长期保存云凭据的需要。短期令牌仍需正确限制以下内容受众主体仓库分支权限。不能因为令牌寿命短就放弃必要的身份校验。5. 建立可轮换的元数据每个 Secret 至少记录负责人用途消费者环境权限创建时间到期时间上次轮换时间轮换步骤紧急联系人。没有这些元数据事故发生时团队甚至不知道撤销会影响谁。6. 定期演练可以每季度选择一把低风险测试 Key 进行演练发现告警。通知负责人。创建替代凭据。更新消费者。撤销旧凭据。验证旧凭据失效。审计调用记录。清理暴露载体。演练应当计时并把卡住的步骤转化为自动化任务。十一、可直接复制的应急检查表发现与定级未在工单、群聊或截图中再次暴露完整 Key已记录凭据 ID、发行方、环境、权限和负责人已确认仓库或载体的可见范围已确定首次可能暴露时间和审计窗口未知项已按较高风险处理止损与恢复已撤销旧 Key或启动有截止时间的受控轮换新 Key 权限不高于旧 Key已更新全部消费者并逐一验证已用无副作用请求确认旧 Key 失效未通过重新启用旧 Key 解决迁移遗漏审计与清理已检查 API 调用、资源变更、账单和权限记录已检查仓库历史、分支、标签、Fork 和本地克隆已检查 CI 日志、镜像层、制品、缓存和备份已检查聊天、工单、文档、截图和录屏结论区分“未发现证据”与“确认未发生”防止复发凭据迁移到 Secret 管理系统或受控运行时启用预提交、CI 和服务端 Secret 扫描按环境与应用拆分 Key并落实最小权限能使用时改为短期身份或动态 Secret为每个 Secret 建立消费者和轮换元数据应急手册已经演练并记录完成时间结语API Key 泄露不是一个“把字符串删掉”的代码问题而是一场小型身份安全事件。旧 Key 是否失效决定攻击窗口是否真正关闭消费者清单是否完整决定轮换能否不靠运气审计证据是否充分决定团队能否判断影响预防控制是否落地决定相同事故会不会再次发生。最稳妥的原则仍然很朴素泄露即视为失守先撤销或轮换随后审计和清理最后把长期、共享、权限过大的 Key逐步替换为可管理、可追踪、可快速失效的身份。安全响应的质量不在于事故群里有多少消息而在于旧钥匙什么时候真正开不了门。参考资料GitHub DocsRemediating a leaked secret in your repositoryhttps://docs.github.com/en/code-security/secret-scanning/managing-alerts-from-secret-scanning/remediating-a-leaked-secretGitHub DocsRemoving sensitive data from a repositoryhttps://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repositoryGitHub DocsPush protectionhttps://docs.github.com/en/code-security/concepts/secret-security/push-protectionGitHub DocsSecret scanninghttps://docs.github.com/en/code-security/concepts/secret-security/secret-scanningGitHub DocsOpenID Connecthttps://docs.github.com/en/actions/concepts/security/openid-connectOWASP Cheat Sheet SeriesSecrets Management Cheat Sheethttps://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.htmlOWASP Cheat Sheet SeriesLogging Cheat Sheethttps://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html