Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/actions/mcp-audit/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: 'AgentShield MCP Audit'
description: 'Fail CI when MCP tool inventory fails pin/scan/allowlist policy gates'
inputs:
tools-file:
description: 'Path to tools JSON (list or {tools:[]})'
required: true
policy-file:
description: 'Path to policy YAML/JSON'
required: false
default: ''
pins-file:
description: 'Optional pin registry baseline JSON'
required: false
default: ''
fail-on-severity:
description: 'Fail when report severity is at least this level (low|medium|high|critical)'
required: false
default: 'high'
sarif-output:
description: 'Optional SARIF output path'
required: false
default: 'mcp-audit.sarif'
working-directory:
description: 'Repo subdirectory containing the python/ package (usually repo root)'
required: false
default: '.'
runs:
using: 'composite'
steps:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install AgentShield Python SDK
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
python -m pip install --upgrade pip
pip install -e "./python"

- name: Run MCP security audit
id: audit
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
set -euo pipefail
ARGS=(mcp audit --tools "${{ inputs.tools-file }}" --fail-on-severity "${{ inputs.fail-on-severity }}" --out mcp-audit-report.json)
if [ -n "${{ inputs.policy-file }}" ]; then
ARGS+=(--policy "${{ inputs.policy-file }}")
fi
if [ -n "${{ inputs.pins-file }}" ]; then
ARGS+=(--pins "${{ inputs.pins-file }}")
else
ARGS+=(--ignore-unpinned)
fi
if [ -n "${{ inputs.sarif-output }}" ]; then
ARGS+=(--sarif "${{ inputs.sarif-output }}")
fi
agentshield "${ARGS[@]}"
echo "report=mcp-audit-report.json" >> "$GITHUB_OUTPUT"

- name: Upload SARIF (optional)
if: ${{ inputs.sarif-output != '' }}
uses: actions/upload-artifact@v4
with:
name: agentshield-mcp-sarif
path: ${{ inputs.working-directory }}/${{ inputs.sarif-output }}
if-no-files-found: ignore
30 changes: 30 additions & 0 deletions .github/workflows/mcp-audit-example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: MCP Audit Example

# Manual example only — does not run on push/PR so poisoned fixtures do not fail default CI.
on:
workflow_dispatch:

jobs:
mcp-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: AgentShield MCP audit
uses: ./.github/actions/mcp-audit
with:
tools-file: python/examples/tools_poisoned.json
policy-file: python/examples/policy.yaml
fail-on-severity: high
sarif-output: mcp-audit.sarif
working-directory: .
continue-on-error: true

- name: Show report
if: always()
run: |
if [ -f mcp-audit-report.json ]; then
python3 -m json.tool mcp-audit-report.json | head -80
else
echo "No report generated"
fi
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@ NOTE-Cloning and reuse are allowed only by approved contributors under CLA.
- **Authentication**: JWT-based with bcrypt
- **Testing**: Jest for backend, React Testing Library for frontend
- **Charts**: Recharts for data visualization
- **Python SDK** (`python/agentshield`): MCP firewall pin/scan, action oracles, mutational packs, SARIF (Phase 0 / Phase 1 start)
- **Python SDK** (`python/agentshield`): MCP firewall (pin/scan/audit/policy), AgentBOM, action oracles, mutational packs, SARIF (Phase 0 + Phase 1)
- **MCP Security Gate**: Schema pins, rug-pull drift detection, poisoning scan, allowlist policy, GitHub Action CI gate — see [`docs/MCP_SECURITY.md`](docs/MCP_SECURITY.md)

## Strategy

AgentShield is evolving into an **Open Agent Security Fabric** (Assure + Enforce + Govern). See [`docs/STRATEGY.md`](docs/STRATEGY.md) and the refreshed [`docs/threat-model.md`](docs/threat-model.md) (OWASP LLM + Agentic + MCP Top 10).

**Priority:** MCP firewall first (Python-first runtime primitives), then adaptive Assure expansion.
**Priority:** MCP firewall first (Python-first runtime primitives), then adaptive Assure expansion. Phase 1 MCP audit/API/CI is documented in [`docs/MCP_SECURITY.md`](docs/MCP_SECURITY.md).

## 🚀 Quick Start

Expand Down
49 changes: 49 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,52 @@ curl -X POST http://localhost:5000/api/tests/123/execute \
curl -X GET "http://localhost:5000/api/results?page=1&limit=10" \
-H "Authorization: Bearer <token>"
```

## MCP Security API (Phase 1)

All `/api/mcp/*` routes require JWT (`Authorization: Bearer <token>`). Secrets in `env` / token fields are redacted in responses.

### POST /api/mcp/audit

Run scan + optional pin verify + policy allowlist.

```json
{
"tools": { "tools": [{ "name": "search_docs", "description": "...", "inputSchema": {} }] },
"policy": {
"fail_closed": true,
"allowed_tools": ["search_docs"],
"approval_required": ["write_file"],
"blocked_patterns": []
},
"server_id": "default",
"verify_pins": true,
"include_unpinned": true,
"include_sarif": false,
"prefer_python": true
}
```

**Response:** scored findings, `firewall_decisions`, `drift_findings`, `scan_findings`, optional `sarif`, and `agentbom`.

### POST /api/mcp/pin

Persist pins for the authenticated user (in-memory store for Phase 1).

### POST /api/mcp/verify

Compare live tools to stored pins; returns `schema_drift` findings on rug pulls.

### GET /api/mcp/pins

List stored pins (`?server_id=` optional filter).

### POST /api/mcp/sarif

Same inputs as audit; returns SARIF 2.1.0 only.

### POST /api/mcp/bom

Build or return AgentBOM from an audit report body.

See also: [`docs/MCP_SECURITY.md`](MCP_SECURITY.md).
101 changes: 101 additions & 0 deletions docs/MCP_SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# MCP Security — Phase 1

AgentShield’s MCP & Tool Firewall turns tool inventories into a **fail-closed security gate**: pin schemas, scan for poisoning, detect rug pulls, enforce allowlists, export SARIF, and emit an AgentBOM.

## Quick start (CLI)

```bash
cd python
pip install -e ".[dev]"

# Full audit (scan + pin verify + policy)
agentshield mcp audit \
--tools examples/tools_poisoned.json \
--policy examples/policy.yaml \
--ignore-unpinned \
--sarif /tmp/mcp.sarif \
--fail-on-severity high

# Pin registry
agentshield mcp pin-registry add --tools examples/tool.json --pins /tmp/pins.json
agentshield mcp pin-registry verify --tools examples/tool.json --pins /tmp/pins.json

# AgentBOM
agentshield mcp audit --tools examples/tool.json --policy examples/policy.yaml --ignore-unpinned --out /tmp/audit.json
agentshield bom generate --audit-report /tmp/audit.json --out /tmp/agentbom.json
```

## Policy file

```yaml
fail_closed: true
allowed_tools:
- search_docs
- read_file
approval_required:
- write_file
- send_email
blocked_patterns:
- "^exec_"
```

- `fail_closed: true` (default) denies tools not explicitly allowed or marked for approval.
- `require_approval` is recorded as a **High** finding in Phase 1 (no human UX yet).
- Override via env: `AGENTSHIELD_POLICY_FILE`, `AGENTSHIELD_ALLOWED_TOOLS`, `AGENTSHIELD_APPROVAL_REQUIRED`, `AGENTSHIELD_FAIL_CLOSED`.

## Pinning & rug-pull detection

Pins hash canonical `name + description + inputSchema` (SHA-256).

1. Approve tools → `mcp pin-registry add`
2. On reconnect / CI → `mcp pin-registry verify` or `mcp audit`
3. Description or schema drift → `schema_drift` / Critical finding (MCP02)

Default registry path: `.agentshield/pins.json` (or `AGENTSHIELD_PINS_FILE`).

## Adapter transports

| Transport | Config |
|-----------|--------|
| `file` | `{ "transport": "file", "path": "tools.json" }` |
| `stdio` | `{ "transport": "stdio", "command": ["python", "server.py"] }` |
| `sse` / `http` | `{ "transport": "http", "url": "https://…/mcp" }` |

Errors are structured (`AdapterError`) — never a silent empty success.

## CI (GitHub Action)

```yaml
- uses: ./.github/actions/mcp-audit
with:
tools-file: path/to/tools.json
policy-file: path/to/policy.yaml
pins-file: path/to/pins.json # optional
fail-on-severity: high
sarif-output: mcp-audit.sarif
```

Example workflow: `.github/workflows/mcp-audit-example.yml` (`workflow_dispatch`).

## API

JWT-protected routes (see `docs/API.md`):

- `POST /api/mcp/audit`
- `POST /api/mcp/pin`
- `POST /api/mcp/verify`
- `GET /api/mcp/pins`
- `POST /api/mcp/sarif`
- `POST /api/mcp/bom`

## Threat coverage (Phase 1)

| Control | MCP Top 10 |
|---------|------------|
| Description poisoning scan | MCP01 / MCP03 |
| Schema pin + drift | MCP02 |
| Typosquat / shadow tools | MCP03 / MCP08 |
| Allowlist / approval gate | MCP05 |
| SARIF + AgentBOM | Govern / supply-chain evidence |

Human-in-the-loop approval UI, CEL/Rego policy DSL, and runtime IFC are **Phase 2** (see `docs/PHASE1_GAPS.md`).
68 changes: 68 additions & 0 deletions docs/PHASE1_GAPS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Phase 1 Gaps & Follow-ups

**Branch:** `cursor/phase1-mcp-firewall-f5d6`
**Status after Phase 1 PR:** Implementation complete for D1–D8; items below are intentional deferrals and contributor fodder.
**Note for maintainers:** Convert suggested tasks into GitHub issues / good-first-issue labels **after merge**. Do not treat this file as an issue tracker.

---

## Completed (D1–D8)

- [x] **D1** MCP adapter — file / stdio / HTTP(SSE) transports (`python/agentshield/mcp/adapter.py`)
- [x] **D2** Pin registry + rug-pull / schema drift (`registry.py`, CLI `mcp pin-registry`)
- [x] **D3** Integrated audit pipeline (`audit.py`, CLI `mcp audit`)
- [x] **D4** Config-driven policy YAML/JSON (`policy.py`, env overrides)
- [x] **D5** Server API `/api/mcp/*` + Node mirrors + `server/tests/mcp.audit.test.js`
- [x] **D6** AgentBOM v1 (`python/agentshield/bom/agentbom.py`, CLI `bom generate`, audit embedding)
- [x] **D7** GitHub composite action + example workflow (`.github/actions/mcp-audit`, `mcp-audit-example.yml`)
- [x] **D8** Docs — `MCP_SECURITY.md`, API/README/STRATEGY/python README updates
- [x] **This file** — `docs/PHASE1_GAPS.md`

---

## Deferred (with reason)

| Item | Reason |
|------|--------|
| Human-in-the-loop approval UI | Explicit Phase 1 non-goal; `require_approval` recorded as High finding only |
| CEL / Rego policy compiler | Phase 2 policy DSL |
| Runtime IFC / quarantine SDK | Phase 2 `@agentshield/runtime` |
| LangChain / CrewAI / MAF middleware | Phase 2 bindings |
| CycloneDX export from AgentBOM | Optional stretch; not required for v1 BOM |
| Shadow-MCP network discovery | Out of scope; needs host telemetry |
| Persistent Mongo pin store as source of truth | JSON + in-memory user store sufficient for v1; Mongo model deferred |
| Official MCP Python SDK hard dependency | Minimal JSON-RPC client ships; optional SDK can wrap later |
| Audit result persistence / `GET /api/mcp/bom/:auditId` | Returns 501; BOM included on audit response + `POST /api/mcp/bom` |

---

## Known limitations

- Stdio/HTTP adapters speak a **minimal** `tools/list` JSON-RPC subset; full MCP session lifecycle (initialize, notifications, streaming tool calls) is not implemented.
- SSE parsing takes the last `data:` JSON payload; exotic multi-event servers may need a richer client.
- Node scan typosquat similarity uses a Levenshtein approximation; Python uses `SequenceMatcher` — rare score differences at the threshold boundary.
- Server pin registry is **per-process in-memory** (plus optional file path in Python CLI); multi-replica deployments need shared storage.
- GitHub Action example uses the **poisoned** fixture and is `workflow_dispatch` + `continue-on-error` so default CI stays green.
- Policy YAML parser is a built-in subset unless PyYAML is installed (`pip install agentshield[yaml]`).
- `prefer_python` subprocess from Node requires `python3` + editable install / `PYTHONPATH=python`; otherwise Node orchestration runs.

---

## Suggested contributor tasks (for maintainers to turn into issues later)

- [good first issue] Add more poisoning / typosquat fixtures under `python/tests/fixtures/` and assert rule_ids
- [good first issue] Document MCP stdio transport setup on Windows (command quoting, PATH)
- [good first issue] Add a *clean* tools fixture + CI example that expects exit 0
- [good first issue] Expand `docs/MCP_SECURITY.md` with a short “pin then verify” tutorial GIF/screenshots
- [good first issue] Wire `npm run test:mcp` alias in `server/package.json`
- [help wanted] CycloneDX 1.5 export from AgentBOM
- [help wanted] Mongoose `McpPinRegistry` model + migrate API off in-memory map
- [help wanted] Dashboard UI panel for MCP audit results / SARIF deep links
- [help wanted] Optional dependency on official MCP Python SDK with feature-detect wrapper
- [help wanted] Persist audit reports and implement `GET /api/mcp/bom/:auditId`
- [help wanted] Human approval queue UX for `require_approval` decisions (Phase 1.5)
- [help wanted] Align Node ↔ Python typosquat scoring to shared test vectors

---

*Generated at Phase 1 PR completion. Maintainers own issue creation from this list.*
Loading
Loading