#!/usr/bin/env bash # ============================================================================= # L0 static check (offline, no cluster) — vuln-acceptances-schema # Serves: R-VULN-08 (docs/17 "Risk acceptance": "An entry with no reason or expiry is rejected by the gate"). # # GIVEN platform/vuln-scanner/acceptances.yaml, # THEN (1) it is a YAML list; # (2) every entry has a non-empty id, a non-empty reason, an expires of the form YYYY-MM-DD # at most 180 days from today, and only the permitted keys (image is optional); # AND (3) the validator rejects mutated negative fixtures for each rule and accepts a good entry. # ============================================================================= set -euo pipefail DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT="$(cd "${DIR}/../../../.." && pwd)" exec python3 - "$ROOT" <<'PY' import datetime as dt, re, sys from pathlib import Path import yaml ROOT = Path(sys.argv[1]) NAME = "vuln-acceptances-schema" FAILURES, CHECKS = [], 0 TODAY = dt.datetime.now(dt.timezone.utc).date() MAX_DAYS = 180 ALLOWED_KEYS = {"id", "image", "reason", "expires"} DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$") def check(cond, desc): global CHECKS CHECKS += 1 if not cond: FAILURES.append(desc) def validate(entries): errs = [] if not isinstance(entries, list): return ["acceptances must be a YAML list"] for i, e in enumerate(entries): if not isinstance(e, dict): errs.append(f"entry {i} is not a mapping") continue if set(e) - ALLOWED_KEYS: errs.append(f"entry {i} has unknown keys {sorted(set(e) - ALLOWED_KEYS)}") for field in ("id", "reason"): if not isinstance(e.get(field), str) or not e[field].strip(): errs.append(f"entry {i} has no non-empty {field}") if "image" in e and (not isinstance(e["image"], str) or not e["image"].strip()): errs.append(f"entry {i} has an empty image") exp = e.get("expires") if isinstance(exp, dt.date) and not isinstance(exp, dt.datetime): exp_s = exp.isoformat() elif isinstance(exp, str): exp_s = exp else: errs.append(f"entry {i} has no expires") continue if not DATE_RE.match(exp_s): errs.append(f"entry {i} expires is malformed: {exp_s!r}") continue try: d = dt.date.fromisoformat(exp_s) except ValueError: errs.append(f"entry {i} expires is not a real date: {exp_s!r}") continue if (d - TODAY).days > MAX_DAYS: errs.append(f"entry {i} expires {exp_s}, more than {MAX_DAYS} days out") return errs path = ROOT / "platform" / "vuln-scanner" / "acceptances.yaml" if not path.is_file(): print(f"CHECK FAIL ({NAME}): platform/vuln-scanner/acceptances.yaml is absent", file=sys.stderr) raise SystemExit(1) entries = yaml.safe_load(path.read_text()) check(entries == [] or (isinstance(entries, list) and entries), "(1) acceptances.yaml parsed to a list") errs = validate(entries) check(not errs, f"(2) acceptances.yaml is valid: {errs}") soon = (TODAY + dt.timedelta(days=30)).isoformat() far = (TODAY + dt.timedelta(days=200)).isoformat() good = {"id": "CVE-2026-00000", "image": "docker.io/library/x", "reason": "not reachable", "expires": soon} check(validate([good]) == [], "(3) a well-formed entry is accepted") check(validate([{k: v for k, v in good.items() if k != "image"}]) == [], "(3) image is optional (platform-wide)") for name, entry in { "no reason": {k: v for k, v in good.items() if k != "reason"}, "empty reason": dict(good, reason=""), "no expires": {k: v for k, v in good.items() if k != "expires"}, "no id": {k: v for k, v in good.items() if k != "id"}, "malformed expiry": dict(good, expires="next-tuesday"), "impossible date": dict(good, expires="2027-13-45"), "past 180 days": dict(good, expires=far), "unknown key": dict(good, owner="me"), }.items(): check(bool(validate([entry])), f"(3) the validator accepted a bad entry: {name}") check(bool(validate({"not": "a list"})), "(3) the validator accepted a non-list") if FAILURES: print(f"CHECK FAIL ({NAME}): {len(FAILURES)} of {CHECKS} assertions failed", file=sys.stderr) for f in FAILURES: print(f" - {f}", file=sys.stderr) raise SystemExit(1) print(f"PASS ({NAME}): {CHECKS} assertions") PY