From d02084777265e836cd05c6d4edff36de649c1226 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Apr 2026 20:56:01 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20[nomad-prep]=20P11=20=E2=80=94=20wire=20?= =?UTF-8?q?lib/secret-scan.sh=20into=20Woodpecker=20CI=20gate=20(#798)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- .woodpecker/run-secret-scan.sh | 66 ++++++++++++++++++++++++++++++++++ .woodpecker/secret-scan.yml | 32 +++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 .woodpecker/run-secret-scan.sh create mode 100644 .woodpecker/secret-scan.yml diff --git a/.woodpecker/run-secret-scan.sh b/.woodpecker/run-secret-scan.sh new file mode 100644 index 0000000..97bcacd --- /dev/null +++ b/.woodpecker/run-secret-scan.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +set -euo pipefail +# run-secret-scan.sh — CI wrapper for lib/secret-scan.sh +# +# Scans files changed in this PR for plaintext secrets. +# Exits non-zero if any secret is detected. + +# shellcheck source=../lib/secret-scan.sh +source lib/secret-scan.sh + +# Path patterns considered secret-adjacent +SECRET_PATH_PATTERNS=( + '\.env' + 'tools/vault-.*\.sh' + 'nomad/' + 'vault/' + 'action-vault/' + 'lib/hvault\.sh' + 'lib/action-vault\.sh' +) + +# Build a single regex from patterns +path_regex=$(printf '%s|' "${SECRET_PATH_PATTERNS[@]}") +path_regex="${path_regex%|}" + +# Get files changed in this PR vs target branch +changed_files=$(git diff --name-only --diff-filter=ACMR "origin/${CI_COMMIT_TARGET_BRANCH}...HEAD" || true) + +if [ -z "$changed_files" ]; then + echo "secret-scan: no changed files found, skipping" + exit 0 +fi + +# Filter to secret-adjacent paths only +target_files=$(printf '%s\n' "$changed_files" | grep -E "$path_regex" || true) + +if [ -z "$target_files" ]; then + echo "secret-scan: no secret-adjacent files changed, skipping" + exit 0 +fi + +echo "secret-scan: scanning $(printf '%s\n' "$target_files" | wc -l) file(s):" +printf ' %s\n' "$target_files" + +failures=0 +while IFS= read -r file; do + # Skip deleted files / non-existent + [ -f "$file" ] || continue + # Skip binary files + file -b --mime-encoding "$file" 2>/dev/null | grep -q binary && continue + + content=$(cat "$file") + if ! scan_for_secrets "$content"; then + echo "FAIL: secret detected in $file" + failures=$((failures + 1)) + fi +done <<< "$target_files" + +if [ "$failures" -gt 0 ]; then + echo "" + echo "secret-scan: $failures file(s) contain potential secrets — merge blocked" + echo "If these are false positives, verify patterns in lib/secret-scan.sh" + exit 1 +fi + +echo "secret-scan: all files clean" diff --git a/.woodpecker/secret-scan.yml b/.woodpecker/secret-scan.yml new file mode 100644 index 0000000..cf9a1c3 --- /dev/null +++ b/.woodpecker/secret-scan.yml @@ -0,0 +1,32 @@ +# .woodpecker/secret-scan.yml — Block PRs that leak plaintext secrets +# +# Triggers on pull requests touching secret-adjacent paths. +# Sources lib/secret-scan.sh and scans each changed file's content. +# Exits non-zero if any potential secret is detected. + +when: + - event: pull_request + path: + - ".env*" + - "tools/vault-*.sh" + - "nomad/**/*" + - "vault/**/*" + - "action-vault/**/*" + - "lib/hvault.sh" + - "lib/action-vault.sh" + +clone: + git: + image: alpine/git + commands: + - AUTH_URL=$(printf '%s' "$CI_REPO_CLONE_URL" | sed "s|://|://token:$FORGE_TOKEN@|") + - git clone --depth 50 "$AUTH_URL" . + - git fetch --depth 50 origin "$CI_COMMIT_REF" "$CI_COMMIT_TARGET_BRANCH" + - git checkout FETCH_HEAD + +steps: + - name: secret-scan + image: alpine:3 + commands: + - apk add --no-cache bash git grep + - bash .woodpecker/run-secret-scan.sh