Hand-authored platform/vuln-scanner (non-gating Flux tier, digest-pinned Trivy 0.76.1 and python 3.14.9-alpine, read-only RBAC, per-pod oauth2-proxy, internal-only cve.<domain>), five alerts with promtool tests, R-VULN-01..09 registry entries, and vuln-* L0 checks with mutation self-tests. src/ and the bot workflow are integrated separately. Co-Authored-By: Claude Opus 5.5 <[email protected]>
106 lines
4.4 KiB
Bash
Executable File
106 lines
4.4 KiB
Bash
Executable File
#!/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
|