11# SPDX-License-Identifier: MPL-2.0
2- # Prevention workflow - scans for hardcoded secrets before they reach main
32name : Secret Scanner
43
54on :
6- workflow_dispatch :
75 pull_request :
86 push :
9- branches : ['**']
7+ branches : [main]
8+
9+ concurrency :
10+ group : ${{ github.workflow }}-${{ github.ref }}
11+ cancel-in-progress : true
1012
1113permissions :
1214 contents : read
1315
1416jobs :
15- hypatia-elixir-secrets :
16- runs-on : ubuntu-22.04
17- steps :
18- - uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
19- with :
20- fetch-depth : 0
21-
22- - name : Setup Elixir for Hypatia ruleset
23- uses : erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.18.2
24- with :
25- elixir-version : ' 1.19.4'
26- otp-version : ' 28.3'
27-
28- - name : Clone Hypatia
29- run : git clone --depth 1 https://github.com/hyperpolymath/hypatia.git "$RUNNER_TEMP/hypatia"
30-
31- - name : Run Hypatia Elixir secret ruleset
32- id : hypatia_ruleset
33- working-directory : ${{ runner.temp }}/hypatia
34- env :
35- SCAN_REPO : ${{ github.workspace }}
36- OUTPUT_FILE : ${{ github.workspace }}/hypatia-secret-findings.json
37- run : |
38- set -euo pipefail
39- mix deps.get --only prod
40-
41- cat > "$RUNNER_TEMP/hypatia-secret-scan.exs" <<'ELIXIR'
42- repo = System.fetch_env!("SCAN_REPO")
43- output = System.fetch_env!("OUTPUT_FILE")
44-
45- {stdout, status} = System.cmd("git", ["-C", repo, "ls-files"])
46- if status != 0, do: raise("git ls-files failed")
47-
48- tracked_files = String.split(stdout, "\n", trim: true)
49-
50- findings =
51- tracked_files
52- |> Enum.reject(fn rel_path ->
53- # Known safe test files/docs containing mock credentials
54- rel_path in [
55- "emergency-room/src/zig/capture_test.zig",
56- "czech-file-knife/docs/DISTRIBUTED_FILESYSTEMS.md",
57- "hybrid-automation-router/test/attestation/a2ml_test.exs",
58- "hybrid-automation-router/test/data_plane/transformers/kubernetes_test.exs",
59- "hybrid-automation-router/docs/HAR_SECURITY.md",
60- "cicada/scripts/verify_rsr.jl",
61- "session-sentinel/.envrc"
62- ]
63- end)
64- |> Enum.flat_map(fn rel_path ->
65- abs_path = Path.join(repo, rel_path)
66-
67- case File.stat(abs_path) do
68- {:ok, %File.Stat{type: :regular, size: size}} when size <= 1_000_000 ->
69- case File.read(abs_path) do
70- {:ok, content} ->
71- if String.contains?(content, <<0>>) do
72- []
73- else
74- Hypatia.Rules.SecurityErrors.detect_secrets(content)
75- |> Enum.map(fn label ->
76- %{
77- rule_module: "security_errors",
78- severity: "critical",
79- type: "secret_detected",
80- file: rel_path,
81- reason: "Secret found: #{label}",
82- action: "revoke_rotate_and_purge",
83- detected_at: DateTime.utc_now() |> DateTime.to_iso8601()
84- }
85- end)
86- end
87-
88- _ ->
89- []
90- end
91-
92- _ ->
93- []
94- end
95- end)
96-
97- File.write!(output, Jason.encode!(findings, pretty: true))
98- IO.puts("secret_findings=#{length(findings)}")
99- ELIXIR
100-
101- SCAN_RESULT="$(mix run "$RUNNER_TEMP/hypatia-secret-scan.exs")"
102- echo "$SCAN_RESULT"
103- FINDINGS_COUNT="$(echo "$SCAN_RESULT" | awk -F= '/^secret_findings=/{print $2}' | tail -1)"
104- FINDINGS_COUNT="${FINDINGS_COUNT:-0}"
105- echo "findings_count=$FINDINGS_COUNT" >> "$GITHUB_OUTPUT"
106- echo "Hypatia Elixir secret findings: $FINDINGS_COUNT" >> "$GITHUB_STEP_SUMMARY"
107-
108- - name : Upload Hypatia secret findings
109- if : always()
110- uses : actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
111- with :
112- name : hypatia-secret-findings
113- path : hypatia-secret-findings.json
114- retention-days : 30
115-
116- - name : Dispatch secret alert to gitbot-fleet
117- if : steps.hypatia_ruleset.outputs.findings_count != '0'
118- env :
119- DISPATCH_TOKEN : ${{ secrets.FARM_DISPATCH_TOKEN }}
120- REPO : ${{ github.repository }}
121- REF : ${{ github.ref }}
122- SHA : ${{ github.sha }}
123- RUN_ID : ${{ github.run_id }}
124- FINDINGS_COUNT : ${{ steps.hypatia_ruleset.outputs.findings_count }}
125- run : |
126- set -euo pipefail
127- if [ -z "${DISPATCH_TOKEN:-}" ]; then
128- echo "::warning::FARM_DISPATCH_TOKEN is not configured; skipping gitbot-fleet dispatch."
129- exit 0
130- fi
131-
132- cat > payload.json <<EOF
133- {
134- "event_type": "hypatia-secret-alert",
135- "client_payload": {
136- "source_repo": "${REPO}",
137- "ref": "${REF}",
138- "sha": "${SHA}",
139- "run_id": "${RUN_ID}",
140- "findings_count": "${FINDINGS_COUNT}",
141- "artifact": "hypatia-secret-findings"
142- }
143- }
144- EOF
145-
146- curl -fsSL \
147- -X POST \
148- -H "Authorization: token ${DISPATCH_TOKEN}" \
149- -H "Accept: application/vnd.github+json" \
150- https://api.github.com/repos/hyperpolymath/gitbot-fleet/dispatches \
151- -d @payload.json
152-
153- echo "Dispatched hypatia-secret-alert to gitbot-fleet." >> "$GITHUB_STEP_SUMMARY"
154-
155- - name : Block on secret findings
156- if : steps.hypatia_ruleset.outputs.findings_count != '0'
157- run : |
158- echo "::error::Hypatia Elixir ruleset detected ${{ steps.hypatia_ruleset.outputs.findings_count }} secret finding(s)."
159- echo "::error::Secrets must be revoked/rotated and removed before merge."
160- exit 1
161-
162- trufflehog :
163- runs-on : ubuntu-latest
164- steps :
165- - uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
166- with :
167- fetch-depth : 0 # Full history for scanning
168-
169- - name : TruffleHog Secret Scan
170- id : trufflehog_scan
171- continue-on-error : true
172- uses : trufflesecurity/trufflehog@37b77001d0174ebec2fcca2bd83ff83a6d45a3ab # v3
173- with :
174- # The v3 action injects --fail automatically on pull_request events.
175- # Passing --fail here triggers "flag 'fail' cannot be repeated".
176- extra_args : --only-verified
177-
178- - name : Immediate dispatch (TruffleHog finding)
179- if : steps.trufflehog_scan.outcome == 'failure'
180- env :
181- DISPATCH_TOKEN : ${{ secrets.FARM_DISPATCH_TOKEN }}
182- REPO : ${{ github.repository }}
183- REF : ${{ github.ref }}
184- SHA : ${{ github.sha }}
185- RUN_ID : ${{ github.run_id }}
186- run : |
187- set -euo pipefail
188- if [ -z "${DISPATCH_TOKEN:-}" ]; then
189- echo "::warning::FARM_DISPATCH_TOKEN not configured; skipping gitbot-fleet dispatch."
190- exit 0
191- fi
192-
193- cat > payload.json <<EOF
194- {
195- "event_type": "secret-scanner-alert",
196- "client_payload": {
197- "scanner": "trufflehog",
198- "source_repo": "${REPO}",
199- "ref": "${REF}",
200- "sha": "${SHA}",
201- "run_id": "${RUN_ID}"
202- }
203- }
204- EOF
205-
206- curl -fsSL \
207- -X POST \
208- -H "Authorization: token ${DISPATCH_TOKEN}" \
209- -H "Accept: application/vnd.github+json" \
210- https://api.github.com/repos/hyperpolymath/gitbot-fleet/dispatches \
211- -d @payload.json
212-
213- - name : Fail on TruffleHog finding
214- if : steps.trufflehog_scan.outcome == 'failure'
215- run : |
216- echo "::error::TruffleHog detected verified secret(s)."
217- exit 1
218-
219- gitleaks :
220- runs-on : ubuntu-latest
221- steps :
222- - uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
223- with :
224- fetch-depth : 0
225-
226- - name : Gitleaks Secret Scan
227- id : gitleaks_scan
228- continue-on-error : true
229- uses : gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2
230- env :
231- GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
232-
233- - name : Immediate dispatch (Gitleaks finding)
234- if : steps.gitleaks_scan.outcome == 'failure'
235- env :
236- DISPATCH_TOKEN : ${{ secrets.FARM_DISPATCH_TOKEN }}
237- REPO : ${{ github.repository }}
238- REF : ${{ github.ref }}
239- SHA : ${{ github.sha }}
240- RUN_ID : ${{ github.run_id }}
241- run : |
242- set -euo pipefail
243- if [ -z "${DISPATCH_TOKEN:-}" ]; then
244- echo "::warning::FARM_DISPATCH_TOKEN not configured; skipping gitbot-fleet dispatch."
245- exit 0
246- fi
247-
248- cat > payload.json <<EOF
249- {
250- "event_type": "secret-scanner-alert",
251- "client_payload": {
252- "scanner": "gitleaks",
253- "source_repo": "${REPO}",
254- "ref": "${REF}",
255- "sha": "${SHA}",
256- "run_id": "${RUN_ID}"
257- }
258- }
259- EOF
260-
261- curl -fsSL \
262- -X POST \
263- -H "Authorization: token ${DISPATCH_TOKEN}" \
264- -H "Accept: application/vnd.github+json" \
265- https://api.github.com/repos/hyperpolymath/gitbot-fleet/dispatches \
266- -d @payload.json
267-
268- - name : Fail on Gitleaks finding
269- if : steps.gitleaks_scan.outcome == 'failure'
270- run : |
271- echo "::error::Gitleaks detected secret(s)."
272- exit 1
273-
274- # Rust-specific: Check for hardcoded crypto values
275- rust-secrets :
276- runs-on : ubuntu-latest
277- steps :
278- - uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
279-
280- - name : Check for hardcoded secrets in Rust
281- id : rust_secret_check
282- continue-on-error : true
283- run : |
284- if ! find . -name Cargo.toml -not -path './target/*' -print -quit | grep -q .; then
285- echo 'No Cargo.toml found — skipping Rust secrets check'
286- exit 0
287- fi
288- # Patterns that suggest hardcoded secrets
289- PATTERNS=(
290- 'const.*SECRET.*=.*"'
291- 'const.*KEY.*=.*"[a-zA-Z0-9]{16,}"'
292- 'const.*TOKEN.*=.*"[a-zA-Z0-9\-_]{16,}"'
293- 'let.*api_key.*=.*"'
294- 'HMAC.*"[a-fA-F0-9]{32,}"'
295- 'password.*=.*"[^"]+"'
296- )
297-
298- found=0
299- for pattern in "${PATTERNS[@]}"; do
300- if grep -rn --include="*.rs" -E "$pattern" src/ | grep -v "supersecretvalue123"; then
301- echo "WARNING: Potential hardcoded secret found matching: $pattern"
302- found=1
303- fi
304- done
305-
306- if [ $found -eq 1 ]; then
307- echo "::error::Potential hardcoded secrets detected. Use environment variables instead."
308- exit 1
309- fi
310-
311- - name : Immediate dispatch (Rust secret finding)
312- if : steps.rust_secret_check.outcome == 'failure'
313- env :
314- DISPATCH_TOKEN : ${{ secrets.FARM_DISPATCH_TOKEN }}
315- REPO : ${{ github.repository }}
316- REF : ${{ github.ref }}
317- SHA : ${{ github.sha }}
318- RUN_ID : ${{ github.run_id }}
319- run : |
320- set -euo pipefail
321- if [ -z "${DISPATCH_TOKEN:-}" ]; then
322- echo "::warning::FARM_DISPATCH_TOKEN not configured; skipping gitbot-fleet dispatch."
323- exit 0
324- fi
325-
326- cat > payload.json <<EOF
327- {
328- "event_type": "secret-scanner-alert",
329- "client_payload": {
330- "scanner": "rust-secrets",
331- "source_repo": "${REPO}",
332- "ref": "${REF}",
333- "sha": "${SHA}",
334- "run_id": "${RUN_ID}"
335- }
336- }
337- EOF
338-
339- curl -fsSL \
340- -X POST \
341- -H "Authorization: token ${DISPATCH_TOKEN}" \
342- -H "Accept: application/vnd.github+json" \
343- https://api.github.com/repos/hyperpolymath/gitbot-fleet/dispatches \
344- -d @payload.json
345-
346- - name : Fail on Rust secret finding
347- if : steps.rust_secret_check.outcome == 'failure'
348- run : |
349- echo "::error::Rust hardcoded-secret check failed."
350- exit 1
17+ scan :
18+ uses : hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@3e4bd4c93911750727e2e4c66dff859e00079da0
19+ secrets : inherit
0 commit comments