-
Notifications
You must be signed in to change notification settings - Fork 0
fix: §12 活体验证防起飞窗口误报 + liveness 指纹归一化(P1-4,ADR-0034) #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -439,78 +439,71 @@ else | |||||||||||||||||||||||||||||||||||||||
| [[ $REQ_MISSING -eq 0 ]] && ok "CI-Workflows 必需大版本指针存在($CW_REQUIRED_POINTERS)" | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # ---------- 12. required check 活体存在性(P1-4,ADR-0034)---------- | ||||||||||||||||||||||||||||||||||||||||
| # ---------- 12. required check 活体存在性(P1-4,ADR-0034;in-flight 防误报修订)---------- | ||||||||||||||||||||||||||||||||||||||||
| # 文本对账 ≠ 生效验证:ruleset JSON 完全正确的同时,required check 字符串精确 | ||||||||||||||||||||||||||||||||||||||||
| # 匹配可能实际为空(job 改名 / workflow 重构)→ "零 required check" → PR 裸奔。 | ||||||||||||||||||||||||||||||||||||||||
| # 每个受管仓最近活动的 PR head(无 PR 活动时退化为默认分支 HEAD)上,必须存在 | ||||||||||||||||||||||||||||||||||||||||
| # 每个 required check 名(从 rulesets/*.json 派生——单一真源)的 check run 且 | ||||||||||||||||||||||||||||||||||||||||
| # conclusion 非空。fail-closed:check-runs 查询失败即判漂移,不用部分结果。 | ||||||||||||||||||||||||||||||||||||||||
| # 判据:每个受管仓最近更新的至多 3 个已合并/打开 PR 的 head 上,必须存在每个 | ||||||||||||||||||||||||||||||||||||||||
| # required check 名(从 rulesets/*.json 派生——单一真源)的 check run 且 | ||||||||||||||||||||||||||||||||||||||||
| # conclusion 非空;无 PR 活动时退化为默认分支 HEAD。修订(2026-08-20 实测误报 | ||||||||||||||||||||||||||||||||||||||||
| # mutual:三连新开 PR 的 CI 起飞窗口内 gate 尚未报 conclusion,被误判缺失): | ||||||||||||||||||||||||||||||||||||||||
| # - CI 未完结(任一 check run 非 completed)的 head 不构成任何证据——既不算 | ||||||||||||||||||||||||||||||||||||||||
| # "缺 gate"也不算"有 gate"(防起飞窗口误报);全部候选 in-flight 时显式 | ||||||||||||||||||||||||||||||||||||||||
| # SKIP(非漂移,下轮复核) | ||||||||||||||||||||||||||||||||||||||||
| # - 关闭未合并的 PR 不采样(废弃实验分支的残缺 CI 不构成证据) | ||||||||||||||||||||||||||||||||||||||||
| # - 已完结 CI 的 PR head 缺 gate 仍照报(PR 群体缺 required check = CI 结构性 | ||||||||||||||||||||||||||||||||||||||||
| # 异常或改名注入——T1 的检测对象;main HEAD 仅在无 PR 活动时作退化载体, | ||||||||||||||||||||||||||||||||||||||||
| # 不作为豁免来源)。查询失败维持 fail-closed。 | ||||||||||||||||||||||||||||||||||||||||
| REQ_CHECKS=$(jq -rs '[.[].rules[]? | select(.type == "required_status_checks") | ||||||||||||||||||||||||||||||||||||||||
| | .parameters.required_status_checks[].context] | unique | .[]' "$DIR"/rulesets/*.json) | ||||||||||||||||||||||||||||||||||||||||
| [[ -n "$REQ_CHECKS" ]] || { echo "FATAL: rulesets 未声明任何 required check——§12 活体验证失去判据"; exit 2; } | ||||||||||||||||||||||||||||||||||||||||
| epoch_of() { date -u -d "$1" +%s; } # ISO8601 → epoch(runner GNU date) | ||||||||||||||||||||||||||||||||||||||||
| for r in $REPOS; do | ||||||||||||||||||||||||||||||||||||||||
| jq -e --arg r "$r" '($r as $x | . | index($x)) != null' <<<"$EXCLUDES" >/dev/null && continue | ||||||||||||||||||||||||||||||||||||||||
| LIVE_MISS=0; QUERY_FAIL=0 | ||||||||||||||||||||||||||||||||||||||||
| # 第一判据(强):open PR 是活体漏洞本体——head 上已产生其他 check run(工作流 | ||||||||||||||||||||||||||||||||||||||||
| # 已在跑)但缺任一 required check 名 = 改名/重构实锤,裸奔窗口开启,立即报。 | ||||||||||||||||||||||||||||||||||||||||
| # 零 check run 的启动延迟窗口不在此报(§13(c) 以 liveness 阈值兜底),防误报。 | ||||||||||||||||||||||||||||||||||||||||
| OPEN_PRS12=$(api "https://api.github.com/repos/$ORG/$r/pulls?state=open&per_page=30") | ||||||||||||||||||||||||||||||||||||||||
| if jq -e 'type == "array"' <<<"$OPEN_PRS12" >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||||||||
| while IFS=$'\t' read -r pnum osha; do | ||||||||||||||||||||||||||||||||||||||||
| [[ -n "$pnum" ]] || continue | ||||||||||||||||||||||||||||||||||||||||
| CRS=$(api "https://api.github.com/repos/$ORG/$r/commits/$osha/check-runs?per_page=100") | ||||||||||||||||||||||||||||||||||||||||
| PRS_RECENT=$(api "https://api.github.com/repos/$ORG/$r/pulls?state=all&sort=updated&direction=desc&per_page=20") | ||||||||||||||||||||||||||||||||||||||||
| if jq -e 'type == "array"' <<<"$PRS_RECENT" >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||||||||
| HEADS=$(jq -r '[.[] | select(.state == "open" or .merged_at != null) | .head.sha][0:3] | .[]' <<<"$PRS_RECENT") | ||||||||||||||||||||||||||||||||||||||||
| if [[ -z "$HEADS" ]]; then | ||||||||||||||||||||||||||||||||||||||||
| # 无(已合并/打开的)PR 活动 → 退化为默认分支 HEAD | ||||||||||||||||||||||||||||||||||||||||
| DBR=$(api "https://api.github.com/repos/$ORG/$r" | jq -r '.default_branch // "main"') | ||||||||||||||||||||||||||||||||||||||||
| HEADS=$(api "https://api.github.com/repos/$ORG/$r/git/ref/heads/$DBR" | jq -r '.object.sha // empty') | ||||||||||||||||||||||||||||||||||||||||
| [[ -n "$HEADS" ]] || { drift "repo '$r' 无 PR 活动且默认分支 HEAD 不可读,活体验证无载体(fail-closed)"; continue; } | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||
| drift "repo '$r' PR 清单拉取失败,required check 活体验证无法执行(fail-closed)" | ||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| LIVE_MISS=0; QUERY_FAIL=0; ALL_INFLIGHT=1 | ||||||||||||||||||||||||||||||||||||||||
| for ctx in $REQ_CHECKS; do | ||||||||||||||||||||||||||||||||||||||||
| FOUND=0 | ||||||||||||||||||||||||||||||||||||||||
| while IFS= read -r sha; do | ||||||||||||||||||||||||||||||||||||||||
| [[ -n "$sha" ]] || continue | ||||||||||||||||||||||||||||||||||||||||
| CRS=$(api "https://api.github.com/repos/$ORG/$r/commits/$sha/check-runs?per_page=100") | ||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 5. Check runs remain unpaginated Each candidate requests only the first 100 check runs, yet both completion detection and context lookup treat that page as the complete set. A required or in-progress run on a later page can therefore cause a false missing-check drift instead of FOUND or SKIP. Agent Prompt
|
||||||||||||||||||||||||||||||||||||||||
| if ! jq -e 'type == "object" and has("check_runs")' <<<"$CRS" >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||||||||
| drift "repo '$r' PR#$pnum check-runs 查询失败,required check 活体无法验证(fail-closed)" | ||||||||||||||||||||||||||||||||||||||||
| QUERY_FAIL=1; continue | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| N_ANY=$(jq '.check_runs | length' <<<"$CRS") | ||||||||||||||||||||||||||||||||||||||||
| [[ "$N_ANY" -gt 0 ]] || continue # 启动延迟窗口,交 §13(c) | ||||||||||||||||||||||||||||||||||||||||
| for ctx in $REQ_CHECKS; do | ||||||||||||||||||||||||||||||||||||||||
| jq -e --arg c "$ctx" '[.check_runs[] | select(.name == $c)] | length > 0' <<<"$CRS" >/dev/null 2>&1 \ | ||||||||||||||||||||||||||||||||||||||||
| || { drift "repo '$r' PR#$pnum required check '$ctx' 活体缺失:open PR 的工作流已产出其他 check run 但无 '$ctx'——job 改名/workflow 重构,裸奔窗口开启(ADR-0034 §12)"; LIVE_MISS=1; } | ||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||
| done < <(jq -r '.[] | [(.number|tostring), .head.sha] | @tsv' <<<"$OPEN_PRS12") | ||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||
| drift "repo '$r' open PR 清单拉取失败,required check 活体验证无法执行(fail-closed)" | ||||||||||||||||||||||||||||||||||||||||
| QUERY_FAIL=1 | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| # 第二判据(退化):无 open PR 时——最近活动的至多 3 个 PR head(再退化为默认 | ||||||||||||||||||||||||||||||||||||||||
| # 分支 HEAD)上必须存在每个 required check 名且 conclusion 非空(改名合入 main | ||||||||||||||||||||||||||||||||||||||||
| # 后连续 PR 均缺失的兜底检出)。 | ||||||||||||||||||||||||||||||||||||||||
| if [[ $(jq 'length' <<<"$OPEN_PRS12") -eq 0 && $QUERY_FAIL -eq 0 ]]; then | ||||||||||||||||||||||||||||||||||||||||
| PRS_RECENT=$(api "https://api.github.com/repos/$ORG/$r/pulls?state=all&sort=updated&direction=desc&per_page=20") | ||||||||||||||||||||||||||||||||||||||||
| if jq -e 'type == "array"' <<<"$PRS_RECENT" >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||||||||
| HEADS=$(jq -r '[.[] | .head.sha][0:3][]' <<<"$PRS_RECENT") | ||||||||||||||||||||||||||||||||||||||||
| [[ -n "$HEADS" ]] || HEADS=$(api "https://api.github.com/repos/$ORG/$r/git/ref/heads/main" | jq -r '.object.sha // empty') | ||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||
| drift "repo '$r' PR 清单拉取失败,required check 活体验证无法执行(fail-closed)" | ||||||||||||||||||||||||||||||||||||||||
| QUERY_FAIL=1 | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| if [[ -n "${HEADS:-}" && $QUERY_FAIL -eq 0 ]]; then | ||||||||||||||||||||||||||||||||||||||||
| for ctx in $REQ_CHECKS; do | ||||||||||||||||||||||||||||||||||||||||
| FOUND=0 | ||||||||||||||||||||||||||||||||||||||||
| while IFS= read -r sha; do | ||||||||||||||||||||||||||||||||||||||||
| [[ -n "$sha" ]] || continue | ||||||||||||||||||||||||||||||||||||||||
| CRS=$(api "https://api.github.com/repos/$ORG/$r/commits/$sha/check-runs?per_page=100") | ||||||||||||||||||||||||||||||||||||||||
| if ! jq -e 'type == "object" and has("check_runs")' <<<"$CRS" >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||||||||
| QUERY_FAIL=1; continue | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| if jq -e --arg c "$ctx" '[.check_runs[] | select(.name == $c and .conclusion != null)] | length > 0' <<<"$CRS" >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||||||||
| FOUND=1; break | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| done <<<"$HEADS" | ||||||||||||||||||||||||||||||||||||||||
| if [[ $QUERY_FAIL -eq 1 && $FOUND -eq 0 ]]; then | ||||||||||||||||||||||||||||||||||||||||
| drift "repo '$r' check-runs 查询失败,required check '$ctx' 活体无法验证(fail-closed)" | ||||||||||||||||||||||||||||||||||||||||
| elif [[ $FOUND -ne 1 ]]; then | ||||||||||||||||||||||||||||||||||||||||
| drift "repo '$r' required check '$ctx' 活体缺失:ruleset 文本正确但最近 PR head / main HEAD 均无该 check run——job 改名或 workflow 重构?(ADR-0034 §12 退化判据)" | ||||||||||||||||||||||||||||||||||||||||
| LIVE_MISS=1 | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||
| TOTAL=$(jq -r '.total_count // 0' <<<"$CRS") | ||||||||||||||||||||||||||||||||||||||||
| [[ "$TOTAL" -eq 0 ]] && continue | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+484
to
+485
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2. Zero runs falsely skipped 当所有候选 head 都没有 check run 时,代码直接跳过且保持 ALL_INFLIGHT=1,最终将“工作流完全未触发或已删除”误标为 SKIP,而不是 required check 缺失。§13 只会在存在且超过阈值的 open PR 上兜底,因此没有 open PR 的仓库可以持续假绿。 Agent Prompt
|
||||||||||||||||||||||||||||||||||||||||
| # CI 未完结的 head:不构成证据(防起飞窗口误报——gate job 在依赖图末端, | ||||||||||||||||||||||||||||||||||||||||
| # 新开 PR 的前几分钟 conclusion 必为 null,此时判"缺失"全是误报) | ||||||||||||||||||||||||||||||||||||||||
| jq -e '[.check_runs[] | select(.status != "completed")] | length > 0' <<<"$CRS" >/dev/null 2>&1 && continue | ||||||||||||||||||||||||||||||||||||||||
| ALL_INFLIGHT=0 | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+484
to
+489
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 严重级别:高(fail-open)。所有候选 head 均无 check run 时,检测器永久输出 SKIP,漏报最严重的裸奔形态。 Line 482 在 建议用提交时间给"零 check run"设置时限。已定义的 🔒 建议修复:零 check run 超时后判为缺失 TOTAL=$(jq -r '.total_count // 0' <<<"$CRS")
- [[ "$TOTAL" -eq 0 ]] && continue
+ if [[ "$TOTAL" -eq 0 ]]; then
+ # 零 check run:起飞窗口内允许(视为 in-flight);超窗则构成"缺 gate"证据
+ CDATE=$(api "https://api.github.com/repos/$ORG/$r/commits/$sha" | jq -r '.commit.committer.date // empty')
+ if [[ -n "$CDATE" ]] && (( $(date -u +%s) - $(epoch_of "$CDATE") > 1800 )); then
+ ALL_INFLIGHT=0
+ fi
+ continue
+ fi📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| if jq -e --arg c "$ctx" '[.check_runs[] | select(.name == $c and .conclusion != null)] | length > 0' <<<"$CRS" >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||||||||
| FOUND=1; break | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| done <<<"$HEADS" | ||||||||||||||||||||||||||||||||||||||||
| if [[ $FOUND -ne 1 ]]; then | ||||||||||||||||||||||||||||||||||||||||
| if [[ $QUERY_FAIL -eq 1 && $ALL_INFLIGHT -eq 1 ]]; then | ||||||||||||||||||||||||||||||||||||||||
| drift "repo '$r' check-runs 查询失败,required check '$ctx' 活体无法验证(fail-closed)" | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+494
to
+496
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3. Query failures can pass A failed check-runs request sets QUERY_FAIL, but if another candidate contains the context, FOUND=1 bypasses the only failure-reporting branch and the repository can still emit OK. This contradicts the stated fail-closed contract and allows partial API results to validate required-check liveness. Agent Prompt
|
||||||||||||||||||||||||||||||||||||||||
| LIVE_MISS=1 | ||||||||||||||||||||||||||||||||||||||||
| elif [[ $ALL_INFLIGHT -eq 1 ]]; then | ||||||||||||||||||||||||||||||||||||||||
| echo "SKIP required-check-live '$r'(候选 head 全部 CI in-flight,本轮无法判定——非漂移,下轮复核)" | ||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||
| drift "repo '$r' required check '$ctx' 活体缺失:已完结 CI 的候选 head 均无该 check run——job 改名或 workflow 重构?裸奔窗口已开启(ADR-0034 §12)" | ||||||||||||||||||||||||||||||||||||||||
| LIVE_MISS=1 | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| [[ $LIVE_MISS -eq 0 && $QUERY_FAIL -eq 0 ]] && ok "required-check-live '$r'(open PR 无裸奔;${REQ_CHECKS//$'\n'/ } 活体齐备)" | ||||||||||||||||||||||||||||||||||||||||
| unset HEADS | ||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||
| [[ $LIVE_MISS -eq 0 && $ALL_INFLIGHT -eq 0 ]] && ok "required-check-live '$r'(required check 在已完结候选 head 上齐备)" | ||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # ---------- 13. PR liveness 侦测(P1-4,ADR-0034)---------- | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
基础设施故障通道会被空指纹去重永久静默。
本步骤只在
failure()时运行,其中包含 exit 2 与 API 故障(Line 55-58 已区分该通道)。这些情况下drift-report.txt没有 DRIFT 行,grep输出为空,FP因此等于空输入的固定 sha256。该常量指纹在 open issue 上被评论过一次后,之后所有基础设施故障都会命中 Line 93 的去重分支而被跳过。结果是 token 失效或 API 持续故障时不再产生任何新报告。建议在无 DRIFT 行时把故障类别并入指纹输入,或对该通道跳过去重。
🐛 建议修复:把故障类别纳入指纹输入
📝 Committable suggestion
🤖 Prompt for AI Agents