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