A .NET-first hardcoded-secret scanner for source files and git history. Reports location only — never values.
Not to be confused with
dotnet user-secrets. That command stores secrets locally for development; this tool finds secrets that have been hardcoded into your source or committed to your history.
Removing a secret from the working tree does not remove it from git history. A credential committed at any point in a repository's lifetime remains fully readable to anyone who can clone it — including public forks, CI systems, and compromised laptops.
Existing tools (truffleHog, gitleaks) are general-purpose and return raw secret values in their output, which creates a second exposure risk when piped through logs or AI tools.
This tool is .NET-first and architecturally prevents value leakage at the type level. The raw match record is internal sealed; the only exit point is a sanitiser that produces a public Finding shape with no field that can carry a value.
- Sends no telemetry, no analytics, no usage data, no findings, no file paths.
- Performs no version checks. Does not phone home.
- Network operations: zero. Enforced by an invariant test that scans the published assemblies for forbidden namespaces (
System.Net.Http,Sockets,HttpClient,WebRequest, etc.).
dotnet tool install -g dotnet-tool-secrets-scan
# Scan the working tree
dotnet tool-secrets-scan --path ./src
# Scan git history
dotnet tool-secrets-scan history --severity high
# Install a pre-commit hook
dotnet tool-secrets-scan --install-hookExit codes: 0 no findings, 1 findings present, 2 tool error (bad args, not a git repo, cap-policy hit).
See docs/usage.md for the full flag list, configuration sources, and common invocation patterns.
dotnet tool install -g tool-secrets-scan-mcpThen register in .claude/settings.json:
{
"mcpServers": {
"secrets-scan": {
"command": "tool-secrets-scan-mcp"
}
}
}Claude can now call scan_for_secrets and scan_git_history. PII (author names, email-shaped strings, user-home paths) and the entropy field are stripped at the wire boundary regardless of caller config — Claude never sees them.
- run: dotnet tool install -g dotnet-tool-secrets-scan
- run: dotnet tool-secrets-scan --severity medium
- run: dotnet tool-secrets-scan history --since ${{ github.event.before }} --severity highA composite GitHub Action (baardie/dotnet-tool-secrets-scan-action) is on the roadmap; for now use the two run: steps directly.
console(default) — grouped by file, severity-coloured, with commit metadata for history findingsjson— versioned envelope,schemaVersion: "1", suitable for pipingsarif— SARIF v2.1.0 for GitHub Code Scanning, Azure DevOps, Defender
| Category | Examples |
|---|---|
| Connection strings | SQL Server, MongoDB, PostgreSQL with embedded Password=/Pwd= |
| Cloud credentials | AWS access keys (AKIA*/ASIA*/AROA*), Azure storage keys |
| Provider tokens | GitHub PATs (classic + fine-grained), GitLab PATs, Stripe live keys, Slack tokens |
| Secrets and keys | JWT signing secrets, hardcoded bearer tokens, private keys / certificates |
| Generic | API key fields, password fields in config files; opt-in high-entropy heuristic |
Patterns are .NET-first with a layered detection pipeline: keyword pre-filter, compiled regex with 200ms timeout, Shannon entropy validation, and placeholder rejection. Files are extension-filtered before scanning rather than structurally parsed — minimal dependencies, predictable performance on large repos. AST-based detection (Tier B) is on the v1.x roadmap; the Roslyn dependency was removed pre-release rather than shipped half-implemented.
See docs/severity-rubric.md. Briefly:
- Critical — direct production compromise (prod DB, AWS, Stripe live, private keys)
- High — single-service compromise (third-party API keys, JWT signing secrets)
- Medium — heuristic detections that need triage
- Low — likely false-positive but flagged for thoroughness
src/
SecretsScanner.Core/ # Scanning library — sole owner of raw values
SecretsScanner.Cli/ # dotnet-tool-secrets-scan
SecretsScanner.Mcp/ # tool-secrets-scan-mcp (Claude Code)
tests/
SecretsScanner.Core.Tests/
SecretsScanner.Cli.Tests/
SecretsScanner.Mcp.Tests/
SecretsScanner.E2E.Tests/ # fixture repos with seeded secrets
SecretsScanner.Bench/ # BenchmarkDotNet + a 'validate' fast-path
docs/
PRD.md # Product requirements
severity-rubric.md # Per-pattern severity reasoning
schema-changelog.md # Wire-shape compatibility policy
non-goals.md # What this tool deliberately does not do
- CHANGELOG.md — release notes; the v1.0.0 entry covers the safety invariant and every R1–R20 risk decision
- docs/usage.md — full flag list, configuration sources, exit codes, and invocation cookbook
- docs/severity-rubric.md — severity definitions and per-pattern rationale
- docs/schema-changelog.md — wire-shape compatibility policy
Issues and PRs at https://github.com/baardie/SecretsChecker.
The single most important review rule: never add a field to Finding that could carry a secret value, and never expose RawMatch outside the core library. The safety invariant is enforced structurally — keep it that way.
MIT.