Let the reset test make its own premise instead of inheriting it (ER69) #927
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Secret Scan | |
| # Secret scanning with gitleaks (HARD FAIL). | |
| # | |
| # gitleaks scans the full git history for committed credentials (API keys, | |
| # tokens, private keys). It complements the SAST workflow: bandit and | |
| # semgrep look for insecure *code*, gitleaks looks for leaked *secrets*. | |
| # | |
| # Known non-secrets (synthetic test fixtures, deterministic CI dummy | |
| # secrets, and documentation placeholders) are allowlisted in | |
| # .gitleaks.toml. A one-time full-history scan confirmed there are no real | |
| # leaked credentials. When gitleaks flags a genuine secret: remove it, | |
| # rotate the credential, and do NOT add it to the allowlist. | |
| # | |
| # Unlike ci.yml / sast.yml this workflow deliberately has NO paths-ignore: | |
| # a secret can be committed in any file type (including docs and config), | |
| # the scan is cheap (a few seconds), and it must never be skipped. | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: {} | |
| # Least-privilege: gitleaks only needs to read the checked-out source. | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: secret-scan-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| gitleaks: | |
| name: gitleaks (HARD FAIL on leak) | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| # Full history so gitleaks scans every commit, not just the tip. | |
| fetch-depth: 0 | |
| - name: Install gitleaks | |
| env: | |
| GITLEAKS_VERSION: "8.30.1" | |
| run: | | |
| curl -sSfL \ | |
| "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ | |
| -o gitleaks.tar.gz | |
| tar -xzf gitleaks.tar.gz gitleaks | |
| ./gitleaks version | |
| - name: Run gitleaks (HARD FAIL on any leak) | |
| # `detect` scans the COMMIT HISTORY reachable from HEAD, not the | |
| # checked-out files. Two consequences worth stating, because both have | |
| # cost real time: | |
| # | |
| # 1. Deleting the value and committing the deletion does NOT clear | |
| # this. The commit that introduced it is still on the branch, and | |
| # commits are what is scanned. The failure survives the fix, which | |
| # reads like the fix not being picked up. The `if: failure()` step | |
| # below says so at the moment it happens. | |
| # 2. Another contributor's branch cannot fail your pull request. | |
| # `fetch-depth: 0` means "all of this ref's history", not "all | |
| # branches": actions/checkout fetches only the pull-request ref, | |
| # so no other branch is present for gitleaks to read. A | |
| # cross-branch failure was reported once; the run logs show each | |
| # pull request scanning main's commit count plus its own (289 + n | |
| # at the time), never the whole repository. | |
| # | |
| # `--exit-code 1` makes a finding fail the job. Findings are redacted | |
| # in the log and written to the SARIF artifact for the Security tab. | |
| # Suppress provable non-secrets only in .gitleaks.toml, never by | |
| # relaxing this gate. | |
| run: | | |
| ./gitleaks detect \ | |
| --source . \ | |
| --config .gitleaks.toml \ | |
| --redact \ | |
| --verbose \ | |
| --report-format sarif \ | |
| --report-path gitleaks.sarif \ | |
| --exit-code 1 | |
| - name: Explain what a finding means | |
| if: failure() | |
| run: | | |
| cat <<'EOF' | |
| ============================================================ | |
| gitleaks scanned this branch's COMMIT HISTORY, not its files. | |
| ============================================================ | |
| If you already removed the value, that is why this still fails: the | |
| commit that added it is still on the branch, and the scan reads | |
| commits. Taking it out of the working tree does not take it out of | |
| history. | |
| To clear it the commit has to go. From a clean tree, with no one | |
| else building on your branch: | |
| git fetch origin | |
| git rebase origin/main # FIRST, before collapsing anything | |
| git reset --soft origin/main # your whole diff becomes one staged change | |
| git commit # one commit, without the bad value | |
| git push --force-with-lease | |
| Rebase before the reset, not after. `git reset --soft` moves the | |
| branch to wherever origin/main points NOW while keeping your tree, so | |
| if main advanced after you branched, the single commit you create | |
| silently reverts everything merged in between. That has happened | |
| here: 125 lines from two merged pull requests were undone this way, | |
| and the only signal was a diffstat with far more deletions than the | |
| change could account for. Before committing, check | |
| `git diff --stat origin/main`, and check that | |
| `git diff --name-only origin/main` lists no file you did not touch. | |
| If the value is a real credential: rotate it. It reached a remote, so | |
| treat it as disclosed no matter what the history says afterwards. | |
| If it is provably not a secret, add it to .gitleaks.toml with the | |
| reason. Never allowlist a value you cannot prove is safe. | |
| More: docs-site/docs/contributor-guide/coding-standards.md | |
| EOF | |
| - name: Upload gitleaks SARIF | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: gitleaks-report | |
| path: gitleaks.sarif | |
| if-no-files-found: warn | |
| retention-days: 14 |