fix: fail closed on incomplete weekly image and CVE surveys
This commit is contained in:
+22
-14
@@ -231,6 +231,8 @@ def registry_urls(recipe: str, registry_dir: str) -> tuple[list[str], str | None
|
||||
# recipe render '?' for no real reason — that is what put immich and n8n in the unknown
|
||||
# column on 2026-08-07 (https://docs.n8n.io/release-notes/` ← note the backtick).
|
||||
u = u.rstrip("`'\"*.,;:>)")
|
||||
if "…" in u or "..." in u:
|
||||
continue # prose placeholder, not a fetchable security source
|
||||
if u and u not in urls:
|
||||
urls.append(u)
|
||||
return urls, path
|
||||
@@ -826,6 +828,7 @@ def scan(recipe: str, v_from: str | None, v_to: str | None, registry_dir: str,
|
||||
windows[src] = (v_from, v_to)
|
||||
window_key[src] = k
|
||||
for key, wf, wt in (images or []):
|
||||
matched = False
|
||||
for src in gh_sources:
|
||||
if src in windows:
|
||||
continue
|
||||
@@ -837,6 +840,9 @@ def scan(recipe: str, v_from: str | None, v_to: str | None, registry_dir: str,
|
||||
if k in src.lower() or name in k:
|
||||
windows[src] = (wf, wt)
|
||||
window_key[src] = key
|
||||
matched = True
|
||||
if not matched:
|
||||
report.setdefault("unmatched_image_windows", []).append(key)
|
||||
report["windows"] = {k: {"from": f, "to": t} for k, (f, t) in windows.items()}
|
||||
|
||||
def _classify_window(src, wf, wt):
|
||||
@@ -1020,24 +1026,22 @@ def scan(recipe: str, v_from: str | None, v_to: str | None, registry_dir: str,
|
||||
# A scan with NO usable source has not measured anything, so it must not report a number —
|
||||
# least of all 0, which asserts safety. mumble had no cc-ci-plan/upstream/mumble.md at all and
|
||||
# still produced "0 identified", which was then published as a clean 0 in a CVE report.
|
||||
usable_sources = [
|
||||
s for s in report["sources"]
|
||||
if s["status"] == "ok" or s["status"].startswith("no-advisories-published")
|
||||
]
|
||||
usable_sources = [s for s in report["sources"] if s["status"] == "ok"]
|
||||
no_sources = not usable_sources
|
||||
if no_sources:
|
||||
report["no_usable_sources"] = True
|
||||
report["count_known"] = not unresolved_any and not no_sources
|
||||
report["cve_count_fixed"] = (len(fixed_set)
|
||||
if (not unresolved_any and not no_sources) else None)
|
||||
report["cve_count_total_seen"] = len(report["cves"])
|
||||
# Only GENUINE failures make a count unreliable. "no-advisories-published" (404: the repo has
|
||||
# no advisory feed) and "skipped: template URL" are benign and must not degrade the verdict.
|
||||
report["sources_failed"] = [
|
||||
s["source"]
|
||||
for s in report["sources"]
|
||||
if not (s["status"] == "ok" or s["status"].startswith(("no-advisories-published", "skipped:")))
|
||||
]
|
||||
coverage_failed = bool(report.get("unmatched_image_windows")) or bool(v_from and not primary)
|
||||
report["count_known"] = not unresolved_any and not no_sources and not coverage_failed and not report["sources_failed"]
|
||||
report["cve_count_fixed"] = (len(fixed_set)
|
||||
if report["count_known"] else None)
|
||||
report["cve_count_total_seen"] = len(report["cves"])
|
||||
# Only GENUINE failures make a count unreliable. "no-advisories-published" (404: the repo has
|
||||
# no advisory feed) and "skipped: template URL" are benign and must not degrade the verdict.
|
||||
report["sources_benign"] = [
|
||||
s["source"]
|
||||
for s in report["sources"]
|
||||
@@ -1052,16 +1056,20 @@ def markdown(rep: dict) -> str:
|
||||
f"{rep.get('from') or '?'} → {rep.get('to') or '?'}"]
|
||||
if not rep.get("count_known", True):
|
||||
L.append("\n**CVEs fixed by this upgrade: UNKNOWN — the scan could NOT determine a count.**")
|
||||
if rep.get("fixed_by_this_upgrade"):
|
||||
L.append(f"\nAt least {len(rep['fixed_by_this_upgrade'])} fixed CVE(s) were identified: "
|
||||
+ ", ".join(rep["fixed_by_this_upgrade"][:15]))
|
||||
if rep.get("no_usable_sources"):
|
||||
L.append("\n⚠ This is NOT zero. **No usable source was checked at all** — the registry "
|
||||
"file `cc-ci-plan/upstream/<recipe>.md` is missing or every source failed, so "
|
||||
"nothing was measured. Render this recipe's cve cell as `?`, never `0`, and add "
|
||||
"the registry file.")
|
||||
else:
|
||||
L.append("\n⚠ This is NOT zero. A version-scheme change (e.g. semver → calver) makes "
|
||||
"numeric ordering meaningless across this jump, so no advisory could be "
|
||||
"classified. Render this recipe's cve cell as `?`, never `0`. Read the vendor's "
|
||||
"release notes for the jump and count by hand.")
|
||||
L.append("\n⚠ This is NOT zero. At least one version window or advisory source could "
|
||||
"not be checked completely. Render this recipe's cve cell as `?`, never `0`.")
|
||||
if rep.get("unmatched_image_windows"):
|
||||
L.append("\n⚠ image windows with no matching advisory source: "
|
||||
+ ", ".join(rep["unmatched_image_windows"]))
|
||||
if rep["unclassified"]:
|
||||
L.append(f"\nAdvisories seen but unclassifiable ({len(rep['unclassified'])}) — includes "
|
||||
f"other images in this recipe: " + ", ".join(rep["unclassified"][:12]))
|
||||
|
||||
Reference in New Issue
Block a user