From 26aa44924a5da336521e31bcd2a4d09aeed141df Mon Sep 17 00:00:00 2001 From: max Date: Tue, 8 Sep 2026 17:15:28 -0700 Subject: [PATCH 1/3] OP-195: add secret detection CI control --- .github/SECRET_DETECTION.md | 30 ++++++++++++++++ .github/workflows/secret-detection.yml | 47 ++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 .github/SECRET_DETECTION.md create mode 100644 .github/workflows/secret-detection.yml diff --git a/.github/SECRET_DETECTION.md b/.github/SECRET_DETECTION.md new file mode 100644 index 0000000..95a727f --- /dev/null +++ b/.github/SECRET_DETECTION.md @@ -0,0 +1,30 @@ +# Secret detection and prevention control + +Owner: Security Engineering +Control: CI secret detection, with native GitHub push protection tracked separately +Review cadence: at least annually and after a material repository or scanner change + +## Required control + +The `Secret detection` workflow scans the complete reachable Git history on every push and pull request and once each week. A finding fails the workflow, so a change cannot satisfy a branch rule that requires this check until the finding is treated. The scanner binary, download checksum, and checkout action are pinned. + +This workflow is a **detective CI control**, not native GitHub push protection. Repository administrators must separately record whether GitHub secret scanning and push protection are enabled. CI coverage must never be reported as native push prevention. + +## Historical findings + +After this workflow reaches the repository's protected base branch, run it manually once and retain the run URL, repository SHA, UTC time, and result. Triage every finding without copying a secret value into logs, issues, or evidence: + +1. Confirm whether it is a real credential, a synthetic test value, or a false positive. +2. For a real credential, follow the incident-response process and obtain authorization before revocation, rotation, or history rewriting. +3. Link the treatment record to the scanner run and record owner, disposition, and completion time. +4. Re-run the full-history scan and retain the passing result. A history rewrite is destructive and requires separate explicit approval. + +An initial failing historical scan proves detection is operating; it does not establish remediation or compliance. + +## Exceptions + +Exceptions must be narrow, time-bounded, and approved by both the repository owner and Security Engineering. The linked tracking record must contain the detector/fingerprint, justification, compensating control, owner, approval, and an expiry no later than 90 days. Add only the approved fingerprint to `.gitleaksignore`. Broad path, regex, detector, or repository-wide allowlists are prohibited unless the same approval record explicitly authorizes them. Remove an exception when it expires and re-run the workflow. + +## Safe validation + +Use an invalid synthetic credential in a temporary branch; never use a live credential. Verify that the workflow fails and redacts the value, remove the fixture, and verify that the same workflow passes. Retain both run URLs and the tested SHA. Native push-protection validation is a separate GitHub-administrator test and must identify itself as such. diff --git a/.github/workflows/secret-detection.yml b/.github/workflows/secret-detection.yml new file mode 100644 index 0000000..c956a30 --- /dev/null +++ b/.github/workflows/secret-detection.yml @@ -0,0 +1,47 @@ +name: Secret detection + +on: + push: + pull_request: + schedule: + - cron: "17 6 * * 1" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: secret-detection-${{ github.repository }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + gitleaks: + name: Gitleaks (complete history) + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check out complete history + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Install pinned Gitleaks release + shell: bash + run: | + set -euo pipefail + readonly version="8.28.0" + readonly archive="gitleaks_${version}_linux_x64.tar.gz" + readonly expected_sha256="a65b5253807a68ac0cafa4414031fd740aeb55f54fb7e55f386acb52e6a840eb" + curl --fail --silent --show-error --location --retry 3 \ + "https://github.com/gitleaks/gitleaks/releases/download/v${version}/${archive}" \ + --output "${RUNNER_TEMP}/${archive}" + echo "${expected_sha256} ${RUNNER_TEMP}/${archive}" | sha256sum --check --strict + tar --extract --gzip --file "${RUNNER_TEMP}/${archive}" --directory "${RUNNER_TEMP}" gitleaks + "${RUNNER_TEMP}/gitleaks" version + + - name: Scan complete repository history + shell: bash + run: | + set -euo pipefail + "${RUNNER_TEMP}/gitleaks" git --redact=100 --no-banner --verbose . From 2f3c7388a0439548bd0f2aa8e572ad797e72ae0d Mon Sep 17 00:00:00 2001 From: max Date: Tue, 8 Sep 2026 17:23:36 -0700 Subject: [PATCH 2/3] OP-195: make full-history checkout robust --- .github/SECRET_DETECTION.md | 2 +- .github/workflows/secret-detection.yml | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/SECRET_DETECTION.md b/.github/SECRET_DETECTION.md index 95a727f..446439b 100644 --- a/.github/SECRET_DETECTION.md +++ b/.github/SECRET_DETECTION.md @@ -6,7 +6,7 @@ Review cadence: at least annually and after a material repository or scanner cha ## Required control -The `Secret detection` workflow scans the complete reachable Git history on every push and pull request and once each week. A finding fails the workflow, so a change cannot satisfy a branch rule that requires this check until the finding is treated. The scanner binary, download checksum, and checkout action are pinned. +The `Secret detection` workflow scans the complete reachable Git history on every push and pull request and once each week. A finding fails the workflow, so a change cannot satisfy a branch rule that requires this check until the finding is treated. The scanner binary and download checksum are pinned. Checkout fetches only the triggering commit and its complete reachable ancestry with the workflow's read-only token; it does not initialize submodules. This workflow is a **detective CI control**, not native GitHub push protection. Repository administrators must separately record whether GitHub secret scanning and push protection are enabled. CI coverage must never be reported as native push prevention. diff --git a/.github/workflows/secret-detection.yml b/.github/workflows/secret-detection.yml index c956a30..e0a9a3b 100644 --- a/.github/workflows/secret-detection.yml +++ b/.github/workflows/secret-detection.yml @@ -21,10 +21,17 @@ jobs: timeout-minutes: 15 steps: - name: Check out complete history - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - with: - fetch-depth: 0 - persist-credentials: false + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + git init . + git remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" + readonly auth_header="AUTHORIZATION: basic $(printf 'x-access-token:%s' "${GH_TOKEN}" | base64 --wrap=0)" + git -c "http.https://github.com/.extraheader=${auth_header}" fetch \ + --no-tags --prune origin "+${GITHUB_SHA}:refs/remotes/origin/scan" + git checkout --detach refs/remotes/origin/scan - name: Install pinned Gitleaks release shell: bash From ca84b61ca407a6e038b953c701b4964bcbc5f87e Mon Sep 17 00:00:00 2001 From: max Date: Tue, 8 Sep 2026 18:15:48 -0700 Subject: [PATCH 3/3] fix(OP-195): preserve checkout credential encoding failures Separate command substitution from readonly declaration so set -e preserves encoder errors. Shared workflow validated with actionlint 1.7.12; failure injection exits 7 rather than incorrectly succeeding. Scanner pin, complete-history scope, redaction and failure gate are unchanged. --- .github/workflows/secret-detection.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/secret-detection.yml b/.github/workflows/secret-detection.yml index e0a9a3b..81f4a9e 100644 --- a/.github/workflows/secret-detection.yml +++ b/.github/workflows/secret-detection.yml @@ -28,7 +28,8 @@ jobs: set -euo pipefail git init . git remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" - readonly auth_header="AUTHORIZATION: basic $(printf 'x-access-token:%s' "${GH_TOKEN}" | base64 --wrap=0)" + auth_header="AUTHORIZATION: basic $(printf 'x-access-token:%s' "${GH_TOKEN}" | base64 --wrap=0)" + readonly auth_header git -c "http.https://github.com/.extraheader=${auth_header}" fetch \ --no-tags --prune origin "+${GITHUB_SHA}:refs/remotes/origin/scan" git checkout --detach refs/remotes/origin/scan