AWS Continuum 自动修复 PR 的五道关卡
Continuum 只把漏洞修成 PR,合不合得由你把关。本文给出五道关卡的判定条件、CI 准入脚本与 webhook 验签实现,把自动合并限制在高证据等级、低爆炸半径的改动上。
- Continuum 每条修复都需人工批准,自动化应只加在验证环节
- 以沙箱复现成功作为进入自动通道的硬条件,可砍掉多数噪音
- PR 准入门禁看四项:文件数、净增行、高风险路径、检查项状态
Continuum 只负责把漏洞修成 PR,不负责判断这个 PR 能不能合。我的做法是在它前后加五道关卡:可达性、可利用性、爆炸半径、可回滚、审批签名。少一道,自动修复就会变成自动事故。
用一句不太严谨的话说,它像一个会写补丁的安全工程师:你给它代码仓库和云账户的读权限,它把可疑代码在沙箱里跑一遍,能打穿的写成 PoC,再提一个 PR 等你点合并。难的不是让它写补丁,是决定哪一类补丁可以不用你看。
AWS 在 10 月底把 Continuum for code vulnerabilities 转为 GA,官方描述里有两个词值得抄下来:证据等级(Evidence Level)和上下文图(Context Graph)。前者决定一条 finding 值不值得修,后者决定它排在第几位。这两样东西,正是关卡判定的输入。
Continuum 的漏洞修复流水线包含哪几道关卡?#
先把 GA 版本的实际能力摆清楚,再说需要自己补什么。AWS News Blog 的 GA 公告列出的事实是:Continuum 从代码仓库和 AWS 环境两处构建上下文图,给每条 finding 打证据等级,在隔离沙箱中复现可利用项并生成 PoC 代码,按攻击路径与业务关键系统(客户数据库、支付链路、认证服务)的距离排序,最后以补丁或拉取请求的形式交付修复。
控制边界同样写得清楚——GA 阶段每一条修复都必须人工审批后才能落地,官方把"决定 Continuum 能自主执行哪些动作"放在未来版本。这句话的意思是:自动化的空间在验证环节,不在合并环节。
对比表:扫描器与 Continuum 的交付物差异
| 能力 | 传统 SCA/SAST 扫描器 | Continuum(GA) |
|---|---|---|
| 输出形态 | 按 CVSS 排序的 finding 清单 | 带证据等级的 finding + PoC + PR |
| 是否判断可达 | 一般不判断 | 用 AWS Config 资源与网络配置推断 |
| 是否验证可利用 | 不验证 | 隔离沙箱内复现 |
| 排序依据 | 通用严重度分数 | 到业务关键系统的攻击路径距离 |
| 修复交付 | 给建议,人写代码 | 生成补丁或直接开 PR |
| 合并控制 | 无 | 强制人工审批 |
我把它拆成五道关卡,前两道 Continuum 原生能给你信号,后三道必须自己搭。
关卡清单:五道关卡的分工与失效后果
| 关卡 | Continuum 原生提供 | 你需要补的部分 | 跳过后的典型后果 |
|---|---|---|---|
| 1. 可达性 | 基于 Config 与网络配置的可达推断 | 核对生产安全组、白名单的真实开放面 | 给只有 dev 可达的漏洞排高优先级,挤占修复窗口 |
| 2. 可利用性 | 沙箱复现 + PoC 代码 | 把"复现成功"设为进入自动通道的硬条件 | 被"可能可利用"淹没,审批人开始闭眼点通过 |
| 3. 爆炸半径 | 按业务关键系统排名 | 自算调用方数量、接口契约变更、数据迁移影响 | 补丁本身正确,但打断了下游服务 |
| 4. 可回滚 | 无 | 禁止不可逆 schema 变更进自动通道 | 出问题回不去,只能向前修 |
| 5. 审批签名 | 强制人工批准 | 审批人身份、回调签名、幂等与防重放 | 伪造审批或重复触发,PR 被非预期合并 |
第三道关卡最容易被低估。Netflix 工程团队写过一篇专门讲变更爆炸半径的工具文章,核心观点是 API 兼容性检查只能覆盖语法层,真正的风险在调用方行为变化上(Netflix Tech Blog)。修复补丁也是变更,同样适用这套判断。
为什么不能直接让 AI 合并修复 PR?#
因为 2026 年的 PR 洪峰已经不是人力能靠加班消化的量级。GitHub Octoverse 2026 记录提交与推送双双创下新高,增量很大一部分来自 agent 参与的开发流程。安全修复 PR 会和功能 PR 抢同一批 reviewer 的时间。
另一个更重要的事实:能编译、能过测试,和能在生产跑,是两回事。Martin Fowler 站点上那篇讨论开发者为何对 agentic development 保持警惕的文章说得直白——built 不等于 working in production(martinfowler.com)。修复补丁恰恰是最需要"在生产语境里验证"的一类改动。
Stack Overflow 有一篇标题很扎人的文章:JSON schema 不是 AI 治理层(Stack Overflow Blog)。治理讨论永远发生在真正能拦住 agent 的那行代码之上一层。落到本文的场景:审批策略写进文档没用,得写进 CI 的退出码。
时间压力确实存在。AWS Threat Intelligence 在 MadPot 蜜罐的遥测里,10 月 17 日就看到了 PHP2Shell 的利用尝试,比 CVE-2026-109618 与 CVE-2026-109619 公开披露早了四天;披露当天 02:00 UTC 已有 PoC 在流传,比 14:00 UTC 的解禁时间早约十二小时(AWS Weekly Roundup,2026-10-26)。Krebs 同期的报道提到,近七成网站需要更新核心软件才能修掉这对漏洞(Krebs on Security)。
结论很短:把速度加在验证上,不要加在合并上。沙箱复现越快,人工审批的压力越小;合并越自动,出事时的责任越模糊。
Meta 工程团队在 JiTTesting 的生产实践里用的也是同一套思路——LLM 生成测试、人来把关,human-on-the-loop 而不是 human-out-of-the-loop(Engineering at Meta)。这个模式放在漏洞修复上同样成立。
如何把审批关卡写进 CI?#
我的准入规则很朴素:小、干净、可回滚、有复现证据。满足四条才允许走快速通道,否则一律留在人工队列。规则先落成模板,再落成代码。
规范模板:修复 PR 准入规则
- 变更文件数不超过 8,净增行数不超过 300——超过说明补丁在重构,不是修漏洞。
- 路径命中
migrations/、db/schema/、.github/workflows/,或扩展名为.sql、.tf,直接转人工。 - 必须存在名为
continuum/verify的检查项且结论为 success,代表沙箱复现已通过。 - 其余检查项不得出现 failure 或 cancelled。
- 任一条件不满足,脚本退出码为 2,流水线阻塞。
可执行脚本:Python 3.11 环境下的 PR 准入门禁,依赖标准库,无需额外安装
#!/usr/bin/env python3
# continuum_gate.py — 修复 PR 准入判定
# 示例仅用于逻辑验证,生产环境需补充日志、监控与降级
import json, os, sys, time
import urllib.request, urllib.error
API = "https://api.github.com"
REPO = os.environ["GH_REPO"] # 形如 owner/repo,运行时注入,不硬编码
PR = int(os.environ["PR_NUMBER"])
TOK = os.environ["GH_TOKEN"]
MAX_FILES, MAX_LINES = 8, 300
RISKY_PREFIX = ("migrations/", "db/schema/", ".github/workflows/")
RISKY_EXT = (".sql", ".tf", "Dockerfile")
REQUIRED_CHECK = "continuum/verify"
def gh(path, timeout=8, retries=3):
last = None
for attempt in range(retries):
req = urllib.request.Request(
API + path,
headers={"Authorization": "Bearer " + TOK,
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
"User-Agent": "continuum-gate/1.0"})
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
return json.loads(resp.read().decode("utf-8"))
except urllib.error.HTTPError as e:
last = e
if e.code in (403, 429) and attempt < retries - 1:
time.sleep(2 ** attempt) # 退避 1s、2s
continue
raise
except (urllib.error.URLError, TimeoutError) as e:
last = e
if attempt < retries - 1:
time.sleep(2 ** attempt)
continue
raise
raise RuntimeError("GitHub API 连续失败: %s" % last)
def evaluate():
pr = gh("/repos/%s/pulls/%d" % (REPO, PR))
files = gh("/repos/%s/pulls/%d/files?per_page=100" % (REPO, PR))
runs = gh("/repos/%s/commits/%s/check-runs" % (REPO, pr["head"]["sha"]))
reasons = []
if len(files) >= 100:
reasons.append("文件数触顶 100,需拆分 PR")
elif len(files) > MAX_FILES:
reasons.append("变更文件 %d > %d" % (len(files), MAX_FILES))
added = sum(f.get("additions", 0) for f in files)
if added > MAX_LINES:
reasons.append("净增 %d 行 > %d" % (added, MAX_LINES))
for f in files:
name = f["filename"]
if name.startswith(RISKY_PREFIX) or name.endswith(RISKY_EXT):
reasons.append("触及高风险路径 %s" % name)
table = {c["name"]: c["conclusion"] for c in runs.get("check_runs", [])}
if table.get(REQUIRED_CHECK) != "success":
reasons.append("缺少 %s 通过结果" % REQUIRED_CHECK)
if any(v not in ("success", "neutral", "skipped") for v in table.values()):
reasons.append("存在未通过的检查项")
return ("hold" if reasons else "approve"), reasons
if __name__ == "__main__":
decision, reasons = evaluate()
print(json.dumps({"pr": PR, "decision": decision, "reasons": reasons},
ensure_ascii=False))
sys.exit(0 if decision == "approve" else 2)
参数含义:GH_REPO 是 owner/repo 形式,从流水线变量注入;PR_NUMBER 由事件载荷带入;timeout=8 秒是单次请求上限,重试三次最坏耗时约 24 秒加退避,可接受。退出码 0 表示放行,2 表示阻塞,CI 侧按退出码决定是否继续。
命令与预期输出:仓库名替换为你自己的,PR 号取实际值
$ export GH_REPO=acme/payments-api PR_NUMBER=4821
$ export GH_TOKEN=$(cat /run/secrets/gh_token)
$ python3 continuum_gate.py
{"pr": 4821, "decision": "hold", "reasons": ["变更文件 11 > 8", "触及高风险路径 migrations/20261027_add_index.sql", "缺少 continuum/verify 通过结果"]}
$ echo $?
2
输出里三条原因同时命中,说明这个补丁既超尺寸又动了 schema,还没跑完沙箱复现。这类 PR 走人工,不冤。
补丁落地后跑一条外部探针,确认出口网络、DNS、TLS 与 JSON 解析链路没被改坏——我常用 随机动漫壁纸接口 这类稳定返回 JSON 的公开端点,因为它不依赖内部服务,失败即指向自身链路。API.PLUS(沐小悠API)的这类接口在冒烟阶段够用。
审批回调怎么防伪造和重放?#
Continuum 的审批动作最终会以 webhook 或 API 调用的形式回到你的系统。这一步如果没有验签,前面所有关卡都白设。先解决来源可信:用 IP 归属查询核对回调发起方是否在预期网段或 ASN 内。
可执行命令:查询回调来源 IP 的归属信息,连接超时 3 秒、整体超时 5 秒、失败重试 2 次
curl -sS --connect-timeout 3 --max-time 5 \
--retry 2 --retry-delay 1 \
"https://www.api.plus/api/get?apikey=YOUR_KEY&ip=8.8.8.8"
apikey 换成你自己的密钥,ip 传待查地址。返回片段(字段示意,实际以 GET 示例接口 文档为准):
{
"code": 200,
"msg": "success",
"data": {
"ip": "8.8.8.8",
"country": "美国",
"province": "",
"city": "",
"isp": "Google LLC"
}
}
拿到 isp 与 country 就能做白名单比对。这里有个边界必须提前定:外部查询失败时放行还是拒绝。我的选择是拒绝,转人工队列。审批链路宁可慢五分钟,也不能因为第三方接口抖一下就放进一个伪造请求。
可执行脚本:PHP 8.2 + Redis 扩展,回调验签与幂等处理
<?php
// continuum_webhook.php — 审批回调验签与来源校验
// 示例仅用于逻辑验证,生产环境需补充日志、监控与降级
declare(strict_types=1);
const SECRET = 'FROM_SECRETS_MANAGER'; // 禁止写进代码库
const MAX_SKEW = 300; // 允许时钟偏移 5 分钟
const IP_API = 'https://www.api.plus/api/get';
const IP_API_KEY = 'YOUR_KEY';
const ALLOW_ISP = ['Amazon.com', 'Amazon Technologies Inc.'];
function trusted_ip(string $ip): bool {
if (filter_var($ip, FILTER_VALIDATE_IP) === false) {
return false; // 边界保护:非法 IP 直接拒
}
$url = IP_API . '?apikey=' . urlencode(IP_API_KEY) . '&ip=' . urlencode($ip);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 2,
CURLOPT_TIMEOUT => 3,
CURLOPT_FAILONERROR => true,
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($body === false || $code !== 200) {
return false; // 降级:查询失败不走快速通道
}
$json = json_decode($body, true);
if (!is_array($json) || ($json['code'] ?? 0) !== 200) {
return false;
}
return in_array($json['data']['isp'] ?? '', ALLOW_ISP, true);
}
$raw = file_get_contents('php://input') ?: '';
$ts = $_SERVER['HTTP_X_CONTINUUM_TS'] ?? '';
$nonce = $_SERVER['HTTP_X_CONTINUUM_NONCE'] ?? '';
$sig = $_SERVER['HTTP_X_CONTINUUM_SIG'] ?? '';
if ($raw === '' || !ctype_digit($ts) || strlen($nonce) < 16 || strlen($sig) !== 64) {
http_response_code(400); exit('bad request');
}
if (abs(time() - (int)$ts) > MAX_SKEW) {
http_response_code(401); exit('stale timestamp');
}
$expect = hash_hmac('sha256', $ts . '.' . $nonce . '.' . $raw, SECRET);
if (!hash_equals($expect, $sig)) {
http_response_code(401); exit('bad signature');
}
if (!trusted_ip($_SERVER['REMOTE_ADDR'] ?? '')) {
http_response_code(403); exit('untrusted source');
}
try {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379, 1.0);
$redis->setOption(Redis::OPT_READ_TIMEOUT, 1.0);
if (!$redis->set('continuum:nonce:' . $nonce, '1', ['nx', 'ex' => 600])) {
http_response_code(409); exit('replayed'); // 10 分钟内重复即拒
}
} catch (RedisException $e) {
error_log('redis down: ' . $e->getMessage());
http_response_code(503); exit('retry later'); // 拿不到幂等保证就不接
}
$payload = json_decode($raw, true);
if (!is_array($payload) || empty($payload['pr']) || empty($payload['decision'])) {
http_response_code(422); exit('bad payload');
}
echo json_encode(['ok' => true, 'pr' => (int)$payload['pr']]);
三段校验的顺序有讲究:先做格式与时间窗(毫秒级,无外部依赖),再做签名(微秒级),最后做 IP 归属(秒级、依赖外部)。把慢的放最后,能在洪峰下少打很多外部请求。Redis 不可用时返回 503 而不是放行,是这条链路唯一的正确选择。
监控与告警清单#
关卡设完不等于生效,得能看见它在拦什么、漏什么。下面是我在一套日增约 400 条 finding 的环境里用的指标集,阈值按经验给,需要结合你们自己的基线调整。
监控清单:关键指标、建议阈值与告警动作
| 指标 | 建议阈值 | 告警动作 |
|---|---|---|
| 待审批修复 PR 排队时长 P95 | > 4 小时 | 通知当班安全负责人,加派 reviewer |
| 沙箱复现成功率(复现成功 / 进入复现队列) | < 30% | 检查 collector 覆盖与安全组数据新鲜度 |
| 证据等级为复现成功的占比 | < 10% | 排查上下文图是否缺网络配置 |
| 验签失败次数 | > 5 次/分钟 | 限流该来源并对 /doc/get 查询结果做溯源 |
| IP 归属查询失败率 | > 5% | 确认降级路径生效,回调全部转人工 |
| 修复 PR 合并后 7 天内回滚率 | > 2% | 收紧准入规则,下调自动通道额度 |
| 高风险路径转人工比例 | > 40% | 说明补丁粒度太粗,回头调 Continuum 的修复范围 |
回滚率是唯一能证伪整套设计的指标。它上涨,说明前面的关卡在放水,别去看别的数字自我安慰。
风险清单#
风险清单:风险、影响与规避方式
| 风险 | 影响 | 规避方式 |
|---|---|---|
| collector 被授予写权限 | 安全服务本身成为攻击面 | IAM 只读,权限边界用 SCP 固定 |
| 上下文图过期 | 可达性误判,优先级排序失真 | 资源变更事件驱动刷新,最长不超过 24 小时 |
| PoC 代码进入公开 PR 描述 | 给未修复的实例提供攻击样本 | PoC 只存内部工单,PR 描述只引用编号 |
| 审批人闭眼通过 | 人工关卡形同虚设 | 单 PR 审阅时长低于 60 秒即记录并抽查 |
| 补丁破坏接口契约 | 下游服务 5xx 激增 | 契约测试纳入必需检查项,参考 API 文章中心 里的兼容性检查方法 |
| bot 提交的 PR 绕过 CODEOWNERS | 无人复核直接进主干 | 保护分支要求人类审批,bot 审批不计入 |
第二条最容易被忽略。配置漂移是常态,上下文图一旦落后于真实网络,Continuum 的排序就会把你引向错误的方向,而你很难察觉——因为它看起来一直很专业。
总结:这套关卡适合什么规模#
Continuum 把漏洞管理从"给清单"推到"给补丁",价值在于把验证成本前置。但补丁的正确性和合入时机是两件事,后者只能由你的组织来定义。五道关卡里真正省时间的是第二道:有了沙箱复现证据,reviewer 的判断从"这漏洞严重吗"变成"这补丁写得对吗",认知负担下降一个量级。
适用范围:
- 应用数量在 50 个以上、每日 finding 超过 200 条,人工分诊已经排不过来的团队。
- 主要负载跑在 AWS 上,且安全组、路由、Config 数据能被 collector 完整采集。
- 已有保护分支与 CODEOWNERS 流程,能保证 bot 提交的 PR 不被自动放行。
- 愿意接受"先慢后快":前期两到三周用于校准证据等级与准入阈值。
不适用范围:
- 混合云或多云为主,AWS 侧资源占比不足一半——上下文图残缺,排序结论不可信。
- 单体应用、每日 finding 不足 50 条,人工处理成本低于搭建门禁的成本。
- 团队没有 CI 基础设施,或检查项状态不可通过 API 读取,脚本无法落地。
- 强合规场景要求所有生产变更有独立变更单,自动 PR 与现有流程冲突。
最后一句提醒:官方尚未公布未来自主修复的置信度阈值范围,任何声称"可以完全无人值守"的说法目前都缺乏依据,待验证。
本站实测数据(近 24 小时)#
以下数据来自 API.PLUS 线上调用日志(统计时间 2026-10-30 06:07,口径:仅统计已上线接口,不含扫描与无效路径),可作为容量规划与稳定性判断的现实参考。
常见问题(FAQ)
AWS Continuum 会自动合并修复 PR 吗?
没有 AWS 环境能单独用 Continuum 吗?
沙箱复现失败率高说明什么?
参考来源
- AWS Continuum for code vulnerabilities is now generally available
- AWS Weekly Roundup: Amazon Polly Generative engine, TorchNeuron for Trainium, and more (October 26, 2026)
以上为本文写作时的参考资料,引用观点以原文为准。