Install any skill in seconds. Free to start, no credit card required.
Get Started Free →This skill covers implementing Gitleaks for detecting and preventing hardcoded secrets in git repositories. It addresses configuring pre-commit hooks, CI/CD pipeline integration, custom rule authoring for organization-specific secrets, baseline management for existing repositories, and remediation workflows for exposed credentials.
.claude/skills/implementing-secret-scanning-with-gitleaks/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | — | — |
| case-13 | ✗→✓ | ▲ Improved | — | — |
| case-12 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✓→✓ | = Same ✓ | — | — |
| case-11 | ✓→✓ | = Same ✓ | — | — |
Do not use for detecting secrets in running applications or memory (use runtime secret detection), for managing secrets after detection (use Vault or AWS Secrets Manager), or for scanning container images (use Trivy or Grype).
Perform a baseline scan of the repository to identify all existing secrets in the git history.
bash# Install Gitleaks brew install gitleaks # macOS # or download binary from https://github.com/gitleaks/gitleaks/releases # Scan entire git history for secrets gitleaks detect --source . --report-format json --report-path gitleaks-report.json -v # Scan only staged changes (for pre-commit) gitleaks protect --staged --report-format json --report-path gitleaks-staged.json # Scan specific commit range gitleaks detect --source . --log-opts="HEAD~10..HEAD" --report-format json # Scan without git history (filesystem only) gitleaks detect --source . --no-git --report-format json
Set up Gitleaks as a pre-commit hook to prevent secrets from being committed.
yaml# .pre-commit-config.yaml repos: - repo: https://github.com/gitleaks/gitleaks rev: v8.21.2 hooks: - id: gitleaks name: gitleaks description: Detect hardcoded secrets using Gitleaks entry: gitleaks protect --staged --verbose --redact language: golang pass_filenames: false
bash# Install pre-commit framework pip install pre-commit # Install hooks defined in .pre-commit-config.yaml pre-commit install # Run against all files (not just staged) pre-commit run gitleaks --all-files # Test the hook with a deliberate secret echo 'AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"' >> test.txt git add test.txt git commit -m "test" # Should be blocked by gitleaks
yaml# .github/workflows/secret-scanning.yml name: Secret Scanning on: push: branches: [main, develop] pull_request: branches: [main] jobs: gitleaks: name: Gitleaks Secret Scan runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Full history for comprehensive scanning - name: Run Gitleaks uses: gitleaks/gitleaks-action@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} # Required for gitleaks-action v2 # Alternative: Run Gitleaks directly - name: Install Gitleaks run: | wget -q https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz tar -xzf gitleaks_8.21.2_linux_x64.tar.gz chmod +x gitleaks - name: Scan for secrets run: | if [ "${{ github.event_name }}" == "pull_request" ]; then ./gitleaks detect \ --source . \ --log-opts="${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}" \ --report-format sarif \ --report-path gitleaks.sarif \ --exit-code 1 else ./gitleaks detect \ --source . \ --report-format sarif \ --report-path gitleaks.sarif \ --exit-code 1 \ --baseline-path .gitleaks-baseline.json fi - name: Upload SARIF if: always() uses: github/codeql-action/upload-sarif@v3 with: sarif_file: gitleaks.sarif category: gitleaks
Create organization-specific rules for internal secret patterns.
toml# .gitleaks.toml title = "Organization Gitleaks Configuration" [extend] useDefault = true # Include all default rules # Custom rule for internal API tokens [[rules]] id = "internal-api-token" description = "Internal API token for service-to-service auth" regex = '''(?i)x-internal-token["\s:=]+["\']?([a-zA-Z0-9_\-]{40,})["\']?''' entropy = 3.5 keywords = ["x-internal-token"] tags = ["internal", "api"] [[rules]] id = "database-connection-string" description = "Database connection string with embedded credentials" regex = '''(?i)(postgres|mysql|mongodb|redis)://[^:]+:[^@]+@[^/]+/\w+''' keywords = ["postgres://", "mysql://", "mongodb://", "redis://"] tags = ["database", "credentials"] [[rules]] id = "jwt-secret" description = "JWT signing secret" regex = '''(?i)(jwt[_-]?secret|jwt[_-]?key)["\s:=]+["\']?([a-zA-Z0-9/+_\-]{32,})["\']?''' entropy = 3.0 keywords = ["jwt_secret", "jwt-secret", "jwt_key", "jwt-key"] # Allowlist for test files and known safe patterns [allowlist] description = "Global allowlist" paths = [ '''(^|/)test(s)?/''', '''(^|/)spec/''', '''\.test\.(js|ts|py)$''', '''\.spec\.(js|ts|py)$''', '''__mocks__/''', '''fixtures/''', '''(^|/)vendor/''', '''node_modules/''' ] regexes = [ '''EXAMPLE''', '''example\.com''', '''test[-_]?(key|secret|token|password)''', '''(?i)placeholder''', '''000000+''' ]
Create a baseline of known findings to avoid blocking development while historical secrets are being rotated.
bash# Generate baseline from current state gitleaks detect --source . --report-format json --report-path .gitleaks-baseline.json # Subsequent scans compare against baseline (only new findings trigger failures) gitleaks detect --source . --baseline-path .gitleaks-baseline.json --exit-code 1 # Review baseline periodically and remove entries as secrets are rotated cat .gitleaks-baseline.json | python3 -m json.tool | head -50
When a secret is detected, follow the rotation and history cleanup procedure.
bash# 1. Immediately rotate the exposed credential # - Revoke the old API key/token in the service provider # - Generate a new credential # - Store the new credential in a secrets manager # 2. Remove secret from git history using git-filter-repo pip install git-filter-repo # Create expressions file for secrets to remove cat > /tmp/expressions.txt << 'EOF' regex:AKIA[0-9A-Z]{16}==>REDACTED_AWS_KEY regex:(?i)password\s*=\s*"[^"]*"==>password="REDACTED" EOF git filter-repo --replace-text /tmp/expressions.txt --force # 3. Force-push the cleaned history (coordinate with team) # git push --force --all # WARNING: Requires team coordination # 4. Add the secret pattern to .gitleaks.toml rules # 5. Update the baseline file to remove the resolved finding
| Term | Definition | |------|------------| | Secret | Any credential, token, key, or sensitive string that should not appear in source code | | Pre-commit Hook | Git hook that runs before a commit is created, blocking commits containing detected secrets | | Entropy | Measure of randomness in a string; high-entropy strings are more likely to be secrets | | Baseline | Snapshot of existing findings used to differentiate new secrets from pre-existing ones | | Allowlist | Configuration specifying paths, patterns, or commits to exclude from detection | | SARIF | Static Analysis Results Interchange Format for uploading findings to security dashboards | | git-filter-repo | Tool for rewriting git history to remove sensitive data from all commits |
Context: A 5-year-old repository has never been scanned. The team needs to enable secret scanning without blocking all development while historical secrets are rotated.
Approach:
gitleaks detect against full history and generate a baseline JSON filePitfalls: Generating a baseline without triaging means accepting risk on unrotated secrets. Never assume a historical secret is inactive without verifying with the service provider. Running git-filter-repo on a shared repository without coordination will cause rebase conflicts for all team members.
Gitleaks Secret Scanning Report
=================================
Repository: org/web-application
Scan Type: Full History
Commits Scanned: 4,523
Date: 2026-02-23
FINDINGS:
Total: 12
New (not in baseline): 3
Baseline (pre-existing): 9
NEW FINDINGS (blocking):
[1] AWS Access Key ID
Rule: aws-access-key-id
File: src/config/aws.py:23
Commit: a1b2c3d (2026-02-22, dev@company.com)
Secret: AKIA...REDACTED
Entropy: 3.8
[2] GitHub Personal Access Token
Rule: github-pat
File: scripts/deploy.sh:15
Commit: d4e5f6g (2026-02-21, ops@company.com)
Secret: ghp_...REDACTED
Entropy: 4.2
[3] Internal API Token
Rule: internal-api-token
File: src/services/auth.py:89
Commit: h7i8j9k (2026-02-20, dev@company.com)
QUALITY GATE: FAILED (3 new findings)
Action: Rotate exposed credentials immediately.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +14 percentage points is the difference between those two pass rates over the 22 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.