shoc-pr-review-runner/scripts/redact-check.sh

94 lines
3.7 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# Pre-upload artifact hygiene (spec §25): scan every staged artifact for the
# run's secret values and for generic credential patterns, and fail the upload
# on any hit. Secrets must never reach console logs, evidence files, prompts, or
# uploaded artifacts.
#
# Matching is deliberately broader than a literal grep: log formatters wrap long
# values across lines, and encoders re-shape them, so each artifact is also
# scanned in a whitespace-stripped form and against derived encodings of each
# secret. Archives are refused rather than scanned opaquely.
#
# Reads: ARTIFACTS_DIR, plus whichever secrets are in scope for this job. At
# least one of GH_TOKEN / FIREWORKS_API_KEY must be present — an empty scan set
# would pass vacuously and produce a green signal that proves nothing.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib.sh
source "$SCRIPT_DIR/lib.sh"
if [ -z "${GH_TOKEN:-}" ] && [ -z "${FIREWORKS_API_KEY:-}" ]; then
die "redaction check has no secrets to scan for — refusing to report clean (set GH_TOKEN and/or FIREWORKS_API_KEY)"
fi
hits=0
scanned=()
# Normalized copy of the artifact tree: newlines and spaces stripped, so a value
# wrapped across lines by a log formatter still matches.
norm_dir="$(mktemp -d)"
trap 'rm -rf "$norm_dir"' EXIT
while IFS= read -r -d '' f; do
case "$f" in
*.zip|*.gz|*.tgz|*.tar|*.bz2|*.xz|*.7z)
log "SECRET LEAK RISK: archive staged for upload cannot be scanned: $f"
hits=$((hits + 1))
continue
;;
esac
tr -d '\n\r \t' <"$f" >"$norm_dir/$(printf '%s' "$f" | md5sum | cut -d' ' -f1)" 2>/dev/null || true
done < <(find "$ARTIFACTS_DIR" -type f -print0)
# scan_value <label> <value> — checks the value and its common derived forms.
scan_value() {
local label="$1" value="$2"
[ -n "$value" ] || return 0
scanned+=("$label")
local -a forms=()
forms+=("$value")
forms+=("$(printf '%s' "$value" | base64 | tr -d '\n')")
forms+=("$(printf 'x-access-token:%s' "$value" | base64 | tr -d '\n')")
# URL-encoded form (only the characters that actually appear in tokens).
forms+=("$(printf '%s' "$value" | sed 's|/|%2F|g; s|+|%2B|g; s|=|%3D|g')")
local form found
for form in "${forms[@]}"; do
[ -n "$form" ] || continue
found="$(grep -rlF -- "$form" "$ARTIFACTS_DIR" 2>/dev/null || true)"
if [ -n "$found" ]; then
log "SECRET LEAK: $label found in artifact file(s):"
printf '%s\n' "$found" >&2
hits=$((hits + 1))
fi
# Whitespace-stripped scan catches line-wrapped occurrences.
found="$(grep -rlF -- "$(printf '%s' "$form" | tr -d '\n\r \t')" "$norm_dir" 2>/dev/null || true)"
if [ -n "$found" ]; then
log "SECRET LEAK: $label found (line-wrapped or whitespace-split) in a staged artifact"
hits=$((hits + 1))
fi
done
}
scan_value "GH_TOKEN (App installation token)" "${GH_TOKEN:-}"
scan_value "FIREWORKS_API_KEY" "${FIREWORKS_API_KEY:-}"
scan_value "SHOC_REVIEW_APP_PRIVATE_KEY" "${SHOC_REVIEW_APP_PRIVATE_KEY:-}"
# Generic credential patterns: catches secrets belonging to the PRODUCT repos
# (a PR touching .env or appsettings) that the runner knows nothing about.
generic_hits="$(grep -rlEI \
-e 'gh[pousr]_[A-Za-z0-9]{30,}' \
-e 'github_pat_[A-Za-z0-9_]{30,}' \
-e 'BEGIN [A-Z ]*PRIVATE KEY' \
-e 'AKIA[0-9A-Z]{16}' \
-e 'xox[baprs]-[A-Za-z0-9-]{10,}' \
"$ARTIFACTS_DIR" 2>/dev/null || true)"
if [ -n "$generic_hits" ]; then
log "SECRET LEAK: generic credential pattern found in artifact file(s):"
printf '%s\n' "$generic_hits" >&2
hits=$((hits + 1))
fi
if [ "$hits" -gt 0 ]; then
die "artifact redaction check failed: $hits leak indicator(s) — artifacts will not be uploaded"
fi
log "artifact redaction check clean (scanned for: ${scanned[*]} + generic credential patterns)"