PRECINCT Gateway
The PRECINCT Gateway is a PRECINCT-native component, purpose-built for this architecture. It is not a fork or wrapper of an existing proxy; it is original software designed from the ground up to enforce the 13-layer middleware chain across all five governed planes.
Overview
The PRECINCT Gateway is the centralized enforcement point for all agent-to-tool communication in the PRECINCT architecture. It functions as a reverse proxy that sits between AI agents and upstream MCP servers, ensuring every request is evaluated against security policy before reaching any tool.
38,000
Lines of Go implementing the gateway core, middleware chain, and integration layers.
62,000
Lines of test code. The test surface exceeds the production surface by 1.6x, covering unit, integration, and end-to-end scenarios.
13 Layers
Defense-in-depth middleware chain. Every request traverses all 13 layers in strict order before reaching the upstream.
The gateway implements the complete 13-layer defense-in-depth middleware chain and governs all five planes of the PRECINCT architecture: LLM/Model Egress, Context/Memory, Tool, Control Loop, and Ingress/Event.
The gateway's 13-layer middleware chain has been validated against all 16 case studies in Research (Shapira et al., 2026, arXiv:2602.20021v1). See the defense mapping table for which gateway layers defend against each attack scenario.
How It Works
When an agent sends a tool call, the gateway intercepts the JSON-RPC request and routes it through the middleware chain. Each layer makes an independent security decision. If any layer denies the request, processing halts and a structured error code is returned to the agent. If all layers approve, the request is forwarded to the upstream MCP server with real credentials injected at the last possible moment.
API Surface
The gateway exposes a minimal API surface. The primary endpoint handles all MCP JSON-RPC traffic. Additional endpoints support health checking and response firewall handle dereference.
| Method | Path | Description |
|---|---|---|
POST |
/ |
Main JSON-RPC endpoint. All MCP-over-HTTP traffic flows through this endpoint. Accepts standard JSON-RPC 2.0 payloads. |
POST |
/data/dereference |
Response Firewall handle dereference. Resolves opaque data handles returned by the response firewall back to their original values, subject to identity-based access control. |
GET |
/health |
Health check endpoint. Returns the gateway's readiness status including upstream connectivity and middleware chain availability. |
| Control Plane Endpoints | ||
POST |
/v1/ingress/admit |
Ingress plane admission. Validates canonical connector envelopes: SPIFFE source principal matching, SHA-256 payload content-addressing, replay detection with 30-minute nonce TTL, and 10-minute freshness windows. |
POST |
/v1/context/admit |
Context plane admission. Enforces memory tier classification (ephemeral/session/long_term/regulated), provenance validation, DLP classification for long_term writes, step-up for regulated reads, and minimum-necessary invariants. |
POST |
/v1/model/call |
Model egress plane. Evaluates model provider authorization, data residency constraints, HIPAA-aware prompt safety, and provider budget tracking. |
POST |
/v1/tool/execute |
Tool plane execution. Evaluates capability registry, protocol adapter rules, CLI shell-injection prevention (command allowlist, max-args, denied-arg-tokens), and step-up requirements. |
POST |
/v1/loop/check |
Loop plane boundary check. Full 8-state governance state machine with immutable budget limits across 8 dimensions (steps, tool calls, model calls, wall time, egress bytes, model cost, provider failovers, risk score). Supports operator halt and provider unavailability events. |
| Admin Endpoints | ||
GET |
/admin/loop/runs |
List all loop runs with state, usage, and limits. Sorted by last update time. |
GET |
/admin/loop/runs/<id> |
Per-run detail including governance state, halt reason, immutable limits, and usage snapshot. |
POST |
/admin/loop/runs/<id>/halt |
Operator halt (human kill switch). Transitions a running loop to HALTED_OPERATOR state. Returns 409 if already terminal. All halt operations are audit-logged. |
Listening Modes
- Dev mode:
http://localhost:9090. Plain HTTP for local development and evaluation. No mTLS required. Suitable formake upworkflows. - SPIFFE mTLS mode:
https://localhost:9443. Production mode with SPIFFE mTLS authentication. All clients must present a valid SVID. Required for production deployments.
Request Processing
The following is a detailed walkthrough of what happens when an agent sends a tool call through the gateway. Each step corresponds to a layer in the 13-layer middleware chain.
1. SPIFFE Identity Verification (Layer 3)
The gateway extracts the caller's SPIFFE ID from the mTLS handshake. The SVID
(SPIFFE Verifiable Identity Document) is validated against the SPIRE trust bundle.
If the SVID is expired, revoked, or from an untrusted domain, the request is rejected
with spiffe_auth_required.
2. Audit Logging (Layer 4)
A structured JSONL audit record is written with a unique decision ID. Each record includes a hash of the previous record, forming a tamper-evident chain. The audit log captures: timestamp, SPIFFE ID, requested tool, parameters (redacted), and the eventual decision (allow/deny with reason).
3. Tool Registry Check (Layer 5)
The requested tool is looked up in the Capability Registry. The gateway computes the
SHA-256 hash of the tool's current state and compares it to the registered hash. If
the hashes do not match, a rug-pull attack is assumed and the request is denied with
tool_hash_mismatch. If the tool is not registered at all, the request
is denied with tool_not_in_registry.
4. OPA Policy Evaluation (Layer 6)
The gateway constructs an OPA input document containing the caller's SPIFFE ID, the
requested tool, the request parameters, session context, and environmental metadata.
This document is evaluated against the loaded Rego policy bundle. Policies can express
fine-grained rules such as "agent X may call tool Y only with parameter Z during
business hours." Denied requests receive authz_policy_denied.
5. DLP Credential Blocking (Layer 7)
The DLP scanner inspects the request payload for sensitive data patterns. Detected patterns include:
- AWS access keys and secret keys
- GitHub personal access tokens
- PEM-encoded private keys and certificates
- API keys matching common provider formats
- Personally identifiable information (PII)
If credentials or sensitive data are detected, the request is denied with
dlp_credentials_detected. Prompt injection patterns trigger
dlp_injection_detected.
6. Session Risk Tracking (Layer 8)
The session context engine accumulates behavioral signals across requests within the same session. It tracks tool invocation patterns, data access sequences, and cumulative risk indicators. If an agent's behavior pattern resembles known exfiltration or lateral movement patterns, the session risk score is elevated.
Layer 8 now includes EscalationScore tracking that detects gradual
privilege escalation across sessions. Each action's escalation contribution is
calculated as Impact x (4 - Reversibility). The cumulative score is evaluated
against three thresholds: Warning (≥ 15), Critical (≥ 25), and
Emergency (≥ 40). When the Emergency threshold is reached, the request is
denied with escalation_emergency. The current escalation score is
propagated via the X-Precinct-Escalation-Score response header for
downstream observability.
7. Step-Up Gating (Layer 9)
For high-risk operations (as defined by policy), the step-up gate dispatches a guard
model to evaluate whether the operation should proceed. The guard model receives the
full request context and returns an independent risk assessment. If the risk exceeds
the configured threshold, the request is denied with stepup_required.
Step-up gating now incorporates irreversibility classification as
an input signal. Actions are classified into four tiers: Low (read), Medium (create),
High (modify), and Critical (delete/destroy). Critical-tier actions automatically
trigger step-up evaluation regardless of the current risk score. The data
classification tier is propagated via the X-Precinct-Data-Classification
response header.
8. Deep Scan (Layer 10)
An asynchronous prompt injection detection layer dispatches the request content to a specialized guard model trained to identify injection attempts, jailbreak patterns, and adversarial inputs. This layer runs asynchronously to minimize latency impact on the fast path.
9. Rate Limit Check (Layer 11)
Per-identity token bucket rate limiting enforced via KeyDB. Each SPIFFE identity has
a configurable rate limit budget. When the budget is exhausted, subsequent requests
are denied with rate_limit_exceeded until tokens are replenished.
10. Circuit Breaker Check (Layer 12)
The circuit breaker monitors upstream MCP server health. If an upstream is
experiencing failures above the configured threshold, the circuit opens and subsequent
requests are immediately denied with circuit_breaker_open rather than
contributing to a cascade failure.
11. Token Substitution (Layer 13)
The final layer before egress. The gateway scans the outbound request for opaque token references and resolves them to real credentials via SPIKE. The real credentials are injected into the request immediately before it is forwarded to the upstream MCP server. No middleware layer upstream of this point ever sees real credentials.
Error Codes
When the gateway denies a request, it returns a structured error code that enables agents to understand the reason for denial and adapt their behavior. Each error code maps to a specific middleware layer and denial reason.
| Error Code | Layer | Description |
|---|---|---|
spiffe_auth_required |
3 (SPIFFE Auth) | The request lacks a valid SPIFFE identity. The caller must present a valid SVID via mTLS. |
authz_policy_denied |
6 (OPA Policy) | The OPA policy evaluation denied the request. The caller's identity does not have permission to perform the requested operation. |
dlp_credentials_detected |
7 (DLP Scanning) | The request payload contains credentials or sensitive data that must not be transmitted. |
stepup_required |
9 (Step-Up Gating) | The operation requires elevated authorization. The guard model determined the risk exceeds the threshold for automatic approval. |
dlp_injection_detected |
10 (Deep Scan) | The request content contains patterns consistent with prompt injection or adversarial manipulation. |
rate_limit_exceeded |
11 (Rate Limiting) | The caller's rate limit budget is exhausted. The request should be retried after the token bucket replenishes. |
circuit_breaker_open |
12 (Circuit Breaker) | The upstream MCP server is experiencing failures. Requests are being short-circuited to prevent cascade failure. |
tool_not_in_registry |
5 (Tool Registry) | The requested tool is not registered in the Capability Registry. Only registered tools may be invoked. |
tool_hash_mismatch |
5 (Tool Registry) | The tool's current hash does not match its registered hash. A rug-pull attack is suspected. The tool must be re-verified and re-registered. |
escalation_emergency |
8 (Session Context) | The cumulative escalation score has reached the Emergency threshold (≥ 40). The session is considered compromised by gradual escalation and further requests are denied (HTTP 403). |
data_source_hash_mismatch |
5 (Tool Registry) | An external data source's content hash does not match its registered hash and the mutable policy is set to block_on_change. The data source may have been tampered with (HTTP 403). |
unregistered_data_source |
5 (Tool Registry) | The requested data source is not registered in the Data Source Integrity Registry. Only registered data sources may be accessed (HTTP 403). |
principal_level_insufficient |
6 (OPA Policy) | The caller's principal authority level is below the minimum threshold required by the target operation's policy. Elevation or delegation is required (HTTP 403). |
irreversible_action_denied |
9 (Step-Up Gating) | An irreversible action (reversibility score ≥ 2) was blocked pending step-up authorization. The guard model or human operator must approve before the action can proceed (HTTP 403). |
discord_signature_invalid |
3 (SPIFFE Auth) / Channel Mediation | An inbound Discord webhook request failed signature verification. The request did not originate from a verified Discord endpoint (HTTP 401). |
Diagnostic Response Headers
The gateway propagates diagnostic metadata via response headers to support downstream observability and policy decisions. These headers are included on responses that traverse the relevant middleware layers.
| Header | Source Layer | Description |
|---|---|---|
X-Precinct-Escalation-Score |
8 (Session Context) | The cumulative escalation score for the current session after processing this request. Numeric value; compare against Warning (15), Critical (25), Emergency (40) thresholds. |
X-Precinct-Data-Classification |
9 (Step-Up Gating) | The irreversibility classification tier assigned to this action: low, medium, high, or critical. |
X-Precinct-Principal-Level |
3 (SPIFFE Auth) | Integer (0-5) representing the principal authority level resolved from SPIFFE path patterns. 5 = System, 4 = Operator, 3 = Trusted Agent, 2 = Standard Agent, 1 = Restricted Agent, 0 = Unknown. |
X-Precinct-Principal-Role |
3 (SPIFFE Auth) | String representing the resolved principal role: system, owner, delegated_admin, agent, external_user, or anonymous. |
X-Precinct-Reversibility |
9 (Step-Up Gating) | Integer (0-3) representing the action reversibility score from ClassifyReversibility. 0 = fully reversible, 3 = irreversible. Actions with score ≥ 2 trigger automatic step-up gating. |
X-Precinct-Backup-Recommended |
9 (Step-Up Gating) | Boolean indicating whether a pre-action snapshot is recommended before executing this operation. Set to true for actions with reversibility score ≥ 2 or impact score ≥ 3. |
Response Firewall
The gateway does not only inspect inbound requests. Responses from upstream MCP servers also pass through a response firewall before being returned to the agent.
When an upstream tool returns sensitive data (credentials, tokens, connection strings, or other privileged information), the response firewall replaces the raw values with opaque handles. The agent receives a handle that is meaningless outside the gateway context.
If the agent (or a downstream consumer with appropriate identity) needs the original
value, it can call the POST /data/dereference endpoint with the handle.
The gateway verifies the caller's SPIFFE identity against the access control policy
for that handle before returning the original value.
Handles prevent agents from inadvertently logging, caching, or transmitting sensitive data. Even if an agent's conversation history or memory is compromised, the attacker obtains only opaque references that cannot be resolved without the proper SPIFFE identity and gateway access.
Pluggable Extension Slots
The gateway supports three named extension slots for integrating third-party security services into the middleware chain. Extensions are external HTTP sidecar services that receive a structured request payload, evaluate it, and return an allow/block/flag decision. No gateway code changes are required to add, remove, or reconfigure extensions.
Extension Slot Positions
Each slot is positioned at a carefully chosen point in the 13-layer middleware chain where third-party inspection is both safe and useful. If no extensions are configured for a slot, the middleware is a zero-cost pass-through.
| Slot Name | Position | Use Cases |
|---|---|---|
post_authz |
After OPA Policy (step 6), before DLP Scanner (step 7) | Custom RBAC enrichment, tool checkers, pre-scan authorization gates |
post_inspection |
After DLP Scanner (step 7), before Session Context (step 8) | Content scanners, markdown validators, format checkers |
post_analysis |
After Deep Scan (step 10), before Rate Limiting (step 11) | Final approval gates, aggregated risk decisions, custom blocking logic |
Extension Protocol
Extensions communicate with the gateway via HTTP POST. The gateway sends a
structured ExtensionRequest payload to each extension's endpoint
and expects a structured ExtensionResponse in return.
Request Payload (ExtensionRequest)
{
"version": "1",
"request_id": "d-29fa...",
"trace_id": "abc123...",
"timestamp": "2026-02-22T14:30:00Z",
"slot": "post_inspection",
"request": {
"method": "tools/call",
"tool_name": "file_read",
"body": "<base64-encoded request body>",
"spiffe_id": "spiffe://trust-domain/agent/xyz",
"security_flags": [],
"session_id": "sess-001"
}
}
Which fields are included in the request object is controlled
per-extension via the request_fields configuration. Extensions
only receive the data they need.
Response Payload (ExtensionResponse)
{
"version": "1",
"decision": "allow",
"flags": [],
"reason": "",
"http_status": 0,
"error_code": ""
}
Extensions return one of three decisions:
allow: The request passes through to the next layer.block: The request is denied. The gateway returns a structured error to the agent using the extension'serror_codeandhttp_statusif provided.flag: The request continues, but the suppliedflagsare appended to the request'sSecurityFlagsCollectorfor downstream layers to consider.
Configuration
Extensions are defined in a YAML registry file (config/extensions.yaml).
The registry is hot-reloaded via file-system watch (fsnotify). Adding or modifying
extensions does not require a gateway restart.
version: "1"
extensions:
- name: content-scanner
slot: post_inspection
enabled: true
endpoint: http://localhost:8090/scan
timeout_ms: 2000
fail_mode: fail_open
priority: 100
description: "Scans request content for policy violations"
filters:
methods: ["tools/call"]
tools: []
request_fields:
include_body: true
include_spiffe_id: true
include_tool_name: true
include_security_flags: false
circuit_breaker:
failure_threshold: 5
reset_timeout_ms: 30000
Within a slot, extensions execute in priority order (lower value runs first). Filters control which requests are dispatched to each extension; empty filter lists match all requests.
Fail Modes
Each extension declares a fail mode that determines gateway behavior when the extension sidecar is unreachable or returns an error:
fail_open: On error, skip this extension and continue processing. The request proceeds as if the extension were not configured. Appropriate for advisory or non-critical extensions.fail_closed: On error, deny the request withextension_unavailable_fail_closed. No request passes through if the extension cannot evaluate it. Appropriate for mandatory compliance checks.
Per-Extension Circuit Breakers
Each extension can optionally configure a circuit breaker to prevent cascading
failures. When the number of consecutive failures reaches the
failure_threshold, the circuit opens and the extension is skipped
(or denied, depending on fail mode) until the reset_timeout_ms has
elapsed. After the timeout, the circuit enters half-open state and allows one
probe request through.
Reference Implementation
The PRECINCT repository includes a reference content-scanner
sidecar that demonstrates the extension protocol. It registers in the
post_inspection slot, receives base64-encoded request bodies,
applies configurable scanning rules, and returns allow/block/flag decisions.
It serves as a starting point for building custom extensions.
If no extensions are configured for a slot, the extension slot middleware is a zero-cost pass-through. There is no performance penalty for having the extension infrastructure in the chain when it is not in use.
Configuration
The gateway is configured through a combination of YAML files, OPA policy bundles, and environment variables. The key configuration surfaces are:
Capability Registry V2
A YAML-based registry that defines all tools and models the gateway is authorized to mediate. Each entry includes the tool's name, upstream endpoint, expected SHA-256 hash, allowed callers, rate limit budget, and any tool-specific policy overrides.
OPA Policy Bundles
Rego policy files organized into bundles. Policies are version-controlled and can be updated at runtime via OPA's bundle API. The gateway reloads policy bundles periodically without requiring a restart.
DLP Rule Configuration
Regular expression patterns and detection rules for the DLP scanning layer. Rules define which patterns constitute credentials, PII, or injection attempts, along with the action to take (block, flag, or log) when a pattern is matched.
Rate Limit Budgets
Per-identity and per-tool rate limit budgets defined as token bucket parameters: bucket capacity, refill rate, and refill interval. Budgets can be overridden per SPIFFE identity to accommodate different agent workloads.
SPIFFE Trust Domain Configuration
Trust domain settings for SPIFFE/SPIRE integration. Defines the trust domain name, SPIRE server endpoint, trust bundle refresh interval, and SVID rotation parameters.
Performance
The gateway is designed around two processing paths with distinct latency profiles. The fast path handles the majority of requests with minimal overhead. The deep path adds guard model evaluation for high-risk requests.
| Path | Layers | Latency | Description |
|---|---|---|---|
| Fast path | 0–8, 11–13 | <5ms | Identity verification, authorization, pattern-based DLP detection, rate limiting, circuit breaking, and token substitution. Handles the majority of requests. |
| Deep path | 9–10 | 200–550ms | Guard model dispatch for step-up gating and async prompt injection detection. Triggered conditionally by risk score or policy rules. |
Per-Middleware Latency Breakdown
An outer observability wrapper (Request Metrics, step 0) captures timing, size, and routing metadata (<0.1ms overhead). Each enforcement layer below emits timing spans observable in any OTel-compatible backend (Jaeger, Grafana Tempo, Datadog).
| Layer | P50 Latency | P99 Latency |
|---|---|---|
| 1. Request Size Limit | <0.05ms | <0.1ms |
| 2. Body Capture | <0.2ms | <0.5ms |
| 3. SPIFFE Auth | <0.5ms | <1ms |
| 4. Audit Log | <0.2ms | <0.5ms |
| 5. Tool Registry Verify | <0.3ms | <1ms |
| 6. OPA Policy | <1ms | <2ms |
| 7. DLP Scanning | <0.5ms | <1ms |
| 8. Session Context | <0.2ms | <0.5ms |
| 9. Step-Up Gating | 0ms (skip) | 200–500ms (dispatch) |
| 10. Deep Scan | 0ms (skip) | 200–550ms (dispatch) |
| 11. Rate Limiting | <0.2ms | <0.5ms |
| 12. Circuit Breaker | <0.05ms | <0.1ms |
| 13. Token Substitution | <0.5ms | <1ms |
Complete per-middleware benchmark results are available by running
make bench in the gateway source tree. Benchmarks exercise each
middleware layer independently and in combination, measuring allocation counts
and bytes alongside wall-clock time.