#!/usr/bin/env bash set -euo pipefail # Live CI supply-chain gate. Unlike the deterministic local denylist gate, this # obtains current advisory data and scans the built images. Missing scanners are # an error in strict CI; no "scanner unavailable => green" path exists. # # Usage: live-supply-chain-scan.sh [image-ref ...] # Required tools: npm, osv-scanner, trivy (when CASAN_REQUIRE_LIVE_SCANNERS=1) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/casan-paths.sh" ROOT="$CASAN_APP_ROOT" OUT="${1:-}"; shift || true [[ -n "$OUT" ]] || { echo "LIVE_SCAN_USAGE report-dir [image-ref ...]" >&2; exit 64; } mkdir -p "$OUT" fail() { echo "LIVE_SUPPLY_CHAIN_FAIL $*" >&2; exit 1; } require() { command -v "$1" >/dev/null 2>&1 || fail "tool_missing name=$1"; } require npm if [[ "${CASAN_REQUIRE_LIVE_SCANNERS:-1}" == "1" ]]; then require osv-scanner require trivy fi [[ -f "$ROOT/package-lock.json" ]] || fail "package_lock_missing" cd "$ROOT" # npm audit exits non-zero for findings at/above the requested level. Preserve # JSON evidence even on failure without converting a failed scan into success. set +e npm audit --omit=dev --audit-level=high --json > "$OUT/npm-audit.json" 2> "$OUT/npm-audit.stderr" audit_rc=$? set -e [[ "$audit_rc" -eq 0 ]] || fail "npm_audit_high_or_critical rc=$audit_rc report=$OUT/npm-audit.json" npm sbom --sbom-format cyclonedx --package-lock-only --omit=dev > "$OUT/npm.cyclonedx.json" \ || fail "npm_sbom_failed" python3 - "$OUT/npm.cyclonedx.json" <<'PY' || exit 1 import json, sys d = json.load(open(sys.argv[1])) assert d.get("bomFormat") == "CycloneDX", d PY if command -v osv-scanner >/dev/null 2>&1; then osv-scanner scan source --recursive --format json --output-file "$OUT/osv-source.json" "$ROOT" \ || fail "osv_source_findings_or_scan_error report=$OUT/osv-source.json" fi if command -v trivy >/dev/null 2>&1; then trivy fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL --exit-code 1 --format json --output "$OUT/trivy-source.json" "$ROOT" \ || fail "trivy_source_findings_or_scan_error report=$OUT/trivy-source.json" fi for image in "$@"; do safe="$(printf '%s' "$image" | tr '/:@' '___')" if command -v osv-scanner >/dev/null 2>&1; then osv-scanner scan image --format json --output-file "$OUT/osv-image-${safe}.json" "$image" \ || fail "osv_image_findings_or_scan_error image=$image" fi if command -v trivy >/dev/null 2>&1; then trivy image --severity HIGH,CRITICAL --exit-code 1 --format json --output "$OUT/trivy-image-${safe}.json" "$image" \ || fail "trivy_image_findings_or_scan_error image=$image" fi done echo "LIVE_SUPPLY_CHAIN_PASS sbom=$OUT/npm.cyclonedx.json images=$#"