From 9fefe287825776494327cb27f908b19965b59a20 Mon Sep 17 00:00:00 2001 From: fernando-mendoza <55415857+fernando-mendoza@users.noreply.github.com> Date: Mon, 2 Nov 2026 22:25:53 -0600 Subject: [PATCH] ci: scan pull requests for committed secrets with gitleaks (#224) Add a Secret Scan workflow that runs the gitleaks CLI (pinned 8.28.0, checksum-verified) over each PR's commit range and fails on findings. .gitleaks.toml extends the default rules with a Stellar secret-seed rule and documents a narrow allowlist (placeholder seeds, a known testnet vector in examples/, public G... account IDs, one bundle false positive). CONTRIBUTING.md says what to do when it fires. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/secret-scan.yml | 37 +++++++++++++++++++++++++ .gitleaks.toml | 46 +++++++++++++++++++++++++++++++ CONTRIBUTING.md | 2 ++ 3 files changed, 85 insertions(+) create mode 100644 .github/workflows/secret-scan.yml create mode 100644 .gitleaks.toml diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml new file mode 100644 index 0000000..bc42eec --- /dev/null +++ b/.github/workflows/secret-scan.yml @@ -0,0 +1,37 @@ +# Issue #224: scan every pull request for committed secrets and fail the +# check when gitleaks finds one. Runs the gitleaks CLI directly (pinned +# version, checksum-verified) instead of gitleaks/gitleaks-action, which +# needs a paid license key for repos owned by an organization. +# Only the PR's own commits are scanned (base..head), so old history does +# not block new PRs. Config and allowlist: .gitleaks.toml. +name: Secret Scan +on: + pull_request: + branches: [main] +permissions: + contents: read +jobs: + gitleaks: + runs-on: ubuntu-latest + env: + GITLEAKS_VERSION: 8.28.0 + GITLEAKS_SHA256: a65b5253807a68ac0cafa4414031fd740aeb55f54fb7e55f386acb52e6a840eb + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - name: Install gitleaks + run: | + curl -sSfL -o gitleaks.tar.gz \ + "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" + echo "${GITLEAKS_SHA256} gitleaks.tar.gz" | sha256sum -c - + tar -xzf gitleaks.tar.gz -C "$RUNNER_TEMP" gitleaks + rm gitleaks.tar.gz + - name: Scan PR commits + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + "$RUNNER_TEMP/gitleaks" git --no-banner --redact --verbose \ + --config .gitleaks.toml \ + --log-opts="${BASE_SHA}..${HEAD_SHA}" . diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..8c77448 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,46 @@ +# gitleaks config for the Secret Scan workflow (issue #224). +# Starts from gitleaks' built-in rules and adds a Stellar secret-seed rule, +# since that is the key type this project actually handles. +# Every allowlist entry below is narrow on purpose; add to it only for +# values that are provably not secrets (public data, published test vectors). + +[extend] +useDefault = true + +[[rules]] +id = "stellar-secret-seed" +description = "Stellar secret seed (S... strkey)" +regex = '''\bS[A-Z2-7]{55}\b''' +keywords = ["s"] + + # Placeholder seeds such as SXXXX...XXXX used in docs. + [[rules.allowlists]] + description = "All-X placeholder seed" + regexTarget = "match" + regexes = ['''^SX{55}$'''] + + # Published testnet test vectors used by example tests. Allowed only + # inside examples/, and only these exact values; any other seed in + # examples/ still fails the scan. + [[rules.allowlists]] + description = "Known testnet test vectors in examples/" + condition = "AND" + paths = ['''^examples/'''] + regexes = ['''SCKB3ECHCPVM4HJPNCQWTQWJJ5XRL6UNKLTTCIH4B7TB22NKJ5GUFMIV'''] + +[[allowlists]] +# Applies to all rules (gitleaks 8.28 ignores targetRules for extended +# default rules). Safe: a G... strkey is always public. +# Stellar public keys (G... strkeys) are public account IDs, not secrets, +# but the built-in generic-api-key rule flags `"key": "G..."` in fixtures. +description = "Stellar public account IDs" +regexTarget = "secret" +regexes = ['''^G[A-Z2-7]{55}$'''] + +[[allowlists]] +# Minified identifier in the ncc bundle, not a credential. +description = "False positive in actions/verify-audit-cid/dist bundle" +condition = "AND" +paths = ['''^actions/verify-audit-cid/dist/index\.js$'''] +regexTarget = "secret" +regexes = ['''^hasV2Preconditions$'''] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c149519..ec82407 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -104,6 +104,8 @@ Email: **niriumprotocol@gmail.com** Include: description, reproduction steps, potential impact, and any suggested remediation. +If the **Secret Scan** check fails on your PR, treat the key as leaked: revoke or rotate it, remove it from your branch (amend/rebase so no commit contains it), and push again. If it is a false positive or a public test vector, add a narrow, commented entry to `.gitleaks.toml` instead of disabling the check. + --- ## License