diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0c629c3..8dec9252 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,17 +107,20 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - security-events: write steps: - name: Checkout (full history) - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - name: Run gitleaks - uses: gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e # v3.0.0 + - name: Install gitleaks env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} - GITLEAKS_CONFIG: .gitleaks.toml - GITLEAKS_ARGS: detect --source . --redact --verbose --exit-code 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release download v8.30.1 --repo gitleaks/gitleaks --pattern 'gitleaks_8.30.1_linux_x64.tar.gz' --output /tmp/gitleaks.tar.gz + echo "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb /tmp/gitleaks.tar.gz" | sha256sum -c + tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks + sudo mv /tmp/gitleaks /usr/local/bin/gitleaks + + - name: Run gitleaks + run: gitleaks detect --source . --redact --verbose --exit-code 1 diff --git a/scripts/compliance-audit.sh b/scripts/compliance-audit.sh index 73f11abd..9412dd29 100755 --- a/scripts/compliance-audit.sh +++ b/scripts/compliance-audit.sh @@ -229,11 +229,10 @@ check_action_pinning() { # Find uses: directives that are NOT SHA-pinned # SHA-pinned: uses: owner/action@<40+ hex chars> # Exclude docker:// and ./ references - # Exclude internal reusable workflow calls to petry-projects/.github and - # petry-projects/.github-private — per ci-standards.md#action-pinning-policy, - # these use deliberate tag refs (@v1, @v2, @main) and are explicitly exempt. + # Exclude $ORG/.github reusable workflow refs — these use tag refs + # (@v1, @v2, @main) by design per ci-standards.md#action-pinning-policy local unpinned - unpinned=$(echo "$decoded" | grep -E '^[[:space:]]*-?[[:space:]]*uses:[[:space:]]+[^#]*@' | grep -vE '@[0-9a-f]{40}' | grep -vE '(docker://|\.\/)' | grep -vE 'uses:[[:space:]]+petry-projects/(\.github|\.github-private)/' || true) + unpinned=$(echo "$decoded" | grep -E '^[[:space:]]*-?[[:space:]]*uses:[[:space:]]+[^#]*@' | grep -vE '@[0-9a-f]{40}' | grep -vE '(docker://|\.\/)' | grep -vE "uses:[[:space:]]+$ORG/\\.github(-private)?/\\.github/workflows/" || true) if [ -n "$unpinned" ]; then local count diff --git a/scripts/lib/push-protection.sh b/scripts/lib/push-protection.sh index a5ec639a..df3ed364 100644 --- a/scripts/lib/push-protection.sh +++ b/scripts/lib/push-protection.sh @@ -230,8 +230,10 @@ pp_check_secret_scan_ci_job() { return fi - # Match actual action references, not bare mentions in comments or docs. - if ! echo "$ci_content" | grep -qE 'uses:[[:space:]]*(gitleaks/gitleaks-action|zricethezav/gitleaks-action)@'; then + # Accept either the gitleaks action or the gitleaks CLI (inline or block run:). + # The action requires a paid org license (gitleaks.io); the CLI installed via + # binary download is a license-free alternative. + if ! echo "$ci_content" | grep -qE '(^[[:space:]]*-?[[:space:]]*uses:[[:space:]]*(gitleaks/gitleaks-action|zricethezav/gitleaks-action)@|gitleaks[[:space:]]+detect([[:space:]]|$))'; then add_finding "$repo" "push-protection" "secret_scan_ci_job_present" "error" \ "\`ci.yml\` does not contain a job using \`gitleaks\` — add the secret-scan job from the standard" \ "$PP_STANDARD_REF#required-ci-job" diff --git a/standards/push-protection.md b/standards/push-protection.md index 85aa4f3e..401637a4 100644 --- a/standards/push-protection.md +++ b/standards/push-protection.md @@ -216,7 +216,6 @@ secret-scan: runs-on: ubuntu-latest permissions: contents: read - security-events: write steps: - name: Checkout (full history) # Pin to SHA per Action Pinning Policy (ci-standards.md#action-pinning-policy). @@ -225,17 +224,29 @@ secret-scan: with: fetch-depth: 0 - - name: Run gitleaks - # Pinned to SHA per Action Pinning Policy (ci-standards.md#action-pinning-policy). - # Refresh with: gh api repos/gitleaks/gitleaks-action/git/refs/tags/v2 --jq '.object.sha' - # then dereference if it points at an annotated tag. - uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2.3.9 - with: - args: detect --source . --redact --verbose --exit-code 1 + - name: Install gitleaks + # Download the pre-built binary and verify its SHA256 checksum. + # To upgrade: download the new checksums.txt from the gitleaks release page, + # update the version tag and the sha256 hash below. env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release download v8.30.1 --repo gitleaks/gitleaks --pattern 'gitleaks_8.30.1_linux_x64.tar.gz' --output /tmp/gitleaks.tar.gz + echo "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb /tmp/gitleaks.tar.gz" | sha256sum -c + tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks + sudo mv /tmp/gitleaks /usr/local/bin/gitleaks + + - name: Run gitleaks + run: gitleaks detect --source . --redact --verbose --exit-code 1 ``` +> **Why CLI instead of `gitleaks/gitleaks-action`?** The action's v2 release +> requires a paid license for GitHub organization repos. The CLI (installed via +> a direct binary download with SHA256 verification) is free, fully pinned, and +> runs the same scan. If you have a license, you can use the action instead — +> add `GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}` to the `env:` block +> alongside `GITHUB_TOKEN`. + The job MUST: - Use `fetch-depth: 0` so the full git history is scanned, not just the