Skip to main content
Glama
Secret Scanner MCP Server

Secret Scanner MCP Server

by 0xmortuex
README.md
# Secret Scanner MCP Server: The Hard Way

Build an MCP server that finds secrets committed to a git repository, in the files and in history,
and tells an AI agent what to do about them, **without ever letting the secret reach the agent**.
Start from a regex and end with a server that is tested over real stdio for leaks.

At the end you will have built a minimal version of [secretscan-mcp](https://github.com/0xmortuex/secretscan-mcp)
yourself, and you will understand the parts most scanner tutorials skip: walking `git log -p` correctly,
keeping false positives low enough that people keep the tool on, and **redaction as a property of the types**
rather than a promise about the output.

## Why This Exists

A secret scanner is a security tool, and an MCP scanner is a security tool that talks to a language model.
That changes the design:

- Whatever the tool returns goes into a transcript, a log, and a model context. A scanner that prints the
  secret it found has just leaked it again.
- An agent will paste things to you and to tools. An error message that echoes its input will echo a secret.
- "Deleted in the last commit" is not "gone". The interesting leaks are in history.
- A noisy scanner is a disabled scanner. Precision is a feature, not polish.

## Who This Is For

- Developers who have built a "hello world" MCP server and want a real one with a safety property to defend
- Anyone curious how gitleaks and trufflehog work underneath
- Anyone building MCP tools whose output must never contain sensitive data

**Prerequisites:** Python 3.10+ and `git` on your PATH. Regexes help; everything is explained where it appears.

```bash
pip install -r requirements.txt
```

## Modules

| # | Module | What You'll Build |
|---|--------|-------------------|
| 01 | [Regex Rules](01-regex-rules/) | A rule table for provider tokens and PEM keys, and why anchors and alphabets matter |
| 02 | [Entropy](02-entropy/) | Shannon entropy plus name context for secrets with no prefix, and where entropy alone fails |
| 03 | [Walking Git History](03-walking-git-history/) | Streaming `git log -p -U0`, a hunk-count parser, and finding a secret that was added then deleted |
| 04 | [False-Positive Control](04-false-positive-control/) | Filters for hashes, UUIDs, placeholders and code, and a fingerprint-based baseline file |
| 05 | [Redaction by Design](05-redaction-by-design/) | A `Finding` type with nowhere to put the secret, an output guard, and a leak hunt |
| 06 | [Remediation](06-remediation/) | An ordered, provider-specific checklist as text, and an `.env` hygiene check |
| 07 | [The MCP Wrapper and Testing](07-the-mcp-wrapper-and-testing/) | The finished server, errors that reach the agent, and a stdio test that fails if a secret leaks |

Each module contains:
- **`README.md`**: the concepts and design decisions
- **`main.py`**: a working, commented implementation of that stage (standalone, no imports between modules)
- **`break-it.md`**: challenges that break your own code, with hints

## Every Module Is Verified

```bash
python verify.py
```

For each module `verify.py` runs `main.py` and then checks the result independently. Module 03 is checked
against git's own pickaxe search (`git log -S`) as an oracle for which commits touched the secret. Module 07
is driven over real MCP stdio by a second client. CI runs it on Linux, macOS and Windows every push and weekly.

**No real secrets, ever.** Every fake credential is assembled at run time from fragments plus seeded random
characters, so nothing in this repository looks like a leak to a scanner or to GitHub push protection. The only
literal is AWS's documented dummy key `AKIAIOSFODNN7EXAMPLE`.

## Architecture Overview

```
 AI agent (Claude Code, ...)
        |  MCP over stdio                                   <- Module 07
        v
 +--------------------------------------------------------+
 | tools: scan_tree, scan_history, explain_finding, ...   |
 |   guard(): re-check every result and error for secrets  |  <- Module 05
 |   Finding: no field can hold the raw value              |  <- Module 05
 |   filters + baseline: keep the signal                   |  <- Module 04
 |   detector: regex rules + entropy + context             |  <- Module 01, 02
 |   history: git log -p -U0, hunk-count parser            |  <- Module 03
 |   remediation: ordered checklist, text only             |  <- Module 06
 +--------------------------------------------------------+
        |  argument lists, read-only subcommands
        v
 the real `git` binary  ->  your repository
```

## Key Design Principles

**1. The agent never sees a secret.** Not in results, not in errors, not in logs. Enforce it with types, then
test it from the outside.

**2. Rotation is the fix; rewriting history is housekeeping.** Anything that was pushed was copied. Tell the user
that, in that order.

**3. Be quiet unless sure.** Prefer a missed weak secret to a scanner people disable.

**4. Shell out to the real tool, with argument lists.** `git` knows its own ignore rules and diff format.
A shell string turns a file name into a command.

**5. Errors are instructions.** "path is not a directory: ..." tells the agent what to fix. A traceback does not.

## Tech Stack

- **Language:** Python 3.10+
- **Dependencies:** `mcp` only. Detection, entropy, and the git walk are standard library.

## License

MIT. Use it, modify it, ship it.

---

Built by [0xmortuex](https://github.com/0xmortuex)