Skip to content

Releases: churik5/bulwark-mcp

v0.4.2

Choose a tag to compare

@github-actions github-actions released this 05 May 18:09

See CHANGELOG.md.

v0.4.1

Choose a tag to compare

@github-actions github-actions released this 05 May 15:33

Changed

  • Renamed project: mcp-firewallbulwark-mcp. PyPI name conflict
    with an unrelated project of the same name (different architecture:
    OPA / Rego, RBAC, compliance). Discovered before the public launch.
    See ADR-0006.
    • PyPI distribution name: bulwark-mcp (was mcp-firewall).
    • CLI command: bulwark (was mcp-firewall).
    • Python module / import name: bulwark_mcp (was mcp_firewall).
    • GitHub repository: churik5/bulwark-mcp (was churik5/mcp-firewall).
    • Environment variables: BULWARK_TELEMETRY, BULWARK_TELEMETRY_URL,
      BULWARK_DB, BULWARK_CONFIG (were MCP_FIREWALL_*).
  • No functional changes. Same code, same behaviour, same versioned schemas.

Migration from v0.4.0

pip uninstall mcp-firewall            # if you installed the prior wheel
pip install bulwark-mcp               # once published
# CLI: replace `mcp-firewall <cmd>` with `bulwark <cmd>` everywhere.
# Env vars: rename MCP_FIREWALL_* → BULWARK_* in your shell rc / systemd unit.
# Existing data/log.db opens unchanged; schema stays at v2.

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 05 May 14:41

Added

  • mcp-firewall doctor — environment diagnostic with four checks
    (Python version, Ollama reachable + model loaded, audit DB writable
    at schema v2, rules + policy validate). Exit code reflects worst
    status: 0 = pass, 1 = warn, 2 = fail.
  • mcp-firewall benchmark — three workloads (rules detector,
    inspector cache hit, end-to-end via cat) with p50/p95/p99 output.
  • GitHub Actions workflows for the launch:
    • publish.yml — PyPI OIDC trusted publishing on tag.
    • test-publish.yml — manual test.pypi.org publishing.
    • release.yml — auto release notes extracted from CHANGELOG.
    • sync-labels.yml — applies .github/labels.yml on push.
    • auto-label.yml — keyword-based labels on new issues.
    • welcome.yml — first-time contributor greeting (issues + PRs).
    • stale.yml — closes inactive issues / PRs after 90 d with a
      7-d warning.
    • All workflows opt-out via repo variable
      MCP_FIREWALL_DISABLE_<NAME>.
  • docs/FAQ.md — full 10-question FAQ; top three inlined into
    README.
  • docs/PERFORMANCE.md — measured latency + community-data table
    populated by mcp-firewall benchmark.
  • docs/RELEASING.md — release procedure relying on PyPI trusted
    publishing (no token in repo).
  • PyPI metadata polish — extra classifiers, Changelog /
    Documentation / Release notes / Security policy URLs,
    maintainer email.

Changed

  • README hybrid rewrite — radical voice on hero, problem statement,
    top-3 FAQ; light polish on technical sections. Removed feature-bullet
    log in favour of a problem-first opener.
  • Rules detector — three-pass scan now also folds Cyrillic and
    Greek homoglyphs into Latin counterparts before regex matching, so
    payloads using Іgnore (Cyrillic) or ιgnore (Greek) fire
    role_hijack.ignore_previous.
  • Health endpoint — per-connection wait_for(timeout=5s) plus
    StreamReader limit=8KiB close the slowloris path. Snapshot is
    cached for 1 s under an asyncio.Lock so a probe storm does not
    trigger a SCAN every request.
  • Stats / telemetrydet_rules JSON parse capped at 64 KiB per
    row to bound CPU on a corrupted audit log.
  • Batch JSON-RPC handling — when any member of a batch blocks,
    the proxy now emits a per-id reply array (one response per request
    id), not a 1-element substitute. Benign s2c siblings forwarded
    verbatim; benign c2s siblings get a synthesised
    -32099 batch_aborted_by_sibling error reply.

Security

  • ReDoS guard for community regexmcp-firewall rules lint --strict rejects patterns that contain a nested quantifier
    ((a+)+, (.*x)*) and any pattern that takes more than 100 ms
    on a 512-char benign probe (SIGALRM-bounded).
  • Batch JSON-RPC ID confusion fixed — see Changed above.
  • Health endpoint slowloris — hostile localhost peer can no
    longer hold an event-loop slot indefinitely.
  • Cross-script homoglyph evasion closed — Cyrillic / Greek
    look-alikes for Latin letters fold during normalisation.

Known limitations (v0.5 backlog)

  • HTTP/SSE transport.
  • Community rules repository (mcp-firewall/rules-community).
  • Viewer filters in mcp-firewall logs (search by rule id, by
    trace id).
  • Anthropic Haiku fallback tier for the LLM classifier.
  • Full Unicode confusables.txt shipped as data — current homoglyph
    table is hand-picked from published PoCs.

v0.3.0 - Community readiness (integrations, observability, hardening)

Choose a tag to compare

@churik5 churik5 released this 05 May 13:20

mcp-firewall v0.3.0 — Community readiness, observability, audit-fix harvest

Date: 2026-05-05
Status: alpha — opt-in detector and telemetry; off-by-default posture preserved.

Headline

mcp-firewall is now ready for the public OSS launch. Three things changed:

  1. Audit-fix harvest from v0.2. The five deferred findings from the Week-2 self-audit are landed: NFKC + invisible-char three-pass scan, per-member inspection of JSON-RPC batch frames, explicit skipped:non_text_content audit note, one-end truncation closing the seam evasion, and the Haiku fallback deferred to v0.4 with an explicit ADR slot.
  2. Observability without compromising the privacy posture: a local stats command, a loopback /health endpoint, and an opt-in telemetry pipe that ships nothing more than version + OS + event counts.
  3. Community readinessCONTRIBUTING.md (with the rule-pack promotion ladder), SECURITY.md (GitHub Security Advisories), three integration-tested MCP servers (github, brave-search, postgres).
# Try the new commands:
mcp-firewall stats --since 24h
mcp-firewall stats --json --compact
mcp-firewall rules lint --strict src/mcp_firewall/rules/builtin/
mcp-firewall run --server "..." --health-port 8765

What's new

Stats (local-only)

mcp-firewall stats is a read-only roll-up of the audit log. Rich-table by default, JSON via --json (pretty by default; --compact for one-line). Window selector --since 7d|24h|30m. Versioned schema (schema_version: 1) so future changes are non-breaking.

Health endpoint

--health-port N binds a tiny asyncio listener on 127.0.0.1:N and serves one route: GET /health → 200 application/json. For k8s liveness probes and docker HEALTHCHECK. Loopback-only by design — no auth, no TLS, no external exposure.

Telemetry — opt-in, anonymous, transparent

  • Off by default. Only the env var MCP_FIREWALL_TELEMETRY=true enables it.
  • Privacy contract. No rule names, no method names, no traffic content, no IPs/hostnames. Only installation_id (a self-mintable UUID), version, OS family, Python version, and four integer event counts.
  • Local log first. Every payload is appended to data/telemetry.log before the HTTP call. Network errors never erase the log entry.
  • Endpoint kill-switch. MCP_FIREWALL_TELEMETRY_URL=disabled skips the HTTP call but still writes the local log.
  • Files mode 0600. Co-tenants on a shared machine can't read payloads.
  • Full schema and "what we DON'T send" in docs/OBSERVABILITY.md.

Rules lint

mcp-firewall rules lint <path> validates community-contributed YAML packs:

  • Basic mode: syntax, regex compilation, valid apply_to, score in [0.0, 1.0]. Equivalent to load-time validation.
  • --strict: + description ≥ 10 chars + source is an HTTP(S) URL + severity_tier set + attack_examples list with at least one entry that actually matches the regex + false_positive_examples (when present) must NOT match.

The strict bar is the gate for promotion from community/ to built-in/ (see CONTRIBUTING.md).

Audit-fix harvest

Finding (Week 2) v0.3 fix
NFKC normalisation Three-pass scan: raw + within-word + between-word normalised
Batch frame per-member inspection Per-member rows in audit log; whole-batch block on any hit
Non-text content shapes note=skipped:non_text_content in audit
Truncation seam One-end (head-only) truncation; note=ok:truncated=<chars>
Haiku fallback Deferred to v0.4 (async-parallel inspection redesign)

Privacy hardening (v0.3 audit findings)

The Week-3 self-audit surfaced 4 high/medium issues already closed in this release:

  • platform.release() reduced to its first numeric component (was leaking custom kernel build strings).
  • data/telemetry.log and data/installation_id written with mode 0600.
  • _trace_id no longer mixes the raw frame into the SHA1 seed (os.urandom(8) is sufficient and faster).
  • LLM truncation is now visible in audit via note=ok:truncated=<chars>.

Full report: docs/AUDIT-REPORT-week3.md.

Tested MCP integrations

Server Threat the integration test asserts the proxy catches
github-mcp-server Role-hijack + exfiltration text inside an issue body
brave-search-mcp Search-snippet poisoning (page text steers the agent)
postgres-mcp Stored injection in a TEXT column the agent reads

Adding your favourite server: see docs/INTEGRATIONS.md and the per-server template in tests/integration/.

Test coverage

pytest tests/ jumps from 121 cases (v0.2) to 203 cases (v0.3), all green. New surfaces:

  • tests/test_stats.py (21 cases) — windowing, percentiles, JSON shape.
  • tests/test_telemetry.py (25 cases) — env handling, identity stability, payload privacy contract, transmission paths.
  • tests/test_health.py (7 cases) — loopback-only enforcement, hostile-peer survival.
  • tests/test_lint.py (14 cases) — basic + strict mode, drifted attack-examples, malicious YAML.
  • tests/integration/{github,brave_search,postgres}/*.py (9 cases) — smoke + benign + attack per server.

Compatibility

  • Python ≥ 3.11.
  • AGPL-3.0-or-later.
  • No new runtime dependencies. All four new modules (stats.py, telemetry.py, health.py, lint.py) reuse existing deps.
  • Detector is off by default. Telemetry is off by default and opt-in only. Health endpoint is off by default. Existing v0.2 users keep their behaviour.

Migrating from v0.2.0

  1. pip install -U mcp-firewall (or pip install -e . from the repo).
  2. Existing log.db opens unchanged — schema version stays at 2.
  3. To get the new audit-fix protections, no config change is needed; they are always-on improvements to the detection layer.
  4. To enable observability:
    mcp-firewall stats                          # local read of the audit log
    mcp-firewall run ... --health-port 8765     # k8s/docker probe
    export MCP_FIREWALL_TELEMETRY=true          # opt-in telemetry, off by default

Known limitations (v0.4 backlog)

  • ReDoS guard for community-contributed regex (no pattern-time budget yet).
  • Per-id error synthesis on JSON-RPC batch block (the wire currently desyncs from the audit log when only some members block).
  • Health endpoint per-connection timeout + read-byte cap.
  • Cross-script homoglyph coverage (Cyrillic look-alikes for Latin letters) — needs a confusables table (~10 MB).
  • Anthropic Haiku fallback tier (carried over from v0.2).

Tracking is in docs/AUDIT-REPORT-week3.md §"Deferred to v0.4".

Acknowledgements

The Week-3 adversarial review (10 findings, 4 closed here, 6 documented for v0.4) was performed by a sub-agent run against the codebase. Same threat model as ADR-0004 / ADR-0005.

Rule-pack signatures continue to be sourced from public corpora — see docs/THREATS.md for the per-rule provenance table.

v0.2.0 — Detection layer (rules + LLM classifier)

Choose a tag to compare

@churik5 churik5 released this 04 May 19:19

mcp-firewall v0.2.0 — Detection Layer

Date: 2026-05-04
Status: alpha — opt-in detector; Week 1 audit-only behaviour preserved when detector is off.

Headline

mcp-firewall now enforces, not just observes. Turn on the detector and every JSON-RPC frame goes through a rules + local-LLM cascade, with high-confidence prompt-injection payloads in tool results replaced by a sanitised isError: true reply before the agent ever sees them. The original bytes stay in the audit log for forensics. Off by default, so existing Week 1 users keep their latency profile until they opt in.

ollama pull qwen2.5:3b   # optional — rules-only mode works without it
mcp-firewall run --server "..." --detector
mcp-firewall logs --verdict BLOCK --tail 50

What's new

The detection cascade (ADR-0004)

  1. RulesEngine — 24+ regex signatures shipped as YAML packs. Catalogued in docs/THREATS.md with source URLs (garak, promptfoo, Trojan Source, embracethered, MITRE ATT&CK).
  2. OllamaClassifier — local LLM verdict via qwen2.5:3b. SHA-256 cache, circuit breaker (3 failures → 60 s open), hard 1 s per-request timeout.
  3. Policy — YAML-driven first-match rule engine. Default action allow; built-in rules block on score ≥ 0.85 and warn on bare classifier signal. Custom policies welcome — drop a YAML and pass --policies <path>.
  4. Inspector — orchestrator. Hard latency abort at 1.25× max_latency_ms falls back to WARN so a slow Ollama can never wedge the pump.

CLI

Command What it does
mcp-firewall run --detector Run the proxy with the detection layer on.
mcp-firewall run --policies <path> Override the built-in policy.
mcp-firewall detect "<text>" Run the cascade over a single string. Exit 0 = PASS, 1 otherwise.
mcp-firewall logs --verdict BLOCK Filter the audit log to blocked frames.

Storage

Schema bumped from v1 to v2:

  • events gains six det_* columns (verdict, score, rules, classifier, latency, action).
  • New classifier_cache table for the SHA-256 verdict cache.
  • Partial-failure-safe migration via BEGIN IMMEDIATE and idempotent ALTER TABLE.

A Week-1 log.db opens cleanly under v0.2.0 — the migration runs once on first open and writes a new schema_version=2 row alongside the existing 1.

Performance budget

ADR-0004 §7 numbers, validated by tests/test_perf.py and against a real Ollama:

Path Budget Measured (M-series Mac)
Rules detector ≤5 ms p95 0.04 ms p95
Inspector cache hit ≤10 ms p95 0.13 ms p95
Inspector with LLM (cache miss) ≤200 ms p95 ~146 ms p50, ≤163 ms p95
Hard inspector abort threshold 250 ms enforced in code

Cold Ollama call (~1.8 s on first model load) busts the p95 budget — handled by the hard-abort path: that one frame returns WARN instead of stalling the pump, then subsequent frames stay under budget. Warm-up tip in docs/RUNBOOK.md.

Test coverage

pytest tests/ jumps from 27 cases (Week 1) to 121 cases (Week 2), all green. Highlights:

  • test_proxy_block.py runs the real CLI as a subprocess, feeds it a prompt-injection tool result, and asserts the agent receives the sanitised replacement (not the injection).
  • test_storage_migration.py exercises a hand-rolled v1 DB through the live migration, including a "crashed mid-migration" recovery scenario.
  • test_detectors_rules.py parametrises 30+ canonical PoCs from public sources and asserts each is caught by the right rule id.
  • test_perf.py asserts the latency budgets above with real numbers.

Self-audit findings (5 fixed in this release, 5 deferred)

Full report in docs/AUDIT-REPORT-week2.md. Headline:

Fixed:

  1. Classifier prompt-injection via >>> / Answer: tokens — content is now sanitised before substitution.
  2. Unknown when: keys in policies — now rejected at load time (a typo would otherwise silently match every frame).
  3. Synthetic-block s2c row missed det_verdict — now propagated from the parent c2s decision.
  4. _trace_id was predictable — now uses os.urandom.
  5. Migration used BEGIN (deferred) instead of BEGIN IMMEDIATE — now properly serialised.

Deferred to v0.3 (documented in docs/THREATS.md §"Limitations"):

  • LLM cascade only inspects result.content[*].text blocks; non-text shapes bypass the classifier.
  • No NFKC normalisation in rules — homoglyph / per-word zero-width attacks slip through some patterns.
  • Batch JSON-RPC frames inherit a single inspection verdict across all members.
  • Anthropic Haiku as a third-tier fallback — Ollama-only or rules-only today.
  • Truncation seam in _truncate can split a marker across the discard boundary.

Compatibility

  • Python ≥ 3.11.
  • AGPL-3.0-or-later.
  • No new runtime dependencies. httpx was added in Week 1 in anticipation of this milestone.
  • Detector is off by default. Set detector.enabled: true in config or pass --detector to opt in.

Migrating from v0.1.0

  1. pip install -U mcp-firewall (or pip install -e . from the repo).
  2. Existing log.db migrates automatically on first open. To verify:
    sqlite3 ~/path/to/log.db 'SELECT MAX(version) FROM schema_version;'   # → 2
  3. To turn the detector on: add detector.enabled: true to your config, or pass --detector once on the command line.
  4. Optional: pull qwen2.5:3b via Ollama for the LLM-classifier path. The detector works in rules-only mode without it.

Acknowledgements

Rule signatures sourced from:

Without these public catalogues this release would not exist.

v0.1.0 - stdio proxy + audit log

Choose a tag to compare

@churik5 churik5 released this 04 May 15:54

First working version: a transparent stdio proxy that sits between an MCP client (like Claude Desktop) and an MCP server, logs every JSON-RPC frame to SQLite, and lets you tail the audit log live.

What works

  • stdio proxy with asyncio + subprocess, half-close handling, signal-aware shutdown
  • Audit log in SQLite (WAL mode), batched async writes, drop-on-overflow protection
  • CLI: mcp-firewall run (proxy) and mcp-firewall logs --tail/--follow (viewer)
  • JSON-RPC 2.0 parser with pydantic v2 models, batch-aware
  • Config precedence: CLI flags → env vars → YAML → defaults

Verified end-to-end

Tested against the real @modelcontextprotocol/server-filesystem@2025.11.25:
handshake → tools/listlist_directoryread_text_file all flow through cleanly, every frame captured in the audit log.

Quality gates

  • 27 tests (models, storage, e2e proxy round-trip)
  • ruff + ruff format + mypy strict — clean
  • bandit + pip-audit — 0 issues
  • All 10 commits validated against Conventional Commits
  • CI matrix: Python 3.11 + 3.12

What's NOT in this release

No detection layer yet — that's Week 2 (rules-based regex + local LLM classifier via Ollama).
This release is just the plumbing: the proxy and the audit trail. You can run it now and see what your AI agent is actually doing through MCP, but it won't block anything.

Install

git clone https://github.com/churik5/mcp-firewall.git
cd mcp-firewall
pip install -e .
mcp-firewall run --server "npx -y @modelcontextprotocol/server-filesystem@2025.11.25 /tmp"

See docs/RUNBOOK.md for full setup instructions and Claude Desktop integration.