Skip to main content
Glama
Wsh7Ash

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).