diff --git a/.github/workflows/betterleaks.yml b/.github/workflows/betterleaks.yml index e3ee2df..f2dd1e5 100644 --- a/.github/workflows/betterleaks.yml +++ b/.github/workflows/betterleaks.yml @@ -11,123 +11,17 @@ jobs: scan: name: Scan for secrets runs-on: ubuntu-latest - env: - SMTP_URL: ${{ secrets.SMTP_URL }} - SMTP_PORT: ${{ secrets.SMTP_PORT || '25' }} - SMTP_EMAIL: ${{ secrets.SMTP_EMAIL }} - SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }} steps: - - name: Checkout repository - uses: actions/checkout@v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + persist-credentials: false fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha || github.sha }} - - name: Run Betterleaks - id: betterleaks - continue-on-error: true - uses: dortort/betterleaks-action@v0.1.0 + # Shared EEA secret scan: https://github.com/eea/github-actions/tree/main/betterleaks + - uses: eea/github-actions/betterleaks@v1 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - scan-mode: dir - scan-path: . - config: .gitleaks.toml - report-format: json - report-path: betterleaks-report.json - redact: "true" - no-color: "true" - no-banner: "true" - fail-on-leak: "true" - - - name: Upload Betterleaks report - if: always() - uses: actions/upload-artifact@v4 - with: - name: betterleaks-report - path: betterleaks-report.json - if-no-files-found: ignore - - - name: Build Betterleaks email summary - id: leak_summary - if: steps.betterleaks.outcome == 'failure' - shell: bash - run: | - if [[ -s betterleaks-report.json ]]; then - jq -r ' - def one_line: - tostring - | gsub("[\r\n]+"; " ") - | if length > 240 then .[0:240] + "..." else . end; - - .[:20][] - | "- " + (.RuleID // "unknown-rule") - + " at " + (.File // "unknown-file") - + ":" + ((.StartLine // 0) | tostring) - + "\n match: " + ((.Match // .Secret // "REDACTED") | one_line) - ' betterleaks-report.json > betterleaks-email-summary.txt - - count="$(jq 'length' betterleaks-report.json)" - if (( count > 20 )); then - { - echo "" - echo "... and $((count - 20)) more finding(s). Download the artifact for full details." - } >> betterleaks-email-summary.txt - fi - else - echo "No JSON report was generated. Download the workflow logs for details." > betterleaks-email-summary.txt - fi - - { - echo "text<> "$GITHUB_OUTPUT" - - - name: Resolve committer email - id: committer - if: steps.betterleaks.outcome == 'failure' - shell: bash - run: | - committer_email="$(git log -1 --format='%ce')" - author_email="$(git log -1 --format='%ae')" - email="$committer_email" - if [[ -z "$email" || "$email" == *"noreply.github.com"* ]]; then - email="$author_email" - fi - if [[ "$email" =~ ^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$ && "$email" != *"noreply.github.com"* ]]; then - echo "email=$email" >> "$GITHUB_OUTPUT" - else - echo "No deliverable committer email found; skipping Betterleaks email notification." - echo "email=" >> "$GITHUB_OUTPUT" - fi - - - name: Email committer on Betterleaks failure - if: steps.betterleaks.outcome == 'failure' && steps.committer.outputs.email != '' && env.SMTP_URL != '' && env.SMTP_EMAIL != '' - uses: dawidd6/action-send-mail@v18 - with: - server_address: ${{ env.SMTP_URL }} - server_port: ${{ env.SMTP_PORT }} - secure: ${{ env.SMTP_PORT == '465' }} - username: ${{ env.SMTP_EMAIL }} - password: ${{ env.SMTP_PASSWORD }} - from: ${{ env.SMTP_EMAIL }} - to: ${{ steps.committer.outputs.email }} - subject: "[Betterleaks] Secret scan failed in ${{ github.repository }}" - body: | - Betterleaks detected one or more potential secrets. - - Repository: ${{ github.repository }} - Branch: ${{ github.ref_name }} - Commit: ${{ github.sha }} - Workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - - Findings: - ${{ steps.leak_summary.outputs.text }} - - Download the betterleaks-report artifact from the workflow run for details. - - - name: Fail if Betterleaks found leaks - if: steps.betterleaks.outcome == 'failure' - run: | - echo "Betterleaks detected one or more secrets. Download the betterleaks-report artifact from this workflow run for details." - exit 1 + smtp-url: ${{ secrets.SMTP_URL }} + smtp-port: ${{ secrets.SMTP_PORT || '25' }} + smtp-email: ${{ secrets.SMTP_EMAIL }} + smtp-password: ${{ secrets.SMTP_PASSWORD }} diff --git a/.gitleaks.toml b/.gitleaks.toml index 4313fd7..efd10cf 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -1,89 +1,7 @@ +# Repository-specific Betterleaks config. The shared EEA rules are applied by +# eea/github-actions/betterleaks (betterleaks/config/eea-base.toml). + title = "Betterleaks config" [extend] useDefault = true - -[[rules]] -id = "secret-literal-assignment" -description = "Secret-like literal assignment in source, YAML, env and config files" -regex = '''(?i)([A-Za-z0-9_.-]*(?:password|passwd|pwd|api[_-]?key|apikey|access[_-]?key|secret[_-]?access[_-]?key|private[_-]?key|client[_-]?secret|consumer[_-]?key|consumer[_-]?secret|(?:auth|access|refresh|session|api|bearer|id|jwt|csrf|xsrf|oauth)[_-]?token|translate[_-]?auth|translation[_-]?auth|auth[_-]?password|auth[_-]?key|auth[_-]?credentials|database[_-]?url|connection[_-]?string)[A-Za-z0-9_.-]*)[ \t]*[:=][ \t]*["'']?([^"''[:space:]#{}$.][^"''\n#{}]{2,})["'']?''' -secretGroup = 2 -keywords = [ - "password", - "passwd", - "pwd", - "api_key", - "apikey", - "access_key", - "private_key", - "client_secret", - "consumer_key", - "consumer_secret", - "auth_token", - "access_token", - "refresh_token", - "session_token", - "api_token", - "bearer_token", - "id_token", - "jwt_token", - "csrf_token", - "xsrf_token", - "oauth_token", - "translate_auth", - "translation_auth", - "auth_password", - "auth_key", - "auth_credentials", - "database_url", - "connection_string" -] -tags = ["literal-secret"] - -[[rules]] -id = "env-short-secret-assignment" -description = "Uppercase env-style PASS/TOKEN/SECRET assignment" -regex = '''\b((?:PASS|TOKEN|SECRET|[A-Z0-9_]*(?:_PASS|_TOKEN|_SECRET|PASS_|TOKEN_|SECRET_)[A-Z0-9_]*))[ \t]*[:=][ \t]*["'']?([^"''[:space:]#{}$.][^"''\n#{}]{2,})["'']?''' -secretGroup = 2 -keywords = [ - "PASS", - "TOKEN", - "SECRET" -] -tags = ["env", "literal-secret"] - -[[rules]] -id = "standalone-sk-token" -description = "Standalone sk-* token not attached to a secret-like variable name" -regex = '''(?i)\b(sk-[A-Za-z0-9][A-Za-z0-9_-]{20,})\b''' -secretGroup = 1 -entropy = 2.5 -keywords = ["sk-"] -tags = ["standalone-token", "generic"] - -[[rules]] -id = "dotenv-only-jest-setup" -description = ".env may only contain the committed Jest setup lines" -path = '''(?i)(^|/)\.env$''' -regex = '''(?m)^(.+)$''' -secretGroup = 1 -tags = ["file", "dotenv"] -[[rules.allowlists]] -regexTarget = "match" -regexes = [ - '''^JEST_USE_SETUP=(ON|OFF) # Jest configuration variables: ON, OFF\r?$''', -] - -[[rules]] -id = "forbidden-secret-file" -description = "Forbidden secret-bearing file committed to repository" -path = '''(?i)(^|/)(\.env\..*|\.npmrc|\.pypirc|id_rsa|id_ed25519|.*\.(pem|key|p12|pfx|jks|kubeconfig))$''' -regex = '''(?s).{1,}''' -tags = ["file", "secret-file"] - -[[allowlists]] -description = "Allow Jenkins SonarQube token environment variable reference" -regexTarget = "match" -regexes = [ - '''SONAR_AUTH_TOKEN''', -]