Repository navigation
OP-195: add secret-detection CI coverage #71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 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. | ||
|
|
||
| ## 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. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| 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 | ||
| shell: bash | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| set -euo pipefail | ||
| git init . | ||
| git remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" | ||
| 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 | ||
|
|
||
| - 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 . | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: Scan results are emitted only to ephemeral CI logs. GitHub retains workflow run logs for a limited window (default 90 days). For legal-grade audit trail requirements, the raw scan output should be preserved as a permanent, immutable artifact — especially for scheduled and manual runs where findings may be triaged over time. Suggestion: Add |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
INFO: The exception process references
.gitleaksignorefor approved fingerprints, but the file does not exist in the repository. This is expected since no exceptions have been approved yet, but the absence should be noted during the initial historical scan triage.Suggestion: Create an empty
.gitleaksignorewith a header comment explaining the approval process, or add a note in the doc that the file will be created when the first exception is approved.