Skip to content

Commit 0de4365

Browse files
author
repowarden[bot]
committed
chore(security): add betterleaks secret scanning
Co-Authored-By: repowarden[bot] <bot@repowarden.dev>
1 parent d0a81b1 commit 0de4365

2 files changed

Lines changed: 151 additions & 0 deletions

File tree

.betterleaks.toml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Generated by RepoWarden. Extend betterleaks' built-in detectors with a
2+
# conservative allowlist so we don't trip on test fixtures and public IDs.
3+
# The allowlist intentionally covers the same ground as RepoWarden's runner
4+
# filter (apps/runner/src/secret-scan-filter.ts) — if the two drift apart,
5+
# CI will start flagging findings RepoWarden has already cleared.
6+
title = "RepoWarden betterleaks config"
7+
8+
[extend]
9+
useDefault = true
10+
11+
[allowlist]
12+
description = "RepoWarden default allowlist"
13+
paths = [
14+
'''(^|/)\.env\.example($|\.|\b)''',
15+
'''(^|/)\.env\.sample($|\.|\b)''',
16+
'''(^|/)\.env\.template($|\.|\b)''',
17+
'''(^|/)\.env\.test($|\.|\b)''',
18+
'''(^|/)\.env\.testing($|\.|\b)''',
19+
'''(^|/)\.env\.local($|\.|\b)''',
20+
'''(^|/)\.env\.development($|\.|\b)''',
21+
'''(^|/)\.env\.dev($|\.|\b)''',
22+
'''(^|/)__fixtures__/''',
23+
'''(^|/)__snapshots__/''',
24+
'''(^|/)__mocks__/''',
25+
'''(^|/)fixtures?/''',
26+
'''(^|/)mocks?/''',
27+
'''(^|/)testdata/''',
28+
# JS/TS-style test directories
29+
'''(^|/)tests?/''',
30+
# .NET test-project conventions: `*.Tests/`, `*.UnitTests/`, `*.IntegrationTests/`
31+
'''(^|/)[^/]*\.(Tests|UnitTests|IntegrationTests|Specs|Spec)/''',
32+
# .NET environment-specific config — Development/Local are not shipped to prod
33+
'''(^|/)appsettings\.(Development|Local|Test|Testing|Staging|Example|Sample)\.json$''',
34+
'''(^|/)appsettings\..*\.example\.json$''',
35+
# Generic example/template config conventions across languages
36+
'''\.(example|sample|template)(\.[A-Za-z0-9]+)?$''',
37+
'''(^|/)[^/]*\.example\.[A-Za-z0-9]+$''',
38+
'''(^|/)test/.*\.(snap|fixture)$''',
39+
'''(^|/)CHANGELOG(\.md)?$''',
40+
'''(^|/)package-lock\.json$''',
41+
'''(^|/)pnpm-lock\.yaml$''',
42+
'''(^|/)yarn\.lock$''',
43+
'''(^|/)Cargo\.lock$''',
44+
'''(^|/)go\.sum$''',
45+
'''(^|/)poetry\.lock$''',
46+
'''(^|/)Pipfile\.lock$''',
47+
'''(^|/)Gemfile\.lock$''',
48+
'''(^|/)composer\.lock$''',
49+
# .NET package lock / dependency manifests
50+
'''(^|/)packages\.lock\.json$''',
51+
]
52+
regexes = [
53+
# Google Tag Manager IDs are public by design
54+
'''GTM-[A-Z0-9]{4,10}''',
55+
# Google Analytics measurement IDs (UA-... and G-...) are public
56+
'''UA-\d{4,10}-\d{1,4}''',
57+
'''G-[A-Z0-9]{6,12}''',
58+
# PostHog / Segment / Mixpanel public write keys are intentionally shipped
59+
'''phc_[A-Za-z0-9]{20,}''',
60+
# Sentry DSNs published to clients are public
61+
'''https://[a-f0-9]{32}@(o\d+\.ingest\.)?sentry\.io''',
62+
# Stripe publishable keys (pk_live_/pk_test_) are public
63+
'''pk_(live|test)_[A-Za-z0-9]{20,}''',
64+
# Generic "publishable"/"public" prefix conventions
65+
'''(?i)\bpub(lic)?_[A-Za-z0-9_-]{10,}''',
66+
]
67+
stopwords = [
68+
"example",
69+
"sample",
70+
"placeholder",
71+
"dummy",
72+
"fake",
73+
"test",
74+
"fixture",
75+
"your-",
76+
"your_",
77+
"yourkey",
78+
"yourtoken",
79+
"xxxxxxxx",
80+
"0000000000",
81+
"1234567890",
82+
"changeme",
83+
"replace-me",
84+
"replace_me",
85+
"todo",
86+
"redacted",
87+
]

.github/workflows/betterleaks.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: Secret scanning (betterleaks)
2+
3+
on:
4+
push:
5+
branches: ["**"]
6+
pull_request:
7+
branches: ["**"]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
betterleaks:
14+
name: betterleaks
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
- name: Install betterleaks
19+
run: |
20+
set -euo pipefail
21+
VERSION=1.1.2
22+
ARCH=$(uname -m)
23+
case "$ARCH" in
24+
x86_64) ARCH_TAG=x64 ;;
25+
aarch64|arm64) ARCH_TAG=arm64 ;;
26+
*) echo "Unsupported arch: $ARCH" && exit 1 ;;
27+
esac
28+
curl -fsSL --retry 3 --max-time 60 \
29+
"https://github.com/betterleaks/betterleaks/releases/download/v${VERSION}/betterleaks_${VERSION}_linux_${ARCH_TAG}.tar.gz" \
30+
| tar -xz -C /usr/local/bin betterleaks
31+
chmod +x /usr/local/bin/betterleaks
32+
- name: Run betterleaks
33+
id: scan
34+
run: |
35+
set +e
36+
# --exit-code 0: non-blocking. The scan still runs and reports every
37+
# finding (job summary + uploaded artifact + RepoWarden kanban), but it
38+
# never fails CI — so the "add secret scanning" PR lands cleanly instead
39+
# of leaving the user with a red check they can't merge past.
40+
ARGS=(dir . --redact --report-format json --report-path betterleaks-report.json --exit-code 0)
41+
if [ -f .betterleaks-baseline.json ]; then
42+
ARGS+=(--baseline-path .betterleaks-baseline.json)
43+
fi
44+
betterleaks "${ARGS[@]}"
45+
rc=$?
46+
if [ -f betterleaks-report.json ] && [ "$(jq 'length' betterleaks-report.json 2>/dev/null || echo 0)" -gt 0 ]; then
47+
{
48+
echo "## Secret-scan findings"
49+
echo ""
50+
echo "Betterleaks flagged the entries below (this check is **non-blocking** — it won't fail CI). Either rotate + remove the secret, or — if it's genuinely a false positive — add its fingerprint to \`.betterleaks-baseline.json\` (or extend the allowlist in \`.betterleaks.toml\`)."
51+
echo ""
52+
echo "| Rule | File | Line | Fingerprint |"
53+
echo "| --- | --- | --- | --- |"
54+
jq -r '.[] | "| \(.RuleID) | \(.File) | \(.StartLine) | \(.Fingerprint // "n/a") |"' betterleaks-report.json
55+
} >> "$GITHUB_STEP_SUMMARY"
56+
fi
57+
exit $rc
58+
- name: Upload betterleaks report
59+
if: always() && hashFiles('betterleaks-report.json') != ''
60+
uses: actions/upload-artifact@v4
61+
with:
62+
name: betterleaks-report
63+
path: betterleaks-report.json
64+
if-no-files-found: ignore

0 commit comments

Comments
 (0)