MCP runtime trust layer for AI agents. A FastAPI gateway that checks prompts, tool calls, MCP server/tool surfaces, response exposure, behavioral permission drift, and audit evidence before agents keep using risky tools. The core differentiator is post-approval MCP drift detection: is this still the approved tool/risk boundary?
The product is called Interlock. Public-facing copy uses the tagline "MCP runtime trust layer for AI agents." Do not call it an "LLM firewall." The firewall layer is commodity. The MCP drift engine, behavioral effective- permission proof, quarantine decisions, and Security Receipts are the moat. Do not generate landing-page copy or marketing material that calls this an "LLM firewall."
Five layers, short-circuits on first hit:
check_learned_patterns(prompt)— fingerprint cache from prior LLM-judge results. Sub-ms hits.policy_scan(prompt, api_key)— per-key custom policies (blocked keywords/topics/length).rule_based_scan(core/detector.py) — regex/keyword. Layer 1 in marketing.pattern_match_scan(core/pattern_matcher.py) — pattern matching. Layer 2.llm_judge_scan(core/llm_judge.py) — LLM-as-judge via Groq. Layer 3, slowest. Has fail-modes and a circuit breaker — see below.
For agent + MCP use cases, requests do NOT go through run_scan. They go through:
POST /inspect/tool-call→core/tool_inspector.py+ RBAC (core/policy.py::rbac_scan)POST /mcp/call→core/mcp_gateway.py::proxy_mcp_tool_call→ trust check → tool whitelist → inspector → RBAC → forward → response PII scan
When working on agent security, edit those modules — not the prompt-scan layers.
proxy.py— main FastAPI app. All routes live here.- (No
api.pyormain.py; if you see them now, they are new.)
core/db.py— SQLite-backed API key store. All per-key config lives here: plan, rate limit, fail_mode, webhook_url, custom_policy, siem_configs. Never re-introduce hardcoded per-key dicts in any other file.core/admin.py—/admin/keysCRUD, protected byADMIN_TOKENenv var.core/llm_judge.py— Layer 3 with three fail-modes (fail_closed/fail_open/fail_open_safe) and a circuit breaker that trips after 5 consecutive failures and skips Groq for 60s.core/mcp_gateway.py— MCP tool definition validation + tool-call proxy. The differentiator.core/http_cache_headers.py/core/http_body.py— anti-cache header middleware for the boundary-review path, and the shared bounded request-body reader used by both that route and the Streamable HTTP transport.core/ci_boundary_review.py— read-only approved-vs-observed boundary review for the optional CI gate (POST /mcp/servers/{server_id}/boundary-review,mcp.reviewscope). Takes a coherent snapshot viadb.get_boundary_review_snapshot, observes withfetch_candidate_tool_surfaceunder byte/tool caps, classifies withmcp_drift, then re-checks the snapshot version. Never mutates approval/baseline/quarantine/policy state. CLI:scripts/interlock_ci_gate.py.core/tool_inspector.py— SQL/code/shell/file threat detection on tool args.core/policy.py—policy_scan(per-key) andrbac_scan(per-agent-role). Six predefined roles: support_agent, devops_agent, finance_agent, readonly_agent, data_analyst, admin_agent.core/learning.py— fingerprint-based pattern cache populated from LLM judge results.core/shadow_mode.py— log-only mode + risk score (0-100). Risk score combines threat level + confidence + threat-type bonus.core/siem.py— Datadog/Splunk/Elastic/Slack/PagerDuty/generic webhook dispatch.core/webhook.py— Slack-format alerts. Reads webhook_url from the DB key record. Async via FastAPI loop, never blocks scan.core/router.py— multi-provider forwarding (OpenAI/Anthropic/Gemini/Groq/Ollama).core/history.py— scan history log. Different fromdb.py— that's the key store.
interlock-web/index.html— public landing pageinterlock-web/src/— React dashboard/admin/audit views
- All scan functions return a
ScanResult(models/schemas.py). Required:is_threat,threat_level,reason. Setconfidence,layer_caught,scan_time_ms,risk_scorewhen you have them. - New per-key config goes in
core/db.py::api_keystable. Add the column, updatePLAN_DEFAULTSif it has a per-plan default, expose incore/admin.py::UpdateKeyRequest. - Webhooks and SIEM dispatch must NEVER raise into the scan path. Catch + log + continue.
- Run focused
pytestsuites after touching relevant modules, plusruff,black, andmypybefore release commits. - Prefer project-local tooling/virtual environments. Do not suggest global package installs in public docs.
- Don't reintroduce hardcoded per-key dicts (
VALID_API_KEYS,WEBHOOK_URLS,FAIL_MODE_BY_KEY,SIEM_CONFIGS_BY_KEY). They were removed in the SQLite migration. Usedb.lookup_key(raw)instead. - Don't store raw API keys. Only sha256 hashes go in the DB.
core/db.py::_hash_keyis the only allowed path. - Don't use
asyncio.get_event_loop(). Useasyncio.get_running_loop()inside async,asyncio.run()outside. The webhook bug from earlier was exactly this. - Don't add features without a test. Tests live at the project root:
test_*.py. - Don't generate marketing copy that says "LLM firewall." The product is Interlock. See Positioning above.
- Don't add
Co-Authored-Bytrailers to commit messages.
- Python 3.12+ / FastAPI / Uvicorn
- Optional LLM judge providers through
core/router.pyand provider env vars. - SQLite for local/dev and controlled pilots; Postgres/Redis are the production-style path.
- Docker + Helm chart in
helm/with production-oriented examples, not a broad enterprise certification claim.
GROQ_API_KEY— required for Layer 3GEMINI_API_KEY— declared but not consumed (no fallback wired)OPENAI_API_KEY/ANTHROPIC_API_KEY— used by/v1/chat/completionsproxy when forwarding upstreamADMIN_TOKEN— required for/admin/*endpoints. Generate withpython -c "import secrets; print(secrets.token_urlsafe(32))". Treat like a DB root password.SLACK_WEBHOOK_URL/DATADOG_API_KEY/PAGERDUTY_KEY— referenced by older SIEM seed code; new keys carry their ownsiem_configsJSONFIREWALL_DB_PATH— defaults todata/firewall.dbINTERLOCK_BOUNDARY_REVIEW_TIMEOUT_S/..._MAX_RESPONSE_BYTES/..._MAX_TOOLS/..._MAX_FINDINGS/..._IDEMPOTENCY_TTL_S— validated, clamped limits for the CI boundary-review gate
config.py loads these. If you add a new env var, also add it to config.py so it's importable everywhere.
# Windows
venv\Scripts\activate
pip install -r requirements.txt
uvicorn proxy:app --reload --port 8001
# Tests
python test_db.py
python test_webhook_fix.py
python test_judge_failmodes.py
# Swagger UI
# http://localhost:8001/docs- Keep runtime evidence boundaries honest: internal triage may be richer, but public/replay records must not overstate what was verified.
- Keep the core proof path reproducible: approved tool -> same identity/surface -> changed risk or behavior -> hold/quarantine -> hash-chain verified receipt.
- Keep public repository artifacts product-focused and evidence-safe. Local non-product notes, partner-specific drafts, and raw proof captures stay out of git.
- Keep CI green with ruff, black, mypy, pytest, dashboard build, Docker/Helm checks.
verify_keydoes a DB hit on every request. Fine at low scale; cache viafunctools.lru_cachewith TTL once you exceed ~100 RPS.- Rate limits and key usage should use Redis for multi-replica deployments; local memory paths are for local/dev and bounded pilots.
- Do not overclaim production readiness. Public proof packs are technical evidence; production proof requires a customer-approved non-production canary and written scope.
- Do NOT add "Co-Authored-By: Codex" trailer to commit messages.
- Commit messages should attribute work to the human only.