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]>
103 lines
4.0 KiB
Bash
Executable File
103 lines
4.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# L0 static check (offline, no cluster) — vuln-triage-bot-workflow-guards
|
|
# Serves: R-VULN-05 (runs on a schedule, fails loudly), R-VULN-07 (bot conventions, docs/17).
|
|
#
|
|
# GIVEN .gitea/workflows/cve-triage.yaml,
|
|
# THEN (1) it runs on `15 7 * * *` and has a workflow_dispatch `dry_run` input;
|
|
# (2) concurrency is set (the workflow never runs against itself);
|
|
# (3) a step fails the job HARD when RENOVATE_TOKEN is empty (exit non-zero, not skip);
|
|
# AND (4) the evaluator is run on mutated copies and must flag each (non-vacuity).
|
|
# A missing workflow file is a FAIL: this is whole-repo scoped and has no skip path.
|
|
# =============================================================================
|
|
set -euo pipefail
|
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
ROOT="$(cd "${DIR}/../../../.." && pwd)"
|
|
exec python3 - "$ROOT" <<'PY'
|
|
import copy, re, sys
|
|
from pathlib import Path
|
|
import yaml
|
|
|
|
ROOT = Path(sys.argv[1])
|
|
NAME = "vuln-triage-bot-workflow-guards"
|
|
FAILURES, CHECKS = [], 0
|
|
WF = ROOT / ".gitea" / "workflows" / "cve-triage.yaml"
|
|
|
|
|
|
def check(cond, desc):
|
|
global CHECKS
|
|
CHECKS += 1
|
|
if not cond:
|
|
FAILURES.append(desc)
|
|
|
|
|
|
if not WF.is_file():
|
|
print(f"CHECK FAIL ({NAME}): .gitea/workflows/cve-triage.yaml is absent (the BOT's deliverable); "
|
|
f"whether it fails on a missing credential cannot be asserted without it", file=sys.stderr)
|
|
raise SystemExit(1)
|
|
doc = yaml.safe_load(WF.read_text())
|
|
|
|
|
|
def evaluate(doc):
|
|
bad = []
|
|
triggers = doc.get(True) if True in doc else doc.get("on") # YAML reads `on:` as boolean True
|
|
if not isinstance(triggers, dict):
|
|
return ["no triggers parsed"]
|
|
if "15 7 * * *" not in [s.get("cron") for s in triggers.get("schedule") or [] if isinstance(s, dict)]:
|
|
bad.append("no schedule with cron 15 7 * * *")
|
|
inputs = ((triggers.get("workflow_dispatch") or {}).get("inputs") or {})
|
|
if "dry_run" not in inputs:
|
|
bad.append("workflow_dispatch has no dry_run input")
|
|
conc = doc.get("concurrency")
|
|
jobs = doc.get("jobs") or {}
|
|
if not conc and not any(isinstance(j, dict) and j.get("concurrency") for j in jobs.values()):
|
|
bad.append("no concurrency set (the bot must not run against itself)")
|
|
hard_fail = False
|
|
for j in jobs.values():
|
|
for step in (j.get("steps") or []) if isinstance(j, dict) else []:
|
|
run = str(step.get("run", ""))
|
|
if ("RENOVATE_TOKEN" in run and re.search(r"(-z|!\s*-n)[^\n]*RENOVATE_TOKEN|RENOVATE_TOKEN[^\n]*(-z)", run)
|
|
and re.search(r"exit\s+[1-9]", run)):
|
|
hard_fail = True
|
|
if not hard_fail:
|
|
bad.append("no step fails hard (exit non-zero) when RENOVATE_TOKEN is empty")
|
|
return bad
|
|
|
|
|
|
for v in evaluate(doc):
|
|
check(False, f"(1-3) {v}")
|
|
check(isinstance(doc.get("jobs"), dict) and doc["jobs"], "(1) the workflow declares jobs")
|
|
|
|
# (4) non-vacuity
|
|
def mutate(fn):
|
|
m = copy.deepcopy(doc)
|
|
fn(m)
|
|
return m
|
|
|
|
def trig(m):
|
|
return m.get(True) if True in m else m.get("on")
|
|
|
|
def no_cron(m): trig(m).pop("schedule", None)
|
|
def no_dry(m): trig(m)["workflow_dispatch"] = {}
|
|
def no_conc(m):
|
|
m.pop("concurrency", None)
|
|
for j in m.get("jobs", {}).values():
|
|
if isinstance(j, dict):
|
|
j.pop("concurrency", None)
|
|
def no_guard(m):
|
|
for j in m.get("jobs", {}).values():
|
|
for step in j.get("steps") or []:
|
|
if "RENOVATE_TOKEN" in str(step.get("run", "")):
|
|
step["run"] = 'echo "::warning::RENOVATE_TOKEN unset, skipping"'
|
|
for name, fn in (("cron removed", no_cron), ("dry_run input removed", no_dry),
|
|
("concurrency removed", no_conc), ("credential check softened", no_guard)):
|
|
check(bool(evaluate(mutate(fn))), f"(4) evaluator missed mutant: {name}")
|
|
|
|
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
|