Skip to content

Commit e8f8f16

Browse files
chore(ci): replace secret-scanner.yml with reusable wrapper (#110)
## Summary Replaces this repo's `secret-scanner.yml` (~75-116 lines) with a thin ~14-line wrapper calling `hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@3e4bd4c93911750727e2e4c66dff859e00079da0` (merged via standards#190). ## Security debt closed The `shell-secrets` job (added to the canonical 2026-05-21 in direct response to the live Cloudflare API token leak via `avow-protocol/deploy-repos.sh` — a leak both `trufflehog --only-verified` and default `gitleaks` missed) was carried by 0 of 16 sampled estate copies. This PR brings that guardrail to this repo. ## Why now Estate audit: 281 `secret-scanner.yml` deployments / 54 unique SHAs / 19% true drift. Drift is pin churn + whitespace; feature variance is near-zero. Converging behind the reusable means the next post-incident guardrail update propagates via one SHA bump. `secrets: inherit` flows `GITHUB_TOKEN` through implicitly so the `gitleaks-action` step doesn't fall back to anonymous (rate-limited) mode. Part of estate-wide convergence campaign 2026-05-26 (standards#199 / #190).
1 parent a376d28 commit e8f8f16

1 file changed

Lines changed: 8 additions & 339 deletions

File tree

Lines changed: 8 additions & 339 deletions
Original file line numberDiff line numberDiff line change
@@ -1,350 +1,19 @@
11
# SPDX-License-Identifier: MPL-2.0
2-
# Prevention workflow - scans for hardcoded secrets before they reach main
32
name: Secret Scanner
43

54
on:
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

1113
permissions:
1214
contents: read
1315

1416
jobs:
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

Comments
 (0)