From 0a58b88cd604d3af05077a670380bdfa0e5e0759 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 7 Sep 2026 03:48:39 +0100 Subject: [PATCH 1/5] fix(ci): gate current Hypatia SARIF and preserve baseline validation --- .github/workflows/hypatia-scan-reusable.yml | 41 ++++++++++++++++++--- scripts/tests/hypatia-blocking-gate-test.sh | 41 +++++++++++++++++++++ 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/.github/workflows/hypatia-scan-reusable.yml b/.github/workflows/hypatia-scan-reusable.yml index bc486448f..388fd7b2c 100644 --- a/.github/workflows/hypatia-scan-reusable.yml +++ b/.github/workflows/hypatia-scan-reusable.yml @@ -7,7 +7,7 @@ on: workflow_call: inputs: block-on-high: - description: Refuse any high or critical finding, without requiring a baseline + description: Refuse current high or critical findings after validated baseline filtering; no baseline is required type: boolean required: false default: false @@ -246,7 +246,7 @@ jobs: retention-days: 90 - name: Gate on baseline (blocking when a baseline is committed) - if: steps.scan.outputs.findings_count > 0 + if: '!inputs.block-on-high && steps.scan.outputs.findings_count > 0' run: | # Wave-8 gate promotion: when the calling repo commits a # .hypatia-baseline.json (schema: .machine_readable/hypatia-baseline. @@ -271,12 +271,43 @@ jobs: if: inputs.block-on-high run: | set -euo pipefail - count=$(jq '[.[] | select(.severity == "high" or .severity == "critical")] | length' hypatia-findings.json) + # Use the scanner's authoritative SARIF projection, after the same + # baseline filter used for the Security tab. Raw JSON also contains + # code_scanning_alerts: historical GitHub alert summaries, including + # alerts fixed by this PR that cannot close until it reaches main. + # Hypatia already excludes those meta-rules in its SARIF renderer. + # Counting them here creates a circular merge dependency. Raw JSON + # remains in the artifact for the fleet's historical-debt review. + # Validate a present baseline even when there are no current errors. + # Advisory mode validates without gating historical meta-findings; + # the final filtered SARIF below supplies the blocking decision. + if [ -f .hypatia-baseline.json ]; then + if [ ! -f scripts/apply-baseline.sh ]; then + echo "::error::A committed baseline requires scripts/apply-baseline.sh" + exit 2 + fi + bash scripts/apply-baseline.sh hypatia-findings.relativized.json .hypatia-baseline.json advisory >/dev/null + fi + # Missing/truncated output must never become a clean security gate. + if ! jq -se ' + length == 1 and (.[0] | + .version == "2.1.0" and (.runs | type == "array" and length > 0) and + all(.runs[]; + .tool.driver.name == "Hypatia" and + (.results | type == "array") and + all(.results[]; + type == "object" and (.ruleId | type == "string" and length > 0) and + (.level as $level | ["error", "warning", "note", "none"] | index($level) != null)))) + ' hypatia.sarif >/dev/null; then + echo "::error::Hypatia did not produce one valid SARIF findings document" + exit 2 + fi + count=$(jq '[.runs[].results[] | select(.level == "error")] | length' hypatia.sarif) if [ "$count" -gt 0 ]; then - echo "::error::Hypatia found $count high or critical finding(s); see the scan artifact" + echo "::error::Hypatia found $count current high or critical finding(s); see the SARIF artifact" exit 1 fi - echo "Hypatia blocking gate: no high or critical findings" + echo "Hypatia blocking gate: no current high or critical findings after baseline filtering" - name: Check for critical issues (ADVISORY — does not gate) if: '!inputs.block-on-high && steps.scan.outputs.critical > 0' diff --git a/scripts/tests/hypatia-blocking-gate-test.sh b/scripts/tests/hypatia-blocking-gate-test.sh index 7d383e73f..b52063a8b 100755 --- a/scripts/tests/hypatia-blocking-gate-test.sh +++ b/scripts/tests/hypatia-blocking-gate-test.sh @@ -25,6 +25,13 @@ check() { printf '%s' "$payload" > hypatia-findings.json fi if bash validate.sh >result.log 2>&1; then + # Fixture for the authoritative renderer's severity projection. The + # separate controls below exercise malformed SARIF and historical echoes. + jq '{version:"2.1.0", runs:[{tool:{driver:{name:"Hypatia"}}, results: + [.[] | {ruleId:"hypatia/control/planted", level: + (if .severity == "critical" or .severity == "high" then "error" + elif .severity == "medium" then "warning" else "note" end)}]}]}' \ + hypatia-findings.json > hypatia.sarif if bash gate.sh >>result.log 2>&1; then actual=0; else actual=$?; fi else actual=$? @@ -48,3 +55,37 @@ check 'null is not a findings array' 2 'null' check 'unknown severity refuses' 2 '[{"severity":"unknown"}]' check 'missing severity refuses' 2 '[{}]' check 'multiple JSON documents refuse' 2 '[] []' + +sarif_check() { + local name=$1 expected=$2 payload=$3 actual + if [[ "$payload" = MISSING ]]; then + rm -f hypatia.sarif + else + printf '%s' "$payload" > hypatia.sarif + fi + if bash gate.sh >result.log 2>&1; then actual=0; else actual=$?; fi + if [[ "$actual" -ne "$expected" ]]; then + printf 'FAIL: %s: expected %s, got %s\n' "$name" "$expected" "$actual" + cat result.log + exit 1 + fi + printf 'PASS: %s\n' "$name" +} +clean='{"version":"2.1.0","runs":[{"tool":{"driver":{"name":"Hypatia"}},"results":[]}]}' +printf '%s' '[{"rule_module":"code_scanning_alerts","type":"CSA003","severity":"high"}]' > hypatia-findings.json +sarif_check 'historical echo does not block a clean current scan' 0 "$clean" +sarif_check 'missing SARIF refuses' 2 MISSING +sarif_check 'empty SARIF refuses' 2 '' +sarif_check 'truncated SARIF refuses' 2 '{"version":' +sarif_check 'multiple SARIF documents refuse' 2 "$clean $clean" +sarif_check 'empty runs refuse' 2 '{"version":"2.1.0","runs":[]}' +sarif_check 'missing results refuse' 2 '{"version":"2.1.0","runs":[{"tool":{"driver":{"name":"Hypatia"}}}]}' +sarif_check 'unknown result level refuses' 2 '{"version":"2.1.0","runs":[{"tool":{"driver":{"name":"Hypatia"}},"results":[{"ruleId":"control","level":"unknown"}]}]}' +printf '%s' '[]' > .hypatia-baseline.json +sarif_check 'baseline without validator refuses' 2 "$clean" +mkdir scripts +cp "$repo/scripts/apply-baseline.sh" scripts/apply-baseline.sh +printf '%s' '[]' > hypatia-findings.relativized.json +sarif_check 'valid baseline accepted' 0 "$clean" +printf '%s' '{}' > .hypatia-baseline.json +sarif_check 'malformed baseline refuses even without findings' 2 "$clean" From 3bc7de609f3fea6ddb5d584010e7568fba40686f Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 7 Sep 2026 03:56:16 +0100 Subject: [PATCH 2/5] fix(ci): require evidence that baseline filtering completed --- .github/workflows/hypatia-scan-reusable.yml | 7 +++++++ scripts/filter-sarif-by-baseline.sh | Bin 4619 -> 4765 bytes .../tests/filter-sarif-by-baseline-test.sh | 16 ++++++++++++++++ scripts/tests/hypatia-blocking-gate-test.sh | 2 ++ 4 files changed, 25 insertions(+) diff --git a/.github/workflows/hypatia-scan-reusable.yml b/.github/workflows/hypatia-scan-reusable.yml index 388fd7b2c..08db03550 100644 --- a/.github/workflows/hypatia-scan-reusable.yml +++ b/.github/workflows/hypatia-scan-reusable.yml @@ -182,6 +182,7 @@ jobs: sparse-checkout-cone-mode: false - name: Filter SARIF through the baseline before upload + id: filter_baseline if: always() run: | # ⚠ WITHOUT THIS, ACKNOWLEDGING A FINDING DOES NOT UNBLOCK ANYTHING. @@ -269,6 +270,8 @@ jobs: - name: Block high and critical findings when requested id: blocking-findings if: inputs.block-on-high + env: + HYPATIA_BASELINE_FILTERED: ${{ steps.filter_baseline.outputs.filtered }} run: | set -euo pipefail # Use the scanner's authoritative SARIF projection, after the same @@ -287,6 +290,10 @@ jobs: exit 2 fi bash scripts/apply-baseline.sh hypatia-findings.relativized.json .hypatia-baseline.json advisory >/dev/null + if [ "${HYPATIA_BASELINE_FILTERED:-}" != "true" ]; then + echo "::error::Baseline validation passed but SARIF filtering did not complete" + exit 2 + fi fi # Missing/truncated output must never become a clean security gate. if ! jq -se ' diff --git a/scripts/filter-sarif-by-baseline.sh b/scripts/filter-sarif-by-baseline.sh index 98d61b2df1b36d5be7e6f62faf4fa22614b4e01c..406cf28b5aca8c27dca2a0a9a2d3acbbdd7dc4ce 100755 GIT binary patch delta 154 zcmeBHnX9_t5|2x?f^L<9l1jC^XNX6rQ@np@NI+{&giVNz&_5*GlV?k`*b delta 16 XcmbQM+O4wT63^s)+}xYhdG%NUH-rU& diff --git a/scripts/tests/filter-sarif-by-baseline-test.sh b/scripts/tests/filter-sarif-by-baseline-test.sh index 1a67fdbff..aa0bf07ef 100755 --- a/scripts/tests/filter-sarif-by-baseline-test.sh +++ b/scripts/tests/filter-sarif-by-baseline-test.sh @@ -93,5 +93,21 @@ echo '[{"severity":"low","rule_module":"code_safety","type":"unwrap_without_chec bash "$S" "$T/in.sarif" "$T/f.json" "$T/b.json" "$T/out.sarif" >/dev/null 2>&1 ck "an empty SARIF survives filtering" 0 "$(count "$T/out.sarif")" +# The blocking workflow needs affirmative evidence that filtering completed. +export GITHUB_OUTPUT="$T/filter-output" +: > "$GITHUB_OUTPUT" +mk_sarif; mk_findings +bash "$S" "$T/in.sarif" "$T/f.json" "$T/b.json" "$T/out.sarif" > "$T/filter.log" 2>&1 +ck "successful filtering is reported" 'filtered=true' "$(cat "$GITHUB_OUTPUT")" +: > "$GITHUB_OUTPUT" +bash "$S" "$T/in.sarif" "$T/f.json" "$T/bad.json" "$T/out.sarif" > "$T/filter.log" 2>&1 +ck "invalid baseline cannot report filtering success" '' "$(cat "$GITHUB_OUTPUT")" +: > "$GITHUB_OUTPUT" +APPLY_BASELINE="$T/missing-validator" bash "$S" "$T/in.sarif" "$T/f.json" "$T/b.json" "$T/out.sarif" > "$T/filter.log" 2>&1 +ck "missing validator cannot report filtering success" '' "$(cat "$GITHUB_OUTPUT")" +: > "$GITHUB_OUTPUT" +bash "$S" "$T/in.sarif" "$T/empty.json" "$T/b.json" "$T/out.sarif" > "$T/filter.log" 2>&1 +ck "completed zero-match filtering is reported" 'filtered=true' "$(cat "$GITHUB_OUTPUT")" + printf '\n %d passed, %d failed\n' "$pass" "$fail" [ "$fail" -eq 0 ] diff --git a/scripts/tests/hypatia-blocking-gate-test.sh b/scripts/tests/hypatia-blocking-gate-test.sh index b52063a8b..031a9f78d 100755 --- a/scripts/tests/hypatia-blocking-gate-test.sh +++ b/scripts/tests/hypatia-blocking-gate-test.sh @@ -86,6 +86,8 @@ sarif_check 'baseline without validator refuses' 2 "$clean" mkdir scripts cp "$repo/scripts/apply-baseline.sh" scripts/apply-baseline.sh printf '%s' '[]' > hypatia-findings.relativized.json +sarif_check 'unconfirmed baseline filtering refuses' 2 "$clean" +export HYPATIA_BASELINE_FILTERED=true sarif_check 'valid baseline accepted' 0 "$clean" printf '%s' '{}' > .hypatia-baseline.json sarif_check 'malformed baseline refuses even without findings' 2 "$clean" From 3c3acc19951b39c8ffe68b917aae4cc2e5b232e5 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 7 Sep 2026 08:46:15 +0100 Subject: [PATCH 3/5] Pin the SARIF filter to the audited compatible revision --- .github/workflows/hypatia-scan-reusable.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/hypatia-scan-reusable.yml b/.github/workflows/hypatia-scan-reusable.yml index 08db03550..5fc5b453f 100644 --- a/.github/workflows/hypatia-scan-reusable.yml +++ b/.github/workflows/hypatia-scan-reusable.yml @@ -175,7 +175,9 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: hyperpolymath/standards - ref: main + # This revision includes the filter's successful-completion output. + # Keep the helper immutable and compatible with the strict gate. + ref: 3bc7de609f3fea6ddb5d584010e7568fba40686f path: .standards-checkout sparse-checkout: | scripts From 6b0bfce648cf9b306693cea0af0bd5f6fc9fea0f Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 7 Sep 2026 09:34:43 +0100 Subject: [PATCH 4/5] Match baseline globs literally and reject invalid gate configuration --- scripts/apply-baseline.sh | 22 +++++++++++++++---- scripts/tests/apply-baseline-test.sh | 33 +++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/scripts/apply-baseline.sh b/scripts/apply-baseline.sh index 0916c7bfc..72c1568df 100755 --- a/scripts/apply-baseline.sh +++ b/scripts/apply-baseline.sh @@ -39,6 +39,14 @@ FINDINGS_FILE="${1:-}" BASELINE_FILE="${2:-}" MODE="${3:-advisory}" BLOCKING_THRESHOLD="${BLOCKING_THRESHOLD:-high}" +case "$MODE" in + advisory|blocking) ;; + *) echo "error: invalid baseline mode: $MODE" >&2; exit 2 ;; +esac +case "$BLOCKING_THRESHOLD" in + info|low|medium|high|critical) ;; + *) echo "error: invalid blocking threshold: $BLOCKING_THRESHOLD" >&2; exit 2 ;; +esac TODAY="$(date -u +%Y-%m-%d)" if [[ -z "$FINDINGS_FILE" || -z "$BASELINE_FILE" ]]; then @@ -152,6 +160,15 @@ EXPIRED_COUNT=$((EXPIRED_COUNT - ACTIVE_COUNT)) ANNOTATED="$(jq -n \ --argjson findings "$FINDINGS_JSON" \ --argjson baseline "$ACTIVE_BASELINE" ' + # Tokenise the two supported wildcards; every other character is literal. + # No sentinel substitution: a real filename may contain DOUBLESTAR. + def glob_regex: + [scan("\\*\\*|\\*|[^*]") + | if . == "**" then ".*" + elif . == "*" then "[^/]*" + elif inside(".\\+?^$()[]{}|") then "\\" + . + else . end] + | "\\A" + join("") + "\\z"; # Two captures are essential here: # `f as $finding` — without this, references like `f.file` inside the # select() get re-evaluated against the current baseline entry (the @@ -177,10 +194,7 @@ ANNOTATED="$(jq -n \ | $pat != null and ($finding.file | test( $pat - | gsub("\\*\\*"; "DOUBLESTAR") - | gsub("\\*"; "[^/]*") - | gsub("DOUBLESTAR"; ".*") - | "^" + . + "$" + | glob_regex )) ) ) diff --git a/scripts/tests/apply-baseline-test.sh b/scripts/tests/apply-baseline-test.sh index 3bf2f87f6..dc5a1c13b 100755 --- a/scripts/tests/apply-baseline-test.sh +++ b/scripts/tests/apply-baseline-test.sh @@ -15,7 +15,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -APPLY="$SCRIPT_DIR/../apply-baseline.sh" +APPLY="${APPLY_BASELINE_TARGET:-$SCRIPT_DIR/../apply-baseline.sh}" WORK="$(mktemp -d)" trap 'rm -rf "$WORK"' EXIT @@ -25,7 +25,7 @@ fail=0 assert_status() { local label="$1" findings="$2" baseline="$3" expected="$4" local got - got=$("$APPLY" "$findings" "$baseline" advisory 2>/dev/null \ + got=$(bash "$APPLY" "$findings" "$baseline" advisory \ | jq -r '"\(.findings_suppressed | length),\(.findings_kept | length)"') if [ "$got" = "$expected" ]; then echo "PASS: $label (suppressed,kept=$got)" @@ -98,7 +98,7 @@ assert_status "hyphenated HYP-S009 rule type is valid and matches" \ cat > "$WORK/baseline6-invalid.json" <<'EOF' [{"severity":"medium","rule_module":"implementation_inside_canon","type":"HYP--S009","file":"spec/Cargo.toml"}] EOF -if "$APPLY" "$WORK/findings6.json" "$WORK/baseline6-invalid.json" advisory >/dev/null 2>&1; then +if bash "$APPLY" "$WORK/findings6.json" "$WORK/baseline6-invalid.json" advisory >/dev/null 2>&1; then echo "FAIL: malformed HYP--S009 rule type was accepted" fail=$((fail + 1)) else @@ -106,6 +106,33 @@ else pass=$((pass + 1)) fi +# Regex metacharacters and the former sentinel must remain literal glob text. +for literal in 'src/foo.rs' 'src/a+b(1)[2]{x}^$?.rs' 'src/DOUBLESTAR.rs'; do + jq -n --arg file "$literal" '[{severity:"high",rule_module:"cicd_rules",type:"banned_language_file",file:$file}]' > "$WORK/literal.json" + jq -n --arg pattern "$literal" '[{severity:"high",rule_module:"cicd_rules",type:"banned_language_file",file_pattern:$pattern}]' > "$WORK/literal-baseline.json" + assert_status "literal glob matches itself: $literal" "$WORK/literal.json" "$WORK/literal-baseline.json" "1,0" +done +jq -n '[{severity:"high",rule_module:"cicd_rules",type:"banned_language_file",file_pattern:"src/foo.rs"}]' > "$WORK/literal-baseline.json" +for unrelated in 'src/fooXrs' $'src/foo.rs\nother'; do + jq -n --arg file "$unrelated" '[{severity:"high",rule_module:"cicd_rules",type:"banned_language_file",file:$file}]' > "$WORK/unrelated.json" + assert_status "literal glob rejects unrelated path" "$WORK/unrelated.json" "$WORK/literal-baseline.json" "0,1" +done + +assert_invalid_option() { + local label="$1" mode="$2" threshold="$3" status=0 + BLOCKING_THRESHOLD="$threshold" bash "$APPLY" "$WORK/findings1.json" "$WORK/empty.json" "$mode" > "$WORK/invalid.out" 2> "$WORK/invalid.err" || status=$? + if [ "$status" -eq 2 ]; then + echo "PASS: $label rejected as invalid configuration" + pass=$((pass + 1)) + else + echo "FAIL: $label returned $status (expected 2)" + cat "$WORK/invalid.err" + fail=$((fail + 1)) + fi +} +assert_invalid_option "invalid mode" bypass high +assert_invalid_option "invalid threshold" blocking nonsense + echo echo "Total: $pass passed, $fail failed" [ "$fail" -eq 0 ] From 469605210e767ee94d1c7a9c13cb6a1d0a78cad1 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 7 Sep 2026 09:36:14 +0100 Subject: [PATCH 5/5] Pin shared SARIF filter to validated literal baseline matcher --- .github/workflows/hypatia-scan-reusable.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/hypatia-scan-reusable.yml b/.github/workflows/hypatia-scan-reusable.yml index 5fc5b453f..913d0fc2d 100644 --- a/.github/workflows/hypatia-scan-reusable.yml +++ b/.github/workflows/hypatia-scan-reusable.yml @@ -175,9 +175,9 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: hyperpolymath/standards - # This revision includes the filter's successful-completion output. + # Includes the filter's completion output and literal-glob matching. # Keep the helper immutable and compatible with the strict gate. - ref: 3bc7de609f3fea6ddb5d584010e7568fba40686f + ref: 6b0bfce648cf9b306693cea0af0bd5f6fc9fea0f path: .standards-checkout sparse-checkout: | scripts