Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / tools/verify/secret-sweep/sweep.sh |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | tools/verify/secret-sweep/sweep.sh |
| Original SHA-256 | 141cffb53c95b92d17038083646db1e1faab13763ddef5c7a96349a7452844bb |
| Git blob SHA | 8c3159e26b5385b6e1b237f161f59a4f9020393c |
| Reading-copy SHA-256 | d438bea5281cc7a428ed9ce817297739f80d24b95a8547f54e7fed43afa560f9 |
Recovered source transcript
Download complete source text ↓333 lines · 16.8 KB
Credential-bearing and unrelated personal details were removed from this reading copy. The source identity and original checksum remain recorded.
#!/usr/bin/env bash
#
# THE SECRETS SWEEP. Refuses to let a credential into the repository or its history.
#
# It exists because three plaintext passwords were written into docs/BUILD_STATE.md and sat there across
# more than twenty commits before anyone looked. The previous check only looked for API KEY SHAPES, so it
# passed the entire time: sk-proj- prefixed keys were caught and a password on a line saying "password" was
# not. A sweep that only finds the secrets you already thought of is not a sweep.
#
# Usage:
# sweep.sh scan the working tree
# sweep.sh --history scan every commit as well, which is slow and worth it before a push
# sweep.sh --control plant a fake credential, require a FINDING, then remove it
# sweep.sh --out-of-repo also sweep the enumerated ops scripts and units on the hosts, never printing a value
#
# EXIT 0 means clean. EXIT 1 means something was found and is printed with its file and line.
set -uo pipefail
cd "$(git rev-parse --show-toplevel)" || exit 2
CONTROL=0
HISTORY=0
OUTOFREPO=0
for a in "$@"; do
case "$a" in
--control) CONTROL=1 ;;
--history) HISTORY=1 ;;
--out-of-repo) OUTOFREPO=1 ;;
esac
done
# The patterns, each with a name so a finding says WHAT it found rather than just where.
#
# The password shapes are the ones this build actually produced, generalised: an assignment or a labelled
# value whose right hand side looks like a secret rather than a placeholder. They are deliberately a little
# noisy, because a false positive costs a glance and a false negative costs a rotation.
PATTERNS=(
"api key, openai style|sk-proj-[A-Za-z0-9_-]{20,}"
"api key, generic sk|sk-[A-Za-z0-9]{32,}"
"aws access key|AKIA[0-9A-Z]{16}"
"private key block|BEGIN [A-Z ]*PRIVATE KEY"
"password assignment|(password|passwd|pwd)[[:space:]]*[:=][[:space:]]*['\"]?[A-Za-z0-9!@#%^_+=?-]{8,}"
"sshpass inline|sshpass[[:space:]]+-p[[:space:]]*['\"]?[^[:space:]'\"]{6,}"
"credential in a url|://[A-Za-z0-9._%+-]+:[^@/[:space:]]{6,}@"
"dsn with a password|postgres(ql)?://[^:@[:space:]]+:[^@[:space:]]{6,}@"
)
# Placeholders and documented examples are NOT secrets. This list is deliberately short: every entry is a
# hole, and a long allowlist is how a sweep stops meaning anything.
ALLOW='REDACTED|EXAMPLE|example\.(com|invalid|test)|PLACEHOLDER|CHANGE_?ME|your-|<[a-z-]*>|\$\{|\$\(|xxxxx|password_hash|passwordless|password policy|password guessing|password reset|password change|password cap|password input|password verify|password field|password prompt|://user:password@|://mig:secret@|sshpass -p [credential removed]
# Paths that legitimately discuss credential SHAPES: the test fixtures that assert the sweep works, and
# this file itself, which necessarily contains the patterns.
SKIP_PATHS=':!tools/verify/secret-sweep/ :!**/*_test.go :!go.sum'
findings=0
scan_tree() {
for entry in "${PATTERNS[@]}"; do
name="${entry%%|*}"; rx="${entry#*|}"
while IFS= read -r hit; do
[ -z "$hit" ] && continue
echo "$hit" | grep -qE "$ALLOW" && continue
echo " FOUND [$name] $hit"
findings=$((findings + 1))
done < <(git grep --untracked -nIE "$rx" -- . $SKIP_PATHS 2>/dev/null | head -40)
done
}
scan_history() {
echo " scanning history, this takes a moment"
for entry in "${PATTERNS[@]}"; do
name="${entry%%|*}"; rx="${entry#*|}"
while IFS= read -r c; do
[ -z "$c" ] && continue
# THE ALLOWLIST APPLIES HERE TOO. The history scan skipped it entirely, so it reported three findings
# that were a documentation example, a test constant, and the REDACTION TEXT ITSELF: the placeholder
# "[REDACTED, see the secrets store]" is long enough to match the sshpass pattern. A sweep that flags
# its own redactions teaches people to ignore it, which is the failure mode that let the original
# three passwords sit in this ledger for twenty commits.
hit=$(git grep -hoE "$rx" "$c" -- . $SKIP_PATHS 2>/dev/null | head -5)
[ -z "$hit" ] && continue
echo "$hit" | grep -qvE "$ALLOW" || continue
echo " FOUND IN HISTORY [$name] commit $c"
findings=$((findings + 1))
done < <(git log --all --format=%h -S"$rx" --pickaxe-regex -- . $SKIP_PATHS 2>/dev/null | head -10)
done
}
# ==============================================================================
# THE OUT OF REPO SWEEP, finding four. Added 29 July 2026.
# ==============================================================================
#
# The repository sweep above is necessary and was never sufficient: two of the five consumers broken by the D5
# rotation lived in /usr/local/sbin, and one of them held a migrator password in plaintext where this sweep
# could never see it. A clean result meant "clean in the repository" and read as "clean".
#
# THE SCOPE IS ENUMERATED in outofrepo.scope, traced to docs/CREDENTIAL_CONSUMERS.md, so the inventory and the
# sweep can be diffed against each other.
#
# NO VALUE IS EVER PRINTED. The remote half returns KIND, PATH, LINE and PATTERN NAME. Never the matched text.
SCOPE_FILE="$(dirname "$0")/outofrepo.scope"
REMOTE_SCAN="$(dirname "$0")/remote-scan.sh"
oor_host_cmd() {
# $1 host token, $2... command. Kept in one place so the megamind WSL bridge quoting lives here only.
local host="$1"; shift
case "$host" in
bubbles)
ssh -n -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=15 user@100.90.97.104 \
"echo user | sudo -S bash -c $(printf '%q' "$*")" 2>/dev/null ;;
megamind)
# NESTED printf %q THROUGH wsl DOES NOT SURVIVE, which this ledger already recorded once. A single
# unnested invocation does, so the command is passed as one plain string and nothing is re quoted.
sshpass -p [credential removed] ssh -n -o PubkeyAuthentication=no -o PreferredAuthentications=password \
-o StrictHostKeyChecking=no -o ConnectTimeout=15 josep@100.69.120.61 \
"wsl -- sudo -n bash -c \"$*\"" 2>/dev/null ;;
*) return 1 ;;
esac
}
oor_stage() {
local host="$1"
case "$host" in
bubbles)
scp -q -o BatchMode=yes -o StrictHostKeyChecking=no "$REMOTE_SCAN" user@100.90.97.104:/tmp/mm-remote-scan.sh 2>/dev/null &&
oor_host_cmd bubbles "chmod +x /tmp/mm-remote-scan.sh" >/dev/null ;;
megamind)
sshpass -p [credential removed] scp -O -o PubkeyAuthentication=no -o PreferredAuthentications=password \
-o StrictHostKeyChecking=no "$REMOTE_SCAN" josep@100.69.120.61:mm-remote-scan.sh >/dev/null 2>&1 &&
oor_host_cmd megamind "cp /mnt/c/Users/josep/mm-remote-scan.sh /tmp/mm-remote-scan.sh; chmod +x /tmp/mm-remote-scan.sh; echo staged" | grep -q staged ;;
esac
}
scan_out_of_repo() {
[ -r "$SCOPE_FILE" ] || { echo " out of repo scope file missing at $SCOPE_FILE, REFUSING to report clean"; findings=$((findings+1)); return; }
local staged_hosts=""
local scanned=0
# EXPECTED IS COUNTED FROM THE FILE, because "at least one scope answered" is not a check. The first version
# only refused on ZERO, and ssh silently ate the loop.s stdin so exactly ONE scope ran and it reported clean.
local expected
expected=$(grep -cE "^[a-z]" "$SCOPE_FILE")
while IFS=$'\t' read -r host kind glob; do
case "$host" in ''|'#'*) continue ;; esac
[ -z "${kind:-}" ] && continue
case " $staged_hosts " in
*" $host "*) : ;;
*) if oor_stage "$host"; then staged_hosts="$staged_hosts $host"
else echo " out of repo: could NOT reach $host, REFUSING to report clean for it"; findings=$((findings+1)); continue; fi ;;
esac
while IFS=$'\t' read -r rkind rpath rline rname; do
[ -z "${rkind:-}" ] && continue
if [ "$rkind" = "SUMMARY" ]; then scanned=$((scanned+1)); continue; fi
echo " FOUND OUT OF REPO [$rname] $host:$rpath line $rline"
findings=$((findings+1))
done < <(oor_host_cmd "$host" "/tmp/mm-remote-scan.sh $kind '$glob'")
done < "$SCOPE_FILE"
if [ "$scanned" -ne "$expected" ]; then
echo " out of repo: $scanned of $expected enumerated scopes returned a summary. A scope that does not"
echo " answer has not been swept, so this result is NOT clean and is refused."
findings=$((findings+1))
else
echo " out of repo: $scanned of $expected enumerated scopes scanned, all answered"
fi
}
# oor_probe runs ONE scope directly and prints the finding count the remote side reported, or the word
# unreachable. It reads the count out of the SUMMARY line rather than counting printed findings, so a control
# cannot be satisfied by output that happens to have the right number of lines.
oor_probe() {
local host="$1" kind="$2" glob="$3" n=""
while IFS=$'\t' read -r rkind rpath rline rname; do
[ -z "${rkind:-}" ] && continue
[ "$rkind" = "SUMMARY" ] && n="$rname"
done < <(oor_host_cmd "$host" "/tmp/mm-remote-scan.sh $kind '$glob'")
[ -z "$n" ] && n="unreachable"
echo "$n"
}
# THE PLANTED VALUE. Fake, and shaped like the real thing so the patterns must actually work to catch it. It
# deliberately avoids every allowlist entry, because a control that lands on the allowlist passes by being
# ignored, which is the same as not running.
CTL_PW='Zq7mKv3nRt9wLx2bYd'
CTL_DSN="postgres://zzprobe:${CTL_PW}@127.0.0.1:5432/zzdb"
if [ "$CONTROL" = "1" ]; then
# THE POSITIVE CONTROL. A sweep that has never found anything is indistinguishable from one whose
# patterns are broken, so a fake credential is planted in EVERY SCOPE THE SWEEP CLAIMS and each one must
# report it, then stop reporting it once the plant is removed. Both directions, per scope, because a check
# that always fires is as useless as one that never does.
#
# THE CONTROL ITSELF WAS BROKEN FIRST. git grep searches TRACKED files only, so the planted file was never
# scanned and the control was passing on unrelated real findings elsewhere in the tree. It reported
# success while testing nothing, which is the exact failure this whole sweep exists to prevent, and it was
# only caught because a later fix removed the unrelated findings and the control went red. scan_tree now
# passes --untracked, which is also what you want before a commit: a secret in a new unstaged file is the
# most likely way one arrives.
#
# FINDINGS ARE COUNTED, NEVER ECHOED, in this mode. The planted value is fake, and printing it anyway would
# teach the habit that disclosed a live credential through flag usage output in finding D14.
cpass=0; cfail=0
ctl() { # name, expectation met yes or no, detail
if [ "$2" = "yes" ]; then cpass=$((cpass+1)); echo " control OK $1: $3"
else cfail=$((cfail+1)); echo " CONTROL FAILED $1: $3"; fi
}
probe_repo() { local before="$findings"; scan_tree >/dev/null 2>&1; echo $((findings - before)); }
# ---- scope 1, INSIDE the repository, an UNTRACKED file. The historically broken one. ----
utmp="docs/_secret_sweep_control.md"
printf 'host access: ssh admin@example.invalid\npassword: %s\n' "$CTL_PW" > "$utmp"
n=$(probe_repo); rm -f "$utmp"
[ "$n" -gt 0 ] && ctl "repo untracked" yes "planted credential detected, $n finding(s)" \
|| ctl "repo untracked" no "a planted credential in an UNTRACKED file was NOT detected"
n=$(probe_repo)
[ "$n" -eq 0 ] && ctl "repo untracked, cleared" yes "clean once the plant was removed" \
|| ctl "repo untracked, cleared" no "still reporting $n finding(s) with nothing planted"
# ---- scope 2, INSIDE the repository, a TRACKED file. ----
# A MODIFIED tracked file rather than a newly added one, so the INDEX is never touched: staging a credential
# shaped string, even briefly, risks a crash leaving it staged for the next commit. The content is saved
# byte exactly and restored, and the restore is VERIFIED by hash rather than assumed.
ttrk="docs/CREDENTIAL_CONSUMERS.md"
if [ -f "$ttrk" ] && git ls-files --error-unmatch "$ttrk" >/dev/null 2>&1; then
tsave="$(mktemp)"; cp "$ttrk" "$tsave"
tbefore="$(shasum -a 256 "$ttrk" | cut -d' ' -f1)"
printf '\npassword: %s\n' "$CTL_PW" >> "$ttrk"
n=$(probe_repo)
cp "$tsave" "$ttrk"; rm -f "$tsave"
tafter="$(shasum -a 256 "$ttrk" | cut -d' ' -f1)"
[ "$n" -gt 0 ] && ctl "repo tracked" yes "planted credential detected, $n finding(s)" \
|| ctl "repo tracked" no "a planted credential in a TRACKED file was NOT detected"
[ "$tbefore" = "$tafter" ] && ctl "repo tracked, restored" yes "$ttrk restored, hash matches" \
|| ctl "repo tracked, restored" no "$ttrk did NOT restore to its original hash"
n=$(probe_repo)
[ "$n" -eq 0 ] && ctl "repo tracked, cleared" yes "clean once the plant was removed" \
|| ctl "repo tracked, cleared" no "still reporting $n finding(s) with nothing planted"
else
ctl "repo tracked" no "$ttrk is absent or untracked, so the tracked file scope was NOT controlled"
fi
# ---- scopes 3 and up, OUTSIDE the repository, one control per host and kind. ----
if [ "$OUTOFREPO" = "1" ]; then
# Cleanup is a TRAP, not a trailing line, because a control that plants a credential shaped file on a
# server and then dies has made the problem it was testing for.
oor_cleanup() {
for h in bubbles megamind; do
oor_host_cmd "$h" "rm -f /usr/local/sbin/mm-zz-sweep-control.sh /etc/systemd/system/mm-zz-sweep-control.service /etc/missionmatch/zz-sweep-control.env" >/dev/null 2>&1
done
}
trap oor_cleanup EXIT INT TERM
for h in bubbles megamind; do
if ! oor_stage "$h"; then
ctl "$h out of repo" no "host unreachable, so NO out of repo scope on it was controlled"
continue
fi
# 3a. A SCRIPT that embeds a value. Exactly what mm-identity-sync.sh did until 29 July 2026.
p=/usr/local/sbin/mm-zz-sweep-control.sh
oor_host_cmd "$h" "printf 'CONTROL_DSN=$CTL_DSN\n' > $p" >/dev/null 2>&1
n=$(oor_probe "$h" script "/usr/local/sbin/mm-*.sh")
oor_host_cmd "$h" "rm -f $p" >/dev/null 2>&1
case "$n" in
unreachable) ctl "$h script" no "scope did not answer" ;;
0) ctl "$h script" no "a planted DSN in a script under the real glob was NOT detected" ;;
*) ctl "$h script" yes "planted DSN detected, $n finding(s)" ;;
esac
# 3b. A systemd UNIT that inlines a secret instead of pointing at an EnvironmentFile. The unit is never
# enabled and no daemon reload is issued, so it is inert on disk for the length of the check.
if grep -qE "^$h[[:space:]]+unit" "$SCOPE_FILE" 2>/dev/null; then
u=/etc/systemd/system/mm-zz-sweep-control.service
oor_host_cmd "$h" "printf '[Service]\nEnvironment=MM_ZZ_PASSWORD=$CTL_PW\n' > $u" >/dev/null 2>&1
n=$(oor_probe "$h" unit "/etc/systemd/system/mm-*.service")
oor_host_cmd "$h" "rm -f $u" >/dev/null 2>&1
case "$n" in
unreachable) ctl "$h unit" no "scope did not answer" ;;
0) ctl "$h unit" no "an inline Environment= secret in a unit under the real glob was NOT detected" ;;
*) ctl "$h unit" yes "inline unit secret detected, $n finding(s)" ;;
esac
fi
# 3c. The MODE check, controlled BOTH WAYS on a file of its own so no real credential file's permissions
# are ever loosened to test the instrument. World readable must fail, 0600 root owned must pass. The
# second half is the half that matters: a mode check that fires on everything proves nothing either.
m=/etc/missionmatch/zz-sweep-control.env
oor_host_cmd "$h" "printf 'MM_ZZ_URL=$CTL_DSN\n' > $m; chmod 644 $m; chown root:root $m" >/dev/null 2>&1
n=$(oor_probe "$h" mode "$m")
case "$n" in
unreachable) ctl "$h mode, loose" no "scope did not answer" ;;
0) ctl "$h mode, loose" no "a world readable credential file was NOT flagged" ;;
*) ctl "$h mode, loose" yes "world readable credential file flagged, $n finding(s)" ;;
esac
oor_host_cmd "$h" "chmod 600 $m" >/dev/null 2>&1
n=$(oor_probe "$h" mode "$m")
case "$n" in
unreachable) ctl "$h mode, correct" no "scope did not answer" ;;
0) ctl "$h mode, correct" yes "a 0600 root owned credential file was correctly NOT flagged" ;;
*) ctl "$h mode, correct" no "a correctly locked credential file was flagged anyway, $n finding(s)" ;;
esac
oor_host_cmd "$h" "rm -f $m" >/dev/null 2>&1
done
oor_cleanup
trap - EXIT INT TERM
else
echo " note: out of repo scopes were NOT controlled. Pass --control --out-of-repo to control them."
fi
echo "positive control: $cpass passed, $cfail failed"
if [ "$cfail" -gt 0 ]; then
echo "POSITIVE CONTROL FAILED: at least one scope the sweep claims to cover proves nothing."
exit 1
fi
exit 0
fi
echo "secret sweep: working tree"
scan_tree
if [ "$HISTORY" = "1" ]; then
scan_history
fi
if [ "$OUTOFREPO" = "1" ]; then
echo "secret sweep: out of repo scope"
scan_out_of_repo
fi
if [ "$findings" -gt 0 ]; then
echo "SECRET SWEEP FAILED: $findings finding(s). Rotate first, then redact, then purge history."
exit 1
fi
echo "secret sweep: clean"
exit 0