advisory-scan: --image NAME FROM TO replaces --window KEY=FROM:TO

The image name was packed into the value, so the flag needed a hand-rolled
KEY=FROM:TO parser with its own malformed-input branch, and 'window' named the
wrong thing — the tool has two kinds of window (version ranges and, on the date
fallback, real date windows) and the flag meant only the first.

Now each part is its own argument: --image redis 7.4 8.10, repeatable, all in
one call. argparse enforces the arity, so the string parsing and its error path
are deleted. 'windows' survives internally as the computed-range concept.

Counts unchanged: discourse 128 with redis / 123 without, gitea 2.
This commit is contained in:
autonomic-bot
2026-08-11 00:51:12 +00:00
parent 8d7320f32e
commit 65bf3c095b
3 changed files with 39 additions and 32 deletions
+13 -18
View File
@@ -284,7 +284,7 @@ def osv(recipe: str, version: str | None) -> dict | None:
def scan(recipe: str, v_from: str | None, v_to: str | None, registry_dir: str,
extra_windows: list[tuple[str, str, str]] | None = None) -> dict:
images: list[tuple[str, str, str]] | None = None) -> dict:
urls, reg_path = registry_urls(recipe, registry_dir)
report: dict = {
"recipe": recipe,
@@ -352,8 +352,9 @@ def scan(recipe: str, v_from: str | None, v_to: str | None, registry_dir: str,
# false 133 (34 of them redis CVEs, incl. one patched in redis 6.0.11 in 2021). So each source
# is classified against ITS OWN window, and the count is the union across windows.
#
# --from/--to → the PRIMARY app repo (first github source in the registry)
# --window K=F:T → any other source whose name contains K (repeatable), e.g. redis=7.4:8.10
# --from/--to → the PRIMARY app repo (first github source in the registry)
# --image NAME FROM TO → any other source whose name contains NAME (repeatable),
# e.g. --image redis 7.4 8.10
#
# A source with no window is not classified: its advisories are listed as unclassified so they
# stay visible without inflating the count.
@@ -364,7 +365,7 @@ def scan(recipe: str, v_from: str | None, v_to: str | None, registry_dir: str,
windows = {} # source name -> (from, to)
if primary:
windows[primary] = (v_from, v_to)
for key, wf, wt in (extra_windows or []):
for key, wf, wt in (images or []):
for src in gh_sources:
if key.lower() in src.lower() and src not in windows:
windows[src] = (wf, wt)
@@ -414,7 +415,7 @@ def scan(recipe: str, v_from: str | None, v_to: str | None, registry_dir: str,
if cve in fixed_set:
continue
if not any(src in e["sources"] for src in windows):
e["classification"] = "unclassified: no version window given for this image"
e["classification"] = "unclassified: no versions given for this image"
unknown.append(cve)
else:
e.setdefault("classification", "outside-window")
@@ -505,20 +506,14 @@ def main() -> int:
ap.add_argument("--to", dest="v_to", default=None)
ap.add_argument("--json", action="store_true", help="emit raw JSON instead of markdown")
ap.add_argument("--registry", default=REGISTRY_DIR)
ap.add_argument("--window", action="append", default=[], metavar="KEY=FROM:TO",
help="extra image window, e.g. --window redis=7.4:8.10 (repeatable). "
"KEY matches a source repo name; its advisories are then counted "
"against ITS OWN bump instead of being left unclassified.")
ap.add_argument("--image", action="append", default=[], nargs=3,
metavar=("NAME", "FROM", "TO"),
help="a sidecar image and the versions it moved between, e.g. "
"--image redis 7.4 8.10 (repeatable). NAME matches a source repo name; "
"its advisories are then counted against ITS OWN versions instead of "
"being left unclassified.")
a = ap.parse_args()
wins = []
for w in a.window:
key, _, rng = w.partition('=')
wf, _, wt = rng.partition(':')
if key and wf and wt:
wins.append((key, wf, wt))
else:
print(f'ignoring malformed --window {w!r} (expected KEY=FROM:TO)', file=sys.stderr)
rep = scan(a.recipe, a.v_from, a.v_to, a.registry, wins)
rep = scan(a.recipe, a.v_from, a.v_to, a.registry, [tuple(x) for x in a.image])
print(json.dumps(rep, indent=2) if a.json else markdown(rep))
return 0