Skip to content

Commit 5d8da87

Browse files
Merge branch 'main' into rhodibot/rsr-compliance-20260831
2 parents f364bc6 + 30509d6 commit 5d8da87

6 files changed

Lines changed: 98 additions & 20 deletions

File tree

‎.github/workflows/casket-pages.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,4 @@ jobs:
113113
steps:
114114
- name: Deploy to GitHub Pages
115115
id: deployment
116-
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
116+
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1

‎.github/workflows/codeql.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ jobs:
3838
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
3939

4040
- name: Initialize CodeQL
41-
uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v3
41+
uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v3
4242
with:
4343
languages: ${{ matrix.language }}
4444
build-mode: ${{ matrix.build-mode }}
4545

4646
- name: Perform CodeQL Analysis
47-
uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v3
47+
uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v3
4848
with:
4949
category: "/language:${{ matrix.language }}"

‎.github/workflows/dogfood-gate.yml‎

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ jobs:
119119
# Checks for: zero-width spaces, zero-width joiners, BOM, soft hyphens,
120120
# non-breaking spaces, null bytes, and other invisible Unicode in source files.
121121
set +e
122-
PATTERNS='\xc2\xa0|\xe2\x80\x8b|\xe2\x80\x8c|\xe2\x80\x8d|\xef\xbb\xbf|\xc2\xad|\xe2\x80\x8e|\xe2\x80\x8f|\xe2\x80\xaa|\xe2\x80\xab|\xe2\x80\xac|\xe2\x80\xad|\xe2\x80\xae|\x00'
122+
PATTERNS='(*UTF)[\x00-\x08\x0B\x0C\x0E-\x1F\x{a0}\x{ad}\x{200b}-\x{200f}\x{202a}-\x{202f}\x{2060}\x{2066}-\x{2069}\x{feff}]'
123123
find "$GITHUB_WORKSPACE" \
124124
-not -path '*/.git/*' -not -path '*/node_modules/*' \
125125
-not -path '*/.deno/*' -not -path '*/target/*' \
@@ -130,7 +130,7 @@ jobs:
130130
-o -name '*.yml' -o -name '*.yaml' -o -name '*.md' -o -name '*.adoc' \
131131
-o -name '*.idr' -o -name '*.zig' -o -name '*.v' -o -name '*.jl' \
132132
-o -name '*.gleam' -o -name '*.hs' -o -name '*.ml' -o -name '*.sh' \) \
133-
-exec grep -Prl "$PATTERNS" {} \; > /tmp/empty-lint-results.txt 2>/dev/null
133+
-exec grep -aPrl "$PATTERNS" {} \; > /tmp/empty-lint-results.txt 2>/dev/null
134134
EL_EXIT=$?
135135
set -e
136136
@@ -139,13 +139,41 @@ jobs:
139139
echo "exit_code=$EL_EXIT" >> "$GITHUB_OUTPUT"
140140
echo "ready=true" >> "$GITHUB_OUTPUT"
141141
142+
# Blocking subset: C0 controls and NUL only (owner ruling 2026-08-28).
143+
# Invisible Unicode (NBSP/BOM/zero-width) stays ADVISORY - about 2,100
144+
# estate files carry it as legitimate typography in prose.
145+
blocking=0
146+
while IFS= read -r bf; do
147+
[ -z "$bf" ] && continue
148+
if grep -qaP '\x00|[\x01-\x08\x0B\x0C\x0E-\x1F]' "$bf"; then
149+
blocking=$((blocking+1))
150+
echo "::error file=${bf#$GITHUB_WORKSPACE/}::C0 control characters or NUL bytes - file corruption, blocks the gate"
151+
fi
152+
done < /tmp/empty-lint-results.txt
153+
echo "blocking=$blocking" >> "$GITHUB_OUTPUT"
154+
142155
# Emit annotations for each file with invisible chars
143156
while IFS= read -r filepath; do
144157
[ -z "$filepath" ] && continue
145158
REL_PATH="${filepath#$GITHUB_WORKSPACE/}"
146159
echo "::warning file=${REL_PATH}::Invisible Unicode characters detected (zero-width space, BOM, NBSP, etc.)"
147160
done < /tmp/empty-lint-results.txt
148161
162+
# Enforce (owner ruling 2026-08-28): C0/NUL corruption BLOCKS; other
163+
# invisible Unicode stays advisory. Enforcement lives inside this step
164+
# so a crash above fails the job directly - counts can never arrive
165+
# empty into a separate check that then passes silently.
166+
if [ "$EL_EXIT" -ne 0 ]; then
167+
echo "::warning::invisible-character scan exited $EL_EXIT - results may be incomplete"
168+
fi
169+
if [ "${blocking:-0}" -gt 0 ]; then
170+
echo "## Empty-linter: BLOCKED - $blocking file(s) with C0/NUL corruption" >> "$GITHUB_STEP_SUMMARY"
171+
echo "::error::$blocking file(s) contain C0 control characters or NUL bytes - corruption, not typography. See file annotations."
172+
exit 1
173+
elif [ "${FINDINGS:-0}" -gt 0 ]; then
174+
echo "::notice::$FINDINGS file(s) carry invisible Unicode (NBSP/BOM/zero-width) - advisory only"
175+
fi
176+
149177
- name: Write summary
150178
run: |
151179
if [ "${{ steps.lint.outputs.ready }}" = "true" ]; then

‎.github/workflows/push-email-notify.yml‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,27 @@
33
# PUSH_EMAIL_ENABLED=true (the single on/off switch). Addresses are pre-filled;
44
# sending needs the org SMTP secrets (SMTP_HOST/PORT/USER/PASS). Inherited by
55
# new repos from the template; placed on existing repos by the farm sweep.
6+
#
7+
# Re-landed after the 2026-07-20 notification-storm freeze (removed in
8+
# 09f94c5), now on hyperpolymath/smtp-notify-action: Node-free, the SMTP
9+
# session is Idris2-specified and machine-checked, the binary is Zig-built,
10+
# byte-reproducible, and SHA-256-pinned inside the action itself.
611
name: Push email notification
712
on:
8-
push: {}
13+
push:
14+
# Branch pushes only: tag and deletion payloads mislabel Branch:/head_commit.
15+
branches: ['**']
916
permissions:
1017
contents: read
1118
jobs:
1219
notify:
1320
name: Email on push
1421
if: ${{ vars.PUSH_EMAIL_ENABLED == 'true' }}
1522
runs-on: ubuntu-latest
23+
timeout-minutes: 5
1624
steps:
1725
- name: Send push notification email
18-
uses: dawidd6/action-send-mail@0bbdab096651ee93f37ec02383e088183d41ff0b # pinned
26+
uses: hyperpolymath/smtp-notify-action@ede1191ef6ff3ac02c4f4d9efdf837ee517e11d7 # v0.2.0
1927
with:
2028
server_address: ${{ secrets.SMTP_HOST }}
2129
server_port: ${{ secrets.SMTP_PORT }}

‎.github/workflows/release.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ jobs:
100100
# path: artifacts/
101101

102102
- name: Create GitHub Release
103-
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v2
103+
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v2
104104
with:
105105
body: ${{ needs.changelog.outputs.changelog }}
106106
draft: false

‎.github/workflows/static-analysis-gate.yml‎

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,28 @@ jobs:
4545
if: steps.install.outputs.installed == 'true'
4646
run: |
4747
set +e
48-
panic-attack assail --format json . > panic-attack-findings.json 2>&1
48+
panic-attack assail --format json . > panic-attack-findings.json
4949
PA_EXIT=$?
5050
set -e
5151
52+
# Same defect class as the Hypatia job below: `2>&1` folded the
53+
# scanner's stderr into the JSON payload, so every jq parse failed,
54+
# every count silently became 0 via `|| echo 0`, and "Fail on critical
55+
# findings" could never fire on any input. Keep stderr on the log.
5256
if [ ! -s panic-attack-findings.json ]; then
5357
echo "[]" > panic-attack-findings.json
5458
fi
5559
60+
# Deliberately a WARNING, not a failure. panic-attack is a downloaded
61+
# release binary whose exit-code and output contract are not verified
62+
# here, and it has no confirmed --exit-zero equivalent, so we surface a
63+
# malformed payload in the log rather than block on an unverified tool.
64+
# Promote to `exit 1` (as the Hypatia job does) once that contract is
65+
# confirmed -- see the follow-up issue linked from this PR.
66+
if ! jq -e 'type == "array"' panic-attack-findings.json >/dev/null 2>&1; then
67+
echo "::warning::panic-attack output is not a JSON array (exit ${PA_EXIT}); counts below are unreliable"
68+
fi
69+
5670
# Parse finding counts
5771
TOTAL=$(jq '. | length' panic-attack-findings.json 2>/dev/null || echo 0)
5872
CRITICAL=$(jq '[.[] | select(.severity == "critical")] | length' panic-attack-findings.json 2>/dev/null || echo 0)
@@ -71,13 +85,19 @@ jobs:
7185
if: steps.install.outputs.installed == 'true'
7286
run: |
7387
# Convert JSON findings into GitHub Actions annotations
74-
jq -r '.[] | select(.file != null) |
88+
# Findings carry no `.message` (keys: action,file,line,reason,rule_module,
89+
# severity,type), so every annotation read "null". `.file` is an absolute
90+
# runner path, which GitHub cannot anchor to the diff, so it is made
91+
# workspace-relative here.
92+
jq -r --arg ws "$GITHUB_WORKSPACE" '.[] | select(.file != null) |
93+
(.file | ltrimstr($ws + "/")) as $f |
94+
(.reason // .message // .type // "finding") as $m |
7595
if .severity == "critical" then
76-
"::error file=\(.file),line=\(.line // 1)::[panic-attack] \(.message)"
96+
"::error file=\($f),line=\(.line // 1)::[panic-attack] \($m)"
7797
elif .severity == "high" then
78-
"::error file=\(.file),line=\(.line // 1)::[panic-attack] \(.message)"
98+
"::error file=\($f),line=\(.line // 1)::[panic-attack] \($m)"
7999
else
80-
"::warning file=\(.file),line=\(.line // 1)::[panic-attack] \(.message)"
100+
"::warning file=\($f),line=\(.line // 1)::[panic-attack] \($m)"
81101
end
82102
' panic-attack-findings.json || true
83103
@@ -160,12 +180,28 @@ jobs:
160180
if: steps.build.outputs.ready == 'true'
161181
run: |
162182
set +e
163-
HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . > hypatia-findings.json 2>&1
183+
HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . --exit-zero > hypatia-findings.json
164184
HYP_EXIT=$?
165185
set -e
166186
167-
if [ ! -s hypatia-findings.json ] || ! jq empty hypatia-findings.json 2>/dev/null; then
168-
echo "[]" > hypatia-findings.json
187+
# --exit-zero is Hypatia's own documented CI recipe (lib/hypatia/cli.ex),
188+
# for exactly this case: "use in CI when a downstream step gates on
189+
# severity counts". Findings go to stdout, the one-line summary to
190+
# stderr, and the process exits 0 unless the SCANNER itself failed.
191+
#
192+
# Do NOT redirect stderr into the payload with `2>&1`: that folds the
193+
# summary line into the JSON, so every parse fails, the old `[]`
194+
# fallback substituted a clean result, CRITICAL was always 0, and the
195+
# gate below could never fire on any input. Keep stderr on the log.
196+
if [ "$HYP_EXIT" -ne 0 ]; then
197+
echo "::error::Hypatia scanner execution failed with exit ${HYP_EXIT}"
198+
exit "$HYP_EXIT"
199+
fi
200+
# `jq empty` is NOT sufficient -- it succeeds on any valid JSON,
201+
# including a bare string, object or null. Assert the array.
202+
if [ ! -s hypatia-findings.json ] || ! jq -e 'type == "array"' hypatia-findings.json >/dev/null; then
203+
echo "::error::Hypatia did not produce a valid JSON findings array"
204+
exit 1
169205
fi
170206
171207
TOTAL=$(jq '. | length' hypatia-findings.json 2>/dev/null || echo 0)
@@ -183,13 +219,19 @@ jobs:
183219
- name: Emit check annotations
184220
if: steps.build.outputs.ready == 'true'
185221
run: |
186-
jq -r '.[] | select(.file != null) |
222+
# Findings carry no `.message` (keys: action,file,line,reason,rule_module,
223+
# severity,type), so every annotation read "null". `.file` is an absolute
224+
# runner path, which GitHub cannot anchor to the diff, so it is made
225+
# workspace-relative here.
226+
jq -r --arg ws "$GITHUB_WORKSPACE" '.[] | select(.file != null) |
227+
(.file | ltrimstr($ws + "/")) as $f |
228+
(.reason // .message // .type // "finding") as $m |
187229
if .severity == "critical" then
188-
"::error file=\(.file),line=\(.line // 1)::[hypatia] \(.message)"
230+
"::error file=\($f),line=\(.line // 1)::[hypatia] \($m)"
189231
elif .severity == "high" then
190-
"::error file=\(.file),line=\(.line // 1)::[hypatia] \(.message)"
232+
"::error file=\($f),line=\(.line // 1)::[hypatia] \($m)"
191233
else
192-
"::warning file=\(.file),line=\(.line // 1)::[hypatia] \(.message)"
234+
"::warning file=\($f),line=\(.line // 1)::[hypatia] \($m)"
193235
end
194236
' hypatia-findings.json || true
195237

0 commit comments

Comments
 (0)