scopeguard-mcp
by Wsh7Ash
README.md
# scopeguard-mcp
An MCP stdio server that gives bug-hunting AI agents an **enforceable authorized-scope guardrail**: every target is checked against a declarative allow/deny list before a finding can be recorded, and evidence is stripped of secrets on the way in.
## Problem
Agent-driven bug hunting fails in a predictable way: the agent happily turns
toward a host the operator never authorized, or it pastes a live credential
from a captured response straight into a finding (and from there into an LLM
prompt). Human review catches that after the fact — if at all.
`scopeguard-mcp` puts the check *in the middle of the loop*, as MCP tools the
agent must call:
1. `check_target` answers "am I allowed to touch this?" before any request.
2. `add_finding` re-checks the same scope itself and **refuses** out-of-scope
records, so a rule skipped by the agent still holds.
3. `redact` removes tokens, keys, JWTs, card numbers and e-mail addresses from
evidence before it leaves the workstation.
## Key features
- **Declarative scope** — JSON allow/deny list with `host` (wildcard `*.a.b`),
`url` (scheme + path prefix) and `cidr` rules. Deny always wins; loopback is
always allowed so the local lab works out of the box.
- **No network activity, ever** — the scope decision is pure string/IP
matching. No DNS lookups, no connections, so a check can never become a
side channel to an unauthorized host.
- **CVSS v3.1 Base scoring** — verified against FIRST's published example
vectors (9.8, 8.8, 7.8, 8.1, 7.5, 6.1, 0.0).
- **Evidence redaction** — GitHub/Slack/AWS tokens, PEM private keys, JWTs,
bearer headers, `api_key=...` assignments, URL credentials, Luhn-valid card
numbers and e-mail addresses.
- **Append-only JSONL ledger** — each finding stores the scope verdict that
allowed it, with sequential IDs and corrupt-line tolerance.
- **Zero dependencies** — Python 3.10+ standard library only, including the
JSON-RPC layer.
## Example session
```text
-> {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26",...}}
<- {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-03-26","capabilities":{"tools":{}},"serverInfo":{"name":"scopeguard-mcp",...}}}
-> {"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"check_target","arguments":{"target":"https://prod.staging.example.test"}}}
<- {"jsonrpc":"2.0","id":4,"result":{"content":[{"type":"text","text":"{\"allowed\": false, \"reason\": \"explicitly denied by deny rule host:prod.staging.example.test\"}"}],"isError":false}}
-> {"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"add_finding","arguments":{"target":"https://victim.example.org",...}}}
<- {"jsonrpc":"2.0","id":9,"result":{"content":[{"type":"text","text":"refused (out of scope): no matching allow rule..."}],"isError":true}}
```
The full captured run is in [`results/demo_transcript.txt`](results/demo_transcript.txt).
## Architecture
```text
MCP client (Claude Code / opencode / Cursor)
│ newline-delimited JSON-RPC 2.0 over stdin/stdout
▼
server.py ── initialize / tools/list / tools/call / ping
├─ scope.py allow|deny verdicts (pure, no I/O)
├─ redact.py secret + PII scrubbing
├─ cvss.py CVSS:3.1 Base score
└─ findings.py append-only JSONL ledger
```
Protocol revision is pinned to **2025-03-26** (the `initialize` handshake
era); `2024-11-05` clients are negotiated with. See
[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
## Requirements
- Python 3.10 or newer
- No third-party runtime dependencies
- An MCP-capable client for interactive use (tests and demo only need Python)
## Installation
```shell
git clone https://github.com/Wsh7Ash/scopeguard-mcp
cd scopeguard-mcp
pip install -e . # installs the scopeguard-mcp console script
```
For a throwaway run without installing:
```shell
PYTHONPATH=src python -m scopeguard_mcp
```
## Quick start
```shell
# 1. Show the active scope (loopback-only by default)
PYTHONPATH=src python -m scopeguard_mcp --print-scope
# 2. Run the scripted end-to-end session
PYTHONPATH=src python examples/demo_session.py
# 3. Run the test suite
PYTHONPATH=src python -m pytest -q
```
## Usage examples
### Register with an MCP client
```json
{
"mcpServers": {
"scopeguard": {
"command": "scopeguard-mcp",
"args": ["--scope", "examples/scope.json", "--ledger", ".findings.jsonl"]
}
}
}
```
### Authorize a staging host
Edit the scope document — see [examples/scope.json](examples/scope.json):
```json
{
"name": "local-lab",
"allow": [{ "type": "host", "value": "*.staging.example.test" }],
"deny": [{ "type": "host", "value": "prod.staging.example.test" }]
}
```
### Record a finding
```text
tools/call add_finding
title: Debug endpoint reflects Authorization header
target: http://127.0.0.1:8080/debug
evidence: Authorization: Bearer ghp_... echoed in HTML
severity: high
cvss: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N
→ stored with id 1, evidence redacted, cvss_score 8.2
```
## Configuration
| Flag | Default | Meaning |
|---|---|---|
| `--scope PATH` | loopback-only built-in scope | JSON scope document |
| `--ledger PATH` | `./scopeguard-findings.jsonl` | findings ledger file |
| `--print-scope` | – | print the active scope as JSON and exit |
Scope document fields: `name`, `description`, `allow[]`, `deny[]`, each rule
being `{"type": "host|url|cidr", "value": "..."}`.
## Testing
```shell
PYTHONPATH=src python -m pytest -q # 43 tests
```
Coverage: CVSS fixtures from FIRST, scope matching (wildcards, path prefixes,
CIDR, deny precedence), redaction patterns, ledger behaviour, and a
subprocess-level MCP protocol test that exercises the real handshake,
tool calls, tool errors and JSON-RPC error codes.
## Project structure
```text
scopeguard-mcp/
├─ src/scopeguard_mcp/
│ ├─ server.py JSON-RPC dispatch + tool registry
│ ├─ scope.py allow/deny evaluation (no I/O)
│ ├─ redact.py secret/PII scrubbing
│ ├─ cvss.py CVSS v3.1 Base score
│ ├─ findings.py JSONL ledger
│ └─ __main__.py CLI
├─ tests/ unit + protocol tests
├─ docs/ ARCHITECTURE.md, USAGE.md, PROJECT_SPEC.md
├─ examples/ scope.json, demo_session.py
├─ results/ demo_transcript.txt
└─ SECURITY.md
```
## Limitations
- Base metric group only: Temporal and Environmental scores are not computed.
- No DNS resolution by design, so `cidr` rules only match IP-literal targets.
- The ledger is a local file with no locking; two concurrent writers could
interleave lines (corrupt lines are skipped on read).
- Redaction is pattern-based. It catches common token shapes, not every
bespoke secret; treat it as a safety net, not a guarantee.
- It guards *scope*, not *behaviour*: nothing here stops an authorized agent
from making a destructive request to an authorized host.
## Roadmap
- Optional `tools/list` pagination (`nextCursor`).
- Ledger export to SARIF for CI consumption.
- Scope rule unit tests as a CLI subcommand (`--check-rule`).
## Security notes
Defensive tooling for targets you own or are explicitly authorized to test.
See [SECURITY.md](SECURITY.md) for authorized-use wording, threat model and
reporting.
## License
MIT — see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues