-
Notifications
You must be signed in to change notification settings - Fork 11
197 lines (181 loc) · 9.03 KB
/
Copy pathsecret-scan.yml
File metadata and controls
197 lines (181 loc) · 9.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
name: Secret Scan
# Blocks a push or PR that introduces a secret. This complements GitHub's own secret scanning
# and push protection (both enabled on this repo): GitHub catches recognised provider tokens,
# gitleaks catches the generic shapes — private keys, connection strings, high-entropy blobs.
#
# ⚠️ These jobs scan only the commits being ADDED, never full history, and that is deliberate.
# `azure/install_cert_on_appgw/test-cert.key` is a real (but self-signed, February-2023-expired)
# private key committed in 2022 and knowingly left unsuppressed in .gitleaksignore. A blocking
# full-history scan would therefore fail on every run forever and teach everyone to ignore the
# job. Scanning the incoming range keeps the gate meaningful: it fails only on something new.
#
# The scheduled job below does scan everything, non-blocking, to surface drift.
on:
push:
branches: ['**']
pull_request:
branches: [master]
schedule:
# Weekly, Mondays 02:00 UTC — informational full-history sweep.
- cron: '0 2 * * 1'
workflow_dispatch:
permissions:
contents: read
env:
# Pinned: a new gitleaks release can add rules, and an unpinned scanner turns an unrelated
# push into a red build. Bump deliberately, re-triaging .gitleaksignore at the same time.
GITLEAKS_VERSION: 8.30.0
GITLEAKS_SHA256: 79a3ab579b53f71efd634f3aaf7e04a0fa0cf206b7ed434638d1547a2470a66e
jobs:
scan-new-commits:
name: Scan incoming commits
if: github.event_name == 'push' || github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
# fetch-depth: 0 — a shallow clone has no merge base, so the commit range cannot be resolved.
- uses: actions/checkout@v5
with:
fetch-depth: 0
# The OSS CLI is installed directly rather than via gitleaks/gitleaks-action, which
# requires a paid GITLEAKS_LICENSE for organisation-owned repositories. aehrc is an
# organisation, so the action would fail here; the CLI itself has no such restriction.
- name: Install gitleaks
run: |
set -euo pipefail
url="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
curl -sSfL "$url" -o gitleaks.tar.gz
echo "${GITLEAKS_SHA256} gitleaks.tar.gz" | sha256sum -c -
tar -xzf gitleaks.tar.gz gitleaks
sudo install -m 0755 gitleaks /usr/local/bin/gitleaks
gitleaks version
- name: Resolve the commit range
id: range
env:
EVENT: ${{ github.event_name }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
BEFORE: ${{ github.event.before }}
SHA: ${{ github.sha }}
run: |
set -euo pipefail
if [ "$EVENT" = "pull_request" ]; then
# Compare against the merge base, not the base tip, or commits already on master
# get rescanned and an unrelated finding fails someone else's PR.
base="$(git merge-base "$BASE_SHA" "$SHA")"
range="${base}..${SHA}"
elif [ -n "${BEFORE:-}" ] && [ "$BEFORE" != "0000000000000000000000000000000000000000" ] \
&& git cat-file -e "${BEFORE}^{commit}" 2>/dev/null; then
range="${BEFORE}..${SHA}"
elif git cat-file -e "origin/master^{commit}" 2>/dev/null \
&& [ -n "$(git merge-base origin/master "$SHA" 2>/dev/null)" ]; then
# `before` is all-zeros on a newly created branch and names a discarded commit after a
# force-push; neither can be diffed. Diverging from master covers every commit the
# branch actually adds, which a single-commit fallback would miss.
base="$(git merge-base origin/master "$SHA")"
echo "No usable 'before' ref; diffing against master's merge base instead."
range="${base}..${SHA}"
else
# Last resort. `-1 <sha>` limits git log to that one commit — a bare "$SHA" would
# mean every ancestor, i.e. the whole history, which is exactly what must not happen.
echo "No merge base available; scanning this commit only."
range="-1 ${SHA}"
fi
echo "range=$range" >> "$GITHUB_OUTPUT"
echo "Scanning range: $range"
git rev-list --count "$range" 2>/dev/null | sed 's/^/commits in range: /' || true
- name: gitleaks
run: |
set -euo pipefail
# --redact so a leaked value is never echoed into a public build log.
gitleaks git . \
--no-banner \
--redact \
--report-format sarif \
--report-path gitleaks.sarif \
--log-opts "${{ steps.range.outputs.range }}"
- name: Explain a failure
if: failure()
run: |
{
echo "### Secret scan failed"
echo
echo "gitleaks found a potential secret in the commits added by this push or PR."
echo
echo "1. **Treat the value as compromised and rotate it** — rewriting the commit does"
echo " not help once it has been pushed to a public repository."
echo "2. Remove it from the code and use a GitHub Actions secret, an ExternalSecret,"
echo " or the gitignored \`.env\` for local-only credentials."
echo "3. If it is a **false positive**, add its fingerprint to \`.gitleaksignore\`"
echo " with a comment explaining why. Reproduce locally with:"
echo ' ```'
echo " gitleaks git . --no-banner --redact --log-opts \"${{ steps.range.outputs.range }}\""
echo ' ```'
echo
echo "Note that fingerprints embed line numbers, so an existing entry can go stale"
echo "when a file is edited. Re-triage rather than blindly re-adding."
} >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@v4
if: always() && hashFiles('gitleaks.sarif') != ''
with:
name: gitleaks-sarif
path: gitleaks.sarif
retention-days: 14
full-history:
name: Full history sweep (informational)
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Install gitleaks
run: |
set -euo pipefail
url="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
curl -sSfL "$url" -o gitleaks.tar.gz
echo "${GITLEAKS_SHA256} gitleaks.tar.gz" | sha256sum -c -
tar -xzf gitleaks.tar.gz gitleaks
sudo install -m 0755 gitleaks /usr/local/bin/gitleaks
# Not a gate: this is EXPECTED to report the known expired test cert. It exists so that a
# change in the count is visible. Compare against the documented baseline below.
- name: Scan every branch and tag
run: |
set -uo pipefail
git fetch --all --tags --quiet || true
gitleaks git . --no-banner --redact \
--report-format json --report-path full.json --log-opts "--all" || true
count="$(python3 -c 'import json;print(len(json.load(open("full.json"))))' 2>/dev/null || echo 0)"
# Baseline triaged 2026-08-12: 1 finding — azure/install_cert_on_appgw/test-cert.key,
# a self-signed private key expired Feb 2023, knowingly left unsuppressed.
baseline=1
# Built with a one-liner rather than a heredoc: a heredoc terminator has to sit at
# column 0, which would close this YAML block scalar and break the workflow file.
python3 -c 'import json;d=json.load(open("full.json"));print("| Rule | File |\n|---|---|") or [print("| `%s` | `%s` |" % (f.get("RuleID","?"),f.get("File","?"))) for f in d] if d else None' > table.md 2>/dev/null || : > table.md
{
echo "### Full-history secret sweep"
echo
echo "| | |"
echo "|---|---|"
echo "| Findings | ${count} |"
echo "| Triaged baseline | ${baseline} |"
echo
if [ "$count" -gt "$baseline" ]; then
echo "⚠️ **Above baseline.** Something new is present in history, or a"
echo ".gitleaksignore fingerprint went stale after a file edit. Investigate:"
echo '```'
echo 'gitleaks git . --no-banner --redact --log-opts "--all"'
echo '```'
elif [ "$count" -lt "$baseline" ]; then
echo "✅ **Below baseline** — a known finding was resolved. Update \`baseline\`"
echo "in this workflow and the note in \`.gitleaksignore\`."
else
echo "✅ Matches the triaged baseline; no drift."
fi
echo
cat table.md
} >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@v4
if: always() && hashFiles('full.json') != ''
with:
name: gitleaks-full-history
path: full.json
retention-days: 30