← Projects

pythagoras-sec

Most MCP setups rely on static, implicit trust. pythagoras-sec replaces static config with cryptographic identity (SPIFFE/SPIRE) at handshake, then enforces runtime policy on every tool execution.

Status
Active
Started
Jul 2026
Stack
Go · SPIFFE/SPIRE · mTLS · k3s · NetworkPolicy · GCP · Python · Mistral
Code

Problem. Standard MCP implementations trust a server once it is configured. If an agent or a server is compromised, nothing stands between it and tool execution.

Architecture. A policy engine between agents and MCP tools:

  • Identity: SPIFFE/SPIRE workload identity, verified over mTLS at the handshake (public lab: secu-mcp).
  • Runtime policy: every tool call is evaluated against versioned directives before it runs; actions above authorization thresholds pause for human approval.
  • Auditability: every decision is written to a tamper-evident, hash-chained journal that can be replayed and verified offline.

Validation. Synthetic banking scenario: a loan approval runs in 12 calls. With directive D-009 enabled, the action is intercepted, paused for human validation and completes in 14 calls. Both audit chains pass offline verification.


The problem

An MCP client connects to servers listed in local configuration files. That configuration is saved once and never validated at runtime. Any process responding on the expected address with a matching tool name gains agent trust and receives sensitive API credentials.

The lab demonstrates this vulnerability directly. The network-config service (legitimate) and rogue service (malicious) both expose the tool name get_file. With TRUST_MODE=naive, the agent executes calls against the rogue server, leaking credentials. With TRUST_MODE=zerotrust, the rogue connection is terminated before any handler executes.

Two halves of the threat model

DimensionQuestionNatureTreatment
IdentityWho are you?Binary, provableSolved via SPIFFE and mTLS
BehaviorWhat are you doing?Continuous, unpredictableContained through strict execution bounds

An authenticated server can still suffer prompt injection or drift. Text filters applied to inputs eventually fail against obfuscation, uncommon languages, or Base64 encoding. The architecture assumes filters will be bypassed and focuses on containing the blast radius.

Four locks

LockLocationGuarantee
Lock 0 Identityinternal/identityPeers prove SPIFFE ID during mTLS handshake. Without an attested SVID, connection drops. The rogue pod has a ServiceAccount but no SPIRE entry, making identity attestation impossible.
Lock 1 EgressNetworkPoliciesDefault-deny rules enforced at kernel level. Exfiltration attempts fail immediately.
Lock 2 Executioninternal/toolpolicyCapability allowlists evaluated before calls leave process context. Read and write actions are segregated; write actions require explicit human approval with target binding.
Lock 3 Data isolationinternal/envelopeTool responses are encapsulated inside <untrusted_data id="nonce"> tags using fresh 16-byte nonces, marked inert in system prompts.

Locks 1 to 3 remain effective even if prompt injection succeeds against the model.

Lab setup

GCP e2-standard-2 running single node k3s with kube-router. SPIRE server operates as root CA (trust domain mcp.gcp.lab, 1 hour SVID TTL), with SPIRE agent running as DaemonSet using k8s_psat attestation. Prometheus, Alertmanager, and Grafana sit behind an obs-bridge proxy that holds the Grafana API key. The monitoring agent receives metrics without accessing credentials.

Engine design

The control point evolved into a standalone policy engine. Every tool call is evaluated against versioned directives, logged to a hash-chained audit journal, and reproducible offline. Actions exceeding authorization thresholds pause for explicit human approval.

In a test run using synthetic banking data, a standard loan approval executed in 12 calls. Enabling directive D-009 (internal rate checked without official rate reference) caused D-009 to intercept the action, pause execution for human validation, and complete in 14 calls. Both audit chains passed full offline cryptographic verification.

Known limits

  1. NetworkPolicy start-up race condition. iptables rules apply shortly after pod IP allocation. Measured on cluster: at t+0 s a wget command reaches external IPs; at t+20 s rules take effect and block egress. The egress lock protects against compromised workloads at runtime, but not against exfiltration within the first few seconds of pod creation. Mitigating this requires policy enforcement prior to pod startup via specialized CNI or mandatory egress proxies.
  2. Deterministic triage without external LLM calls. Under default-deny, querying an external model requires opening egress paths, which defeats the principle of minimal network exposure.
  3. Scoped shortcuts. PromQL validation uses syntax parsing, data redaction relies on pattern matching rather than full classification, and SPIRE agent uses insecure_bootstrap. The lab models rogue MCP servers rather than a compromised SPIRE control plane.

Development model

The engine was developed by coordinating multiple coding agent sessions in parallel against immutable interface contracts. I define the contracts, write specifications, review pull requests, and merge code only when go vet, go test -race, and journal replay tests pass cleanly.

Next steps

Implementing segmented controls: replacing manual rule definition with chained binary classifiers. Decisions combine deterministically so that no individual model evaluation can relax an existing policy constraint.

Governance console displaying a session where the hash-chained audit journal breaks at index 14, marking subsequent entries untrusted.

Timeline

Jul 2026

Thesis: Replace location-based trust with attested workload identity. Phase 0 design document based on OWASP MCP Top 10, DORA, and EU AI Act requirements.

4 Sep 2026

Public release: Lab published as secu-mcp.

4 Oct 2026

First deployment: Integrated as the core control point inside Lexora at the LLM x Law hackathon.

6 Oct 2026

Segmented controls: Design doc defining security policy evaluation as a deterministic sequence of binary classifier verdicts.

Jul 2026 to 2027

30 Aug 2026

Running lab: k3s and SPIRE on GCP. Rogue servers using identical tool names as legitimate services are rejected during the mTLS handshake.

29 Sep to 3 Oct 2026

Core engine: Directives, hash-chained audit log, offline replay, and human approval tokens. Tagged demo-stable-0310.

5 Oct 2026

On-demand connectors: After the LangChain × Pigment meetup: prebuilt MCP tools give way to connector code generated for each request.

2027 · upcoming

Accepted talk: AWS User Group Paris.