Skip to main content
Glama
GuardX

GuardX

by harshzagade
README.md
<div align="center">

# πŸ›‘οΈ GuardX

**A keyless, defensive (blue-team) code-security auditor for the [Model Context Protocol](https://modelcontextprotocol.io).**
Let your AI assistant scan codebases for secrets, audit dependencies for known CVEs, and check passwords against breach data β€” all from natural language.

[![Python](https://img.shields.io/badge/Python-3.10%2B-3776AB?logo=python&logoColor=white)](https://www.python.org/)
[![MCP](https://img.shields.io/badge/MCP-compatible-8A2BE2)](https://modelcontextprotocol.io)
[![License: MIT](https://img.shields.io/badge/License-MIT-2ea44f)](LICENSE)
[![No API Keys](https://img.shields.io/badge/API%20keys-none%20required-success)]()
[![Use](https://img.shields.io/badge/use-defensive%20only-orange)]()
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)]()

</div>

---

> ⚠️ **Defensive use only.** Every tool is **read-only**, **privacy-preserving**, and needs **no API keys**. Use it only on code and credentials you own or are authorized to assess.

Unlike offensive recon tools that probe live websites, this server inspects **your own code and credentials** β€” making it the blue-team companion to your red-team tooling.

## Table of Contents

- [Features](#features)
- [Screenshots](#screenshots)
- [Installation](#installation)
- [Connect to an MCP Client](#connect-to-an-mcp-client)
- [Usage](#usage)
- [Sample output](#sample-output)
- [How the Privacy-Safe Password Check Works](#how-the-privacy-safe-password-check-works)
- [Data sources](#data-sources)
- [Tool Reference](#tool-reference)
- [Project Structure](#project-structure)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
- [License](#license)

## Features

- πŸ”‘ **Secret scanning** β€” detect hardcoded AWS keys, GitHub/Slack tokens, private keys, JWTs, and generic credentials. Findings are **masked**, so reports are safe to share.
- πŸ“¦ **Dependency auditing** β€” check `requirements.txt` (PyPI), `package.json` (npm), `go.mod` (Go modules), and `pom.xml` (Maven) against the free [OSV.dev](https://osv.dev) vulnerability database. Lockfiles (`package-lock.json`, `poetry.lock`, `Cargo.lock`) resolve transitive dependencies too. CycloneDX SBOM files (`sbom.json`, `cyclonedx.json`, `bom.json`, `*.cdx.json`, or any `.json` with `bomFormat: CycloneDX`) are audited too, across every ecosystem they list.
- πŸ” **Breached-password check** β€” query [Have I Been Pwned](https://haveibeenpwned.com/Passwords) using **k-anonymity**; the password never leaves your machine.
- 🚫 **No API keys, no accounts** β€” clone, install, run.
- πŸ€– **Native MCP** β€” works with Claude Code, Claude Desktop, Gemini CLI, OpenAI Codex, Cursor, and any MCP client.

## Screenshots

### πŸ”‘ Scan a project for hardcoded secrets
<p align="center">
  <img src="assets/scan_secrets.png" alt="scan_secrets output" width="800">
</p>

### πŸ“¦ Audit dependencies against the OSV vulnerability database
<p align="center">
  <img src="assets/audit_dependencies.png" alt="audit_dependencies output" width="800">
</p>

### πŸ” Check whether a password has been breached
<p align="center">
  <img src="assets/check_pwned_password.png" alt="check_pwned_password output" width="800">
</p>

## Installation

```bash
git clone https://github.com/harshzagade/Guardx-mcp.git
cd Guardx-mcp

# (recommended) create a virtual environment
python -m venv .venv
# Windows:
.venv\Scripts\activate
# macOS / Linux:
source .venv/bin/activate

pip install -r requirements.txt
```

**Requirements:** Python 3.10+ and the `mcp` + `httpx` packages (installed via `requirements.txt`).

## Connect to an MCP Client

GuardX runs locally over **stdio**, so any MCP-capable client can launch it. In every example below, replace `/absolute/path/to/guardx-mcp/server.py` with the real path on your machine. If you used a virtual environment, point `command` at that env's Python (e.g. `.venv/bin/python` or `.venv\Scripts\python.exe`) instead of `python`.

> πŸ’‘ First, confirm the server starts on its own (it then waits for a client on stdin β€” press `Ctrl+C` to exit):
> ```bash
> python server.py
> ```

<details open>
<summary><b>Claude Code</b> (CLI)</summary>

Register the server with one command:

```bash
claude mcp add guardx -- python /absolute/path/to/guardx-mcp/server.py
```

- Add `--scope project` to write it to a shared `.mcp.json` in your repo.
- Verify with `claude mcp list`, then use `/mcp` inside Claude Code.

</details>

<details>
<summary><b>Claude Desktop</b> (app)</summary>

Edit your `claude_desktop_config.json`:

- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "guardx": {
      "command": "python",
      "args": ["/absolute/path/to/guardx-mcp/server.py"]
    }
  }
}
```

Restart Claude Desktop; GuardX appears under the πŸ”Œ tools menu.

</details>

<details>
<summary><b>Gemini CLI</b> (& Gemini Code Assist)</summary>

Edit `~/.gemini/settings.json` (global) or `.gemini/settings.json` (per-project). The Gemini Code Assist IDE extension reads the same file:

```json
{
  "mcpServers": {
    "guardx": {
      "command": "python",
      "args": ["/absolute/path/to/guardx-mcp/server.py"]
    }
  }
}
```

Then run `gemini` and use `/mcp` to confirm the server is connected.

</details>

<details>
<summary><b>OpenAI Codex</b> (CLI & IDE extension)</summary>

Codex uses **TOML** and shares config between the CLI and the IDE extension. Either run:

```bash
codex mcp add guardx -- python /absolute/path/to/guardx-mcp/server.py
```

…or hand-edit `~/.codex/config.toml` ( note the **underscore** in `mcp_servers`):

```toml
[mcp_servers.guardx]
command = "python"
args = ["/absolute/path/to/guardx-mcp/server.py"]
```

> Codex only supports **local stdio** MCP servers β€” perfect for GuardX.

</details>

<details>
<summary><b>Cursor</b> / <b>VS Code</b></summary>

Create `.cursor/mcp.json` in your project (or the global `~/.cursor/mcp.json`):

```json
{
  "mcpServers": {
    "guardx": {
      "command": "python",
      "args": ["/absolute/path/to/guardx-mcp/server.py"]
    }
  }
}
```

VS Code (with MCP support) uses the same `mcpServers` shape in its settings.

</details>

> **Windows tip:** in JSON, write paths with forward slashes (`C:/Users/you/guardx-mcp/server.py`) or escaped backslashes (`C:\\Users\\you\\...`).

## Usage

Once connected, just ask your assistant in plain language:

| You say... | Tool used |
| --- | --- |
| *"Scan `./my-project` for hardcoded secrets"* | `scan_secrets` |
| *"Audit `requirements.txt` for known vulnerabilities"* | `audit_dependencies` |
| *"Has the password `password123` been pwned?"* | `check_pwned_password` |
| *"Check these three passwords against breaches"* | `check_pwned_passwords` |

## Pre-commit hook

`examples/pre-commit` runs `scan_secrets` on staged files and blocks the
commit when a HIGH-severity secret is staged, so a key never reaches the
remote in the first place. Install it into any repository:

```bash
cp examples/pre-commit /path/to/your-repo/.git/hooks/pre-commit
chmod +x /path/to/your-repo/.git/hooks/pre-commit
```

The hook finds GuardX via `$GUARDX_HOME` (or `~/guardx-mcp`); it is
stdlib-only, so no `pip install` is needed just to run it. Only staged
files are scanned β€” an uncommitted scratch secret never blocks you β€” and
only HIGH findings block; MED/LOW findings warn, or block too with
`GUARDX_STRICT=1`. A `.guardx-baseline` next to a staged file still
suppresses acknowledged findings. The hook scans the file as it sits in
your worktree, not the exact staged blob, so re-stage after editing
before committing.

## Sample output

Real output from running the tools (secrets shown are intentionally fake):

**`scan_secrets("demo-project")`**

```
Secret scan of demo-project
Files scanned: 1
Findings: 2 (HIGH: 2, MED: 0, LOW: 0)
Unique secrets: 2

  [HIGH] AWS Access Key ID
      demo-project/app.py:1
      match: AKIA...LE (len 20)

  [HIGH] Stripe Secret Key
      demo-project/app.py:2
      match: sk_l...Ab (len 33)

Note: these are heuristic matches and may include false positives. Rotate any real secret that is committed to source control.
```

**`audit_dependencies("requirements.txt")`** β€” with `requests==2.28.0` and `urllib3==1.26.0` pinned:

```
Dependency audit of requirements.txt
Ecosystem: PyPI
Dependencies checked: 2
Vulnerable packages: 2

  [!] requests==2.28.0  -> 8 known vuln(s)
      - GHSA-9hjg-9r4m-mvj7 (CVE-2024-47081, PYSEC-2026-1872): Requests vulnerable to .netrc credentials leak via malicious URLs
      - GHSA-9wx4-h78v-vm56 (CVE-2024-35195, PYSEC-2026-1873): Requests `Session` object does not verify requests after making first request with verify=False
      - GHSA-gc5v-m9x4-r6x2 (CVE-2026-25645, PYSEC-2026-2275): Requests has Insecure Temp File Reuse in its extract_zipped_paths() utility function
      ...

  [!] urllib3==1.26.0  -> 24 known vuln(s)
      - GHSA-2xpw-w6gg-jr37 (CVE-2025-66471, PYSEC-2026-1994): urllib3 streaming API improperly handles highly compressed data
      ...
```

**`check_pwned_password("password123")`**

```
WARNING: this password has appeared in known breaches 2,266,543 times. [!]
Do not use it. Choose a unique, strong password and enable MFA.
(Only the SHA-1 prefix was sent; the password never left this machine.)
```

A password not found in any breach returns `Good news: this password was NOT found in any known breach. [OK]`.

## How the Privacy-Safe Password Check Works

`check_pwned_password` follows the [k-anonymity model](https://haveibeenpwned.com/API/v3#PwnedPasswords) recommended by Have I Been Pwned:

1. The password is hashed locally with **SHA-1**.
2. Only the **first 5 hex characters** of the hash are sent to the HIBP range API.
3. The API returns all hash suffixes sharing that prefix; the match is found **locally**.
4. The request sends `Add-Padding: true`, so every response is padded to the
   same size β€” a network observer cannot infer which prefix you looked up
   from the response length.

➑️ Your password and its full hash **never leave your machine**.

## Data sources

- **Vulnerabilities:** `audit_dependencies` queries the [OSV.dev](https://osv.dev) API **live on every run** β€” there is no local vulnerability database to download or refresh, so results reflect the latest published advisories at query time. Each query sends only `{package name, version, ecosystem}`. Freshness caveat: advisories appear in OSV as it ingests them from upstream sources (NVD, GitHub Security Advisories, PyPA, …), so a brand-new CVE may take a little while to show up. Internet access is required; lookups that fail (network error or non-200 response) are reported in the output rather than silently dropped.
- **Breached passwords:** `check_pwned_password` uses the [Have I Been Pwned Pwned Passwords API](https://haveibeenpwned.com/API/v3#PwnedPasswords) with k-anonymity (see above). Internet access is required. The breach corpus is maintained by HIBP and refreshed as new breaches are added β€” the breach counts you see reflect the corpus at query time, not a fixed snapshot.

## Tool Reference

| Tool | Signature | Description |
| --- | --- | --- |
| **Secret scanner** | `scan_secrets(path, max_findings=200, exclude=None, json_output=False, baseline=None, since=None, history=False)` | Recursively scans a file or directory for hardcoded secrets. Skips binaries and common ignore dirs (`.git`, `node_modules`, …), and honours a `.gitignore` at the scan root (basename patterns at any depth, root-anchored patterns, trailing-slash dir patterns, `!` negations β€” last match wins). A comma-separated `exclude` glob list (e.g. `"*.min.js,fixtures/*"`) skips extra noisy paths on top. Long minified lines are scanned in overlapping windows instead of being skipped. Findings are graded HIGH/MED/LOW and the report header carries per-severity counts plus a "Unique secrets" count β€” the number of distinct secret values across the whole scan, so one key committed in twelve files shows as one unique secret. The header also lists per-file finding counts (most-affected file first), mirrored as `per_file_counts` in JSON output. A "Unique secrets by file" list follows it, mapping each distinct secret value (masked preview) to the files it appears in with its finding count β€” so a key copied across twelve files is one line to rotate, not twelve β€” mirrored as `unique_secret_files` in JSON output. Lines no known pattern matches are also checked for long high-entropy quoted strings (at least 20 characters, entropy at least 4.0 bits/char), reported as LOW "High-Entropy String" findings for custom token formats. With `json_output=True` the report is returned as machine-readable JSON (file, line, type, severity, masked match per finding) for CI pipelines. A `.guardx-baseline` file at the scan root (or an explicit `baseline=` path) suppresses acknowledged findings: each line holds the SHA-256 hash of one finding (relative path + type + secret value, line numbers excluded so suppression survives edits), and suppressed findings are counted in the header and as `suppressed_count` in JSON output instead of being reported β€” so CI stays green on reviewed false positives while new findings still surface. In config files (`.env`, `.ini`, YAML, JSON, TOML, …), a credential-named key (last name token `password`/`secret`/`token`/…) with a non-placeholder value is also reported as a MED "Config Credential" finding, catching unquoted values and `:` assignments the generic pattern misses; placeholder values (`changeme`, `${VAR}` references, masked `***`) stay clean. In infrastructure-as-code files (Terraform `.tf`/`.tfvars`, CloudFormation/ARM `.json`/`.yaml`), a credential assignment inside a sensitive resource block (e.g. `aws_db_instance`, `AWS::RDS::DBInstance`, `Microsoft.Sql/servers`) is reported as a HIGH "IaC Credential" finding; the same assignment outside a sensitive block is left to the generic/config heuristics, and `var.`/`local.` references stay clean. An optional `since=` selector runs an incremental scan: a git ref scans only files changed since that ref (staged/unstaged edits plus untracked files), and a date/timestamp scans only files modified after that time β€” for fast CI runs on large repos. With `history=True` the scan reads `git log -p` instead of the working tree, so a secret committed and later removed is still found: each finding names its commit short-SHA, the header reports commits scanned (capped at the 500 most recent), and the report warns that history findings stay in git until the history is rewritten. `.gitignore` is deliberately not applied in history mode (a committed-then-ignored file is exactly what it is for), while `exclude` patterns and the baseline still apply; `history` rejects `since=`. Values are masked so reports are safe to share. |
| **Dependency auditor** | `audit_dependencies(path, min_severity=None)` | Parses a `requirements.txt`, `package.json`, `go.mod`, or `pom.xml` and checks each pinned dependency against OSV.dev. CycloneDX SBOMs are supported too: each component is identified by its package URL, so one SBOM can mix Maven, npm, PyPI, Go, and crates.io components and each is queried against its own ecosystem (one querybatch call per ecosystem; the header lists them all). Returns CVE/GHSA ids and summaries. Lockfiles are supported too: `package-lock.json` (npm, incl. nested/scoped packages and the legacy v1 tree), `poetry.lock` (PyPI), and `Cargo.lock` (crates.io) β€” so transitive dependencies are audited, not just direct ones. An optional `min_severity` floor (`LOW`, `MEDIUM`/`MED`, `HIGH`, `CRITICAL`) hides lower-severity vulnerabilities from the report (counted in the header); each reported vulnerability is tagged with its CVSS band and score (e.g. `[HIGH 7.5]`), and unscored vulnerabilities are tagged `[UNKNOWN]` and hidden by any filter. Each finding also carries fixed-version guidance from the OSV `affected` ranges: "upgrade to >= X" names the lowest version that closes every affected range for the queried version, or "no fixed version released" when an affected range has no fix yet; vulnerabilities without range data carry no guidance. The same package@version is queried only once per run: duplicate manifest entries are served from a per-run OSV cache and counted as cache hits in the header. All distinct dependencies go in a single `POST /v1/querybatch` call instead of one query per package, so a large manifest costs one round trip (same 429 retry behaviour; a failed batch counts every dep as a lookup failure rather than claiming it clean). |
| **Pwned-password check** | `check_pwned_password(password)` | Checks a password against HIBP via k-anonymity. Reports breach count without transmitting the password. Retries twice with jittered backoff on transient network failures. Successful range responses are cached per hash prefix for 5 minutes, so re-checking the same password (or another sharing its prefix) skips the network; error responses are never cached. |
| **Batch pwned-password check** | `check_pwned_passwords(passwords)` | Checks a list of passwords in one call: one summary line per password (numbered, so no password ever appears in the output), plus a header with breached/clean/error counts. Passwords sharing a SHA-1 hash prefix are looked up once β€” one range request per distinct prefix β€” and a per-prefix lookup failure is reported on that prefix's lines instead of failing the whole batch. Lookups also reuse the 5-minute prefix cache, so a later call re-checking any of these passwords skips the network. |
| **One-shot audit prompt** | `audit_this_codebase_prompt(path=".", manifest=None)` | MCP prompt template chaining the tools into a single audit: run `scan_secrets` on the codebase, then `audit_dependencies` on its manifest, then write a remediation summary (secrets to rotate first, HIGH before MED/LOW, then dependencies to upgrade, then suspected false positives) using only masked previews and making no file changes. |

## Project Structure

```
guardx-mcp/
β”œβ”€β”€ server.py          # MCP server + the three tools
β”œβ”€β”€ examples/
β”‚   └── pre-commit     # git hook: blocks commits staging HIGH secrets
β”œβ”€β”€ tests/             # pytest suite (stdlib-only, no extra installs)
β”‚   β”œβ”€β”€ test_scan_secrets.py
β”‚   β”œβ”€β”€ test_config_credentials.py
β”‚   β”œβ”€β”€ test_baseline.py
β”‚   β”œβ”€β”€ test_pwned_password.py
β”‚   └── test_gitignore.py
β”œβ”€β”€ requirements.txt   # runtime dependencies (mcp, httpx)
β”œβ”€β”€ assets/            # README screenshots
β”œβ”€β”€ README.md
β”œβ”€β”€ CHANGELOG.md
β”œβ”€β”€ LICENSE            # MIT
└── .gitignore
```

## Troubleshooting

- **`ModuleNotFoundError: No module named 'mcp.server.fastmcp'`** β€” you installed `mcp` 2.x, which renamed `FastMCP` to `MCPServer`. GuardX targets the 1.x SDK, so pin it in your venv:
  ```bash
  pip install "mcp<2" httpx
  ```
- **`audit_dependencies` / `check_pwned_password` report network failures** β€” both tools need internet access (OSV.dev and Have I Been Pwned respectively). Corporate proxies may need to be configured in your environment.

## Contributing

Contributions are welcome! Ideas: more secret patterns or additional ecosystems
(Cargo). Please keep all contributions
**defensive** in nature. Open an issue or a pull request.

## License

Released under the [MIT License](LICENSE).

---

<div align="center">
<sub>Built as a defensive blue-team companion. Scan responsibly. πŸ›‘οΈ</sub>
</div>