Secrets scanning for MCP server CI pipelines: gitleaks, trufflehog, and pre-commit hooks
A committed secret is a revoked secret — or should be. The problem is that revocation is slow, public repositories are indexed by bots within minutes of a push, and "I'll just remove it in the next commit" leaves the credential visible in git history forever. For MCP server repositories, the stakes are higher than average: MCP API keys, GitHub PATs, and upstream service tokens committed to source give any reader the ability to call your tools as an authenticated user, exfiltrate data via your tool's upstream API connections, or impersonate your server entirely. Secrets scanning blocks this before it happens.
Layer 1: pre-commit hook with gitleaks
The highest-value layer is blocking secrets before they touch git history at all. A pre-commit hook runs gitleaks detect on the staged changes; if it finds a pattern match, the commit is rejected with a clear error before the secret enters the repository.
Install gitleaks and wire the hook:
# Install gitleaks (macOS)
brew install gitleaks
# Install gitleaks (Linux — download binary)
curl -sSfL https://github.com/gitleaks/gitleaks/releases/download/v8.18.2/gitleaks_8.18.2_linux_x64.tar.gz \
| tar -xz && sudo mv gitleaks /usr/local/bin/
# Wire as a pre-commit hook (project-local)
cat > .git/hooks/pre-commit <<'EOF'
#!/bin/sh
gitleaks protect --staged --redact --exit-code 1
if [ $? -ne 0 ]; then
echo "gitleaks: secret detected in staged changes. Commit blocked."
echo "Remove the secret and store it in your environment instead."
exit 1
fi
EOF
chmod +x .git/hooks/pre-commit
For teams, use pre-commit to manage hooks declaratively so every contributor gets the same check on clone:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.2
hooks:
- id: gitleaks
Custom patterns for MCP-specific secrets
Off-the-shelf gitleaks rulesets cover common provider patterns (GitHub PATs, AWS keys, Stripe keys) but won't know your MCP server's own API key format. Add custom rules in .gitleaks.toml:
# .gitleaks.toml
[extend]
useDefault = true # inherit the built-in 150+ patterns
[[rules]]
id = "mcp-api-key"
description = "SkillAudit / MCP server API key"
regex = '''mcp_[a-zA-Z0-9]{32,64}'''
tags = ["api-key", "mcp"]
[[rules]]
id = "mcp-server-private-key"
description = "MCP server signing key (base64 encoded)"
regex = '''mcp_sk_[a-zA-Z0-9+/]{40,}={0,2}'''
tags = ["signing-key", "mcp"]
# Allowlist for test fixtures that use obviously-fake keys
[allowlist]
regexes = [
'''mcp_test_[a-z]+_placeholder''',
'''example_token_do_not_use''',
]
Layer 2: trufflehog in GitHub Actions CI
Pre-commit hooks can be skipped with --no-verify. The CI layer is the safety net that runs unconditionally on every push. Trufflehog is well-suited for CI: it scans not just the latest diff but the entire commit range being pushed, catching secrets committed in an intermediate commit that was later "cleaned" in a follow-up commit (the secret is still in history):
# .github/workflows/secrets-scan.yml
name: Secrets Scan
on:
push:
branches: ["**"]
pull_request:
jobs:
trufflehog:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history for branch diff scan
- name: Run TruffleHog on commit range
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
extra_args: --only-verified --fail
The --only-verified flag reduces false positives by only flagging secrets that trufflehog can verify are valid (live) credentials by making an API call to the issuing service. This is the correct default for CI — you want zero false positives blocking CI, and any unverified potential secret should be reviewed in a separate workflow that doesn't block merges.
Layer 3: git history scan on first setup
Before setting up secrets scanning, scan the entire git history of your MCP server repository to check whether any secrets have already been committed and should be revoked. This is a one-time operation:
# Scan full git history (can take minutes on large repos)
trufflehog git file://. --only-verified
# Or with gitleaks
gitleaks detect --source . --log-opts="--all"
If either tool finds a live credential in history: revoke it immediately, then rotate it in whatever service issued it. Removing it from the current branch is not enough — the commit is in history, and any clone of the repository has the secret. Revocation is the only effective remediation.
Emergency response: secret committed to main
If a secret reaches the main branch before the CI scan blocks it:
- Revoke immediately. Before anything else, invalidate the credential at the issuing service. Speed matters — bots index GitHub within seconds.
- Rotate. Issue a new credential. Do not re-use the revoked one.
- Remove from history using BFG or git filter-repo. This rewrites history — coordinate with all active contributors before force-pushing.
- Force-push after cleanup. All contributors must re-clone or reset to the new history.
- Post-mortem. Add a custom gitleaks rule for the credential format if one was missing.
What SkillAudit checks for
SkillAudit's credential exposure scan runs trufflehog-equivalent pattern matching against your source code at scan time. Findings include:
- CRITICAL: verified live credential in source — an API key, token, or password that is live and callable. Blocks grade above D.
- HIGH: hardcoded credential pattern detected (unverified) — a string matching a known credential pattern that could not be verified live. May be a test fixture — SkillAudit will note whether it appears in a test file.
- MEDIUM: no gitleaks config present — no
.gitleaks.tomldetected in the repository. Indicates no custom patterns are protecting MCP-specific credential formats. - LOW: no CI secrets scanning workflow — no evidence of a trufflehog or gitleaks step in any detected CI workflow.
Check your MCP server repo for committed secrets
Run a free audit →