Skip to content

Latest commit

 

History

History
2438 lines (1806 loc) · 107 KB

File metadata and controls

2438 lines (1806 loc) · 107 KB

SentinelGate Guide

The complete reference for SentinelGate — the universal MCP proxy/firewall for AI agents.

What is MCP? The Model Context Protocol (MCP) is the standard way AI agents (Claude, Gemini, Codex, etc.) interact with external tools — reading files, querying databases, calling APIs. SentinelGate sits between the agent and those tools as a security proxy: every tool call passes through SentinelGate, which enforces policies, scans for sensitive data, logs everything, and provides an emergency kill switch. The agent connects to SentinelGate instead of directly to the tool servers.

For installation, see the README.


Table of Contents

  1. Architecture
  2. Quick Start — Bootstrap, Profiles, Kill Switch, Container Setup
  3. Policy Engine — CEL Rules, Templates, Session-Aware Policies, Quotas, Transforms
  4. Agent Configuration
  5. Security Features — Content Scanning, Evidence, Tool Security, HITL, Drift Detection, Red Team, Cost Tracking, Agent Health
  6. Admin UI
  7. Configuration Reference — YAML Reference, Environment Variables
  8. CLI Reference
  9. Admin API Reference
  10. Multi-Agent Sessions
  11. Troubleshooting
  12. FAQ
  13. Threat Model and Limitations
  14. Policy Decision API (PDP) — Govern non-MCP agents

1. Architecture

How SentinelGate works

graph LR
    A[AI Agent] --> B[SentinelGate MCP Proxy]
    B --> CA[CanonicalAction]
    CA --> IC[Interceptor Chain]
    IC -->|Allow| U[Upstream MCP Servers]
    IC -->|Deny| BL[Blocked]
Loading

SentinelGate sits between AI agents and upstream MCP servers. Every MCP tool call passes through SentinelGate's policy engine before reaching the upstream server.

  • Agents connect to SentinelGate as if it were an MCP server
  • SentinelGate discovers tools from all configured upstream MCP servers
  • Every tool call is evaluated against your policies before forwarding
  • When upstream servers change, connected clients are automatically notified via notifications/tools/list_changed

CanonicalAction

Every MCP tool call is converted to a CanonicalAction — a unified representation containing the action type, name, arguments, identity, destination, and protocol. The policy engine evaluates all actions identically.

The interceptor chain

Every CanonicalAction passes through an ordered chain:

# Interceptor What it does
0 Kill Switch Emergency stop — blocks tool_call actions when active (protocol actions like initialize, ping, tools/list pass through)
1 Validation Well-formed JSON-RPC?
2 IP Rate Limit Too many requests from this IP?
3 Auth Valid identity and API key? Session management
4 Audit Wraps interceptors below it (#5–#14) — logs tool-call decisions, latency, scan results. Kill switch, validation, IP rate limit, and auth denials (#0–#3) are logged to stdout, not the audit trail.
5 Budget Block Monthly budget exceeded? Denies all tool calls when Cost Tracking budget is exceeded with action "block"
6 Quota Session/tool quotas exceeded? (calls, writes, deletes)
7 User Rate Limit Too many requests from this identity?
8 Quarantine Tool flagged by integrity drift detection?
9 Policy (CEL) Evaluate rules — highest priority match wins
10 Approval (HITL) Human approval required? Blocking wait with timeout
11 Transform Response transforms (redact, truncate, inject, mask, dry_run)
12 Content Scan (Input) PII/secret scanning in tool arguments
13 Response Scan (Output) Prompt injection detection in upstream responses
14 Route Forward to correct upstream via tool cache

What sentinel-gate start exposes

:8080/mcp       MCP Proxy (multi-upstream, tool discovery, policy enforcement)
:8080/admin     Admin UI (policy CRUD, audit log, config, dashboard)
:8080/admin/api REST API for programmatic management
:8080/health    Liveness probe (component health)
:8080/readyz    Readiness probe (bootstrap complete, tools discovered, kill switch off)
:8080/metrics   Prometheus metrics

Default: 127.0.0.1:8080 (localhost only). Docker images use 0.0.0.0:8080 (all interfaces via PORT env var).


2. Quick Start

For installation instructions, see the README.

This section covers: starting the server, adding upstreams, connecting agents, automated bootstrap (file + API), sandbox profiles, kill switch, and container deployment. Skip to any subsection using the sidebar navigation.

Step 1: Start the server

sentinel-gate start

The Admin UI is available at http://localhost:8080/admin. On first launch (no upstreams configured), a guided onboarding wizard walks you through the setup: Add Server → Connect Agent → Set Rules. You can also apply a policy template directly from the onboarding page.

Step 2: Add upstream MCP servers

In the Admin UI, go to Tools & Rules and click Add Upstream. Add one or more MCP servers:

# Example: filesystem server
npx @modelcontextprotocol/server-filesystem /path/to/dir

# Example: remote MCP server
https://my-mcp-server.example.com/mcp

SentinelGate discovers tools from all upstreams automatically.

Step 3: Create an identity and API key

In the Admin UI, go to Connections:

  1. Create an identity (name + roles)
  2. Create an API key for that identity
  3. Save the cleartext_key — it is shown only once

Step 4: Connect your agent

Configure your AI agent to use SentinelGate as its MCP server. The Connections page in the Admin UI has a Connect Your Agent section with ready-to-use configuration snippets for 7 agent types:

  • Claude Code — CLI command or ~/.claude/settings.json
  • Gemini CLI~/.gemini/settings.json
  • Codex CLI~/.codex/config.toml
  • Cursor / IDE — MCP server settings in IDE
  • Python — MCP client library
  • Node.js — MCP client library
  • cURL — Direct HTTP calls

See Agent Configuration for detailed instructions per agent.

Automatic tool list refresh

When you add, remove, or restart upstream MCP servers, SentinelGate sends a notifications/tools/list_changed notification to all connected MCP clients. Agents that support this notification automatically refresh their tool list — no reconnection needed.

Upstreams are hot-pluggable

You can add or remove upstream MCP servers at any time from the Admin UI. No restart needed — SentinelGate discovers tools immediately and the agent sees them on its next request.

Create policies

In the Admin UI, go to Tools & Rules and create rules. Rules have a priority — the highest priority matching rule wins.

Priority Rule Action
0 Match all tools Deny (baseline)
10 Match all tools Allow (override)
20 CEL: action_arg_contains(arguments, "secret") Deny

Everything is allowed (priority 10 beats 0), except actions involving "secret" (priority 20 beats 10). Test rules before deploying with the built-in Policy Test playground.

Automated Bootstrap (containers & CI/CD)

For automated deployments (Docker, Kubernetes, CI/CD), use file-based bootstrap instead of the Admin UI wizard. Drop a bootstrap.json file in one of these locations (checked in order):

  1. Path in SENTINEL_GATE_BOOTSTRAP_FILE environment variable
  2. /etc/sentinelgate/bootstrap.json
  3. Same directory as state.json

Example bootstrap.json:

{
  "profile": "strict",
  "upstreams": [
    {
      "name": "filesystem",
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
    },
    {
      "name": "remote-api",
      "type": "http",
      "url": "https://my-mcp-server.example.com/mcp"
    }
  ],
  "identities": [
    {"name": "agent-1", "roles": ["user"]},
    {"name": "agent-2", "roles": ["admin"]}
  ]
}

On first boot, SentinelGate reads the file, creates upstreams, discovers tools, creates identities, generates API keys, applies the profile policies, and configures content scanning. API keys are written to bootstrap-keys.json (same directory as state.json) and logged to stdout for container log capture.

The bootstrap-keys.json file format:

[
  {"identity_name": "agent-1", "identity_id": "uuid", "cleartext_key": "sg_..."},
  {"identity_name": "agent-2", "identity_id": "uuid", "cleartext_key": "sg_..."}
]

The bootstrap file is consumed once (renamed to bootstrap.json.consumed). On subsequent starts, the system loads from state.json.

Bootstrap fields:

Field Required Description
profile No Security profile: strict, standard, or permissive (see below)
upstreams Yes MCP servers to connect
identities Yes Agent identities with roles
default_policy No System-wide default policy (allow or deny); overrides profile default
policies No Additional policies with custom rules
content_scanning No Override content scanning settings

Upstream fields:

Field For type Description
name both Unique upstream name (required)
type both stdio or http (required)
command stdio Executable path (required for stdio)
args stdio Arguments array
url http Remote MCP server URL (required for http)
env both Environment variables (key-value map)

Content scanning override:

"content_scanning": {
  "enabled": true,
  "mode": "enforce",
  "input_scan_enabled": true
}

If profile is omitted, the system starts with no rules — configure everything via the dashboard or API.

API bootstrap is also available via POST /admin/api/v1/bootstrap with the same request format (requires CSRF token). Returns 409 if already bootstrapped.

Sandbox Profiles

Three built-in profiles provide pre-configured security stances:

Strict Standard Permissive
Default policy deny deny allow
Read (read_, list_, search_, get_) allow allow allow
Write (write_, create_, edit_, update_) deny allow allow
Delete (delete_, remove_, drop_*) deny deny allow
Sensitive paths (.env, .ssh, /etc/shadow, credentials) deny deny deny
Exfiltration domains (pastebin, ghostbin, transfer.sh) deny deny deny
Content scanning enforce monitor monitor
Input scan (PII/secrets in arguments) on on off

Profiles are starting points — after bootstrap, customize everything from the dashboard or API.

Kill Switch

The kill switch is an emergency stop that instantly blocks all MCP tool calls. It is the outermost interceptor in the chain — when active, nothing gets through.

From the dashboard: Click Activate Kill Switch on the main dashboard page.

From the API:

# First, get a CSRF token (needed for all write operations)
COOKIE_JAR=$(mktemp)
curl -s -c "$COOKIE_JAR" http://localhost:8080/admin/ > /dev/null
CSRF_TOKEN=$(grep sentinel_csrf_token "$COOKIE_JAR" | awk '{print $NF}')

# Activate
curl -X POST http://localhost:8080/admin/api/v1/system/kill \
  -H "Content-Type: application/json" \
  -b "$COOKIE_JAR" -H "X-CSRF-Token: $CSRF_TOKEN" \
  -d '{"reason":"suspicious activity detected"}'

# Check status
curl http://localhost:8080/admin/api/v1/system/kill -b "$COOKIE_JAR"

# Resume
curl -X POST http://localhost:8080/admin/api/v1/system/resume \
  -b "$COOKIE_JAR" -H "X-CSRF-Token: $CSRF_TOKEN"

Behavior when active:

  • All tool calls return a generic "Access denied by policy" error (intentionally vague for security)
  • /readyz returns HTTP 503 with "kill_switch": "not ready: kill switch active"
  • The dashboard shows the kill switch banner with reason and denied call count
  • State is persisted — survives restarts

Note: the denied call count shown in the banner is in-memory only. After clicking Resume Operations, the count is hidden from the UI but remains accessible via GET /admin/api/v1/system/kill. It is reset to zero on the next activation and on every process restart (it is not persisted in state.json).

Agents can detect the kill switch by polling /readyz and checking for HTTP 503.

Container & Cloud Deployment

SentinelGate provides deployment examples for 13 platforms and an interactive demo (examples/playground/) in the examples/ directory:

Platform Directory Description
Docker Compose examples/docker/ Standalone container
Docker + Agent examples/docker-demo/ SentinelGate + MCP server + agent simulator
Docker + Claude examples/claude-code/ Claude Code in isolated container with SentinelGate as gateway
Kubernetes examples/kubernetes/ Namespace, deployment, service, configmap
AWS ECS Fargate examples/ecs-fargate/ Task definition + service
Fly.io examples/fly-io/ fly.toml + volume
Modal examples/modal/ Serverless Python app
E2B examples/e2b/ Firecracker microVM sandbox
Firecracker examples/firecracker/ Manual microVM setup
Podman examples/podman/ Rootless container + Quadlet
LXC/LXD examples/lxc-lxd/ System container
Daytona examples/daytona/ Dev container
systemd examples/systemd/ Linux service

Each example includes a README with step-by-step instructions.

Running AI agents in Docker

The most secure pattern is running the AI agent inside a Docker container with SentinelGate as the only gateway to the filesystem:

┌─── Docker ──────────────────────────────────────┐
│                                                  │
│  AI Agent (Claude, etc.)                         │
│    │ brain                     │ hands            │
│    │                           │                  │
└────┼───────────────────────────┼──────────────────┘
     │                           │
     ▼                           ▼
  Model API                 SentinelGate
  (internet)                (policy + audit)
                                 │
                                 ▼
                           MCP Servers
                           (your files)

The agent talks to its model API over the internet (how it thinks). For file operations, it must go through SentinelGate (how it acts). The container has no direct filesystem access — SentinelGate is the only door.

See examples/claude-code/ for a complete working example with Claude Code.

Container environment variables

Variable Default Description
SENTINEL_GATE_ADMIN_OPEN true (Docker image) Allow admin API access from any IP (required for Docker — port forwarding doesn't preserve localhost). Binary default is false.
SENTINEL_GATE_ALLOWED_HOSTS (none) Comma-separated hostnames allowed for /mcp DNS rebinding protection (e.g., sentinel-gate for Docker Compose)
SENTINEL_GATE_BOOTSTRAP_FILE (none) Path to bootstrap.json file
SENTINEL_GATE_STATE_PATH ./state.json Path to state persistence file
PORT (none) Set listen address to :<PORT> when no http_addr is configured (cloud platforms)

Architecture patterns

Embedded (simplest): SentinelGate spawns the MCP server as a subprocess. Agent connects via HTTP. Used in docker-demo and claude-code.

Sidecar (Kubernetes): SentinelGate runs in the same pod as the agent. Agent connects to localhost:8080/mcp. Network policies block direct access to upstreams.

Gateway (multi-agent): Single SentinelGate instance serves multiple agents. Each agent has its own identity, API key, and policies.

Production Deployment (TLS)

SentinelGate listens on HTTP by design. In production, terminate TLS at a reverse proxy in front of SentinelGate. This is the standard pattern for Go services — it keeps the application simple and lets you manage certificates in one place.

Caddy (recommended — automatic HTTPS)

sentinelgate.example.com {
    reverse_proxy localhost:8080
}

Caddy automatically provisions and renews TLS certificates via Let's Encrypt. No manual certificate management required.

nginx

server {
    listen 443 ssl;
    server_name sentinelgate.example.com;
    ssl_certificate     /etc/ssl/certs/sentinelgate.crt;
    ssl_certificate_key /etc/ssl/private/sentinelgate.key;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 86400s;
    }
}

Docker with Caddy sidecar

services:
  sentinelgate:
    image: ghcr.io/sentinel-gate/sentinel-gate:latest
    expose:
      - "8080"
  caddy:
    image: caddy:2-alpine
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
    depends_on:
      - sentinelgate

With the Caddyfile:

sentinelgate.example.com {
    reverse_proxy sentinelgate:8080
}

Why no built-in TLS? SentinelGate is a security proxy for AI agents, not a web server. Delegating TLS to a reverse proxy follows the principle of separation of concerns: the reverse proxy handles transport security, SentinelGate handles tool-call security. This also lets you share TLS termination across multiple services.


3. Policy Engine

How policies work

Policies contain rules. Each rule has:

  • A name (human-readable identifier)
  • A priority (integer — higher priority wins)
  • A condition (tool pattern or CEL expression)
  • An action (allow, deny, or approval_required)

All matching rules are sorted by priority. The highest-priority match wins. If no rule matches, the default action is allow.

Important

When using sandbox profiles (strict or standard), the profile creates a baseline deny rule at priority 0 that blocks everything, then allow rules at higher priority for permitted operations. The effective behavior is "deny by default" even though the engine's built-in fallback is "allow". Without any profiles or rules, the system allows all tool calls.

Note

When creating rules via the API, tool_match defaults to "*" (match all tools) if omitted.

Simple rules (tool patterns)

Pattern Matches
read_file Exactly read_file
read_* Any tool starting with read_
*_file Any tool ending with _file
* All tools

CEL rules

For advanced conditions, use CEL (Common Expression Language) — the same engine used by Kubernetes, Firebase, and Envoy.

Variables

Action variables (always available):

Variable Type Example values
action_type string "tool_call"
action_name string "read_file", "bash", "list_directory"
arguments map {"path": "/etc/passwd"}

Identity variables:

Variable Type Example values
identity_name string "claude-prod", "test-agent"
identity_id string "id-1"
identity_roles list ["admin", "reader"]
user_roles list Alias for identity_roles (backward-compatible)
session_id string Current session identifier
request_time timestamp When the request was received

Context variables:

Variable Type Example values
protocol string "mcp"
framework string "crewai", "langchain", "autogen"
gateway string "mcp-gateway"
framework_attrs map(string,string) Additional framework-specific attributes (e.g., framework_attrs["crewai.role"])

Destination variables (when the action has a target):

Variable Type Description
dest_url string Full destination URL
dest_domain string Destination domain only
dest_ip string Resolved destination IP
dest_port int Destination port number
dest_scheme string "http", "https"
dest_path string URL path or file path
dest_command string Command being executed

Backward-compatible aliases (MCP):

Alias Equivalent to
tool_name action_name when action_type == "tool_call"
tool_args arguments when action_type == "tool_call"

Built-in functions

Argument & pattern functions:

Function Description
action_arg_contains(arguments, "pattern") Search all argument values for a substring
action_arg(arguments, "key") Get a specific argument value by key
glob(pattern, name) Glob pattern match (e.g., glob("read_*", action_name))
dest_domain_matches(dest_domain, "*.evil.com") Glob match on destination domain
dest_ip_in_cidr(dest_ip, "10.0.0.0/8") CIDR range check on destination IP

Session history functions (require session_action_history, session_action_set, or session_arg_key_set):

Function Description
session_count(session_action_history, "read") Count actions by call type
session_count_for(session_action_history, "tool_name") Count actions by tool name
session_count_window(session_action_history, "tool", 60) Count actions in last N seconds
session_has_action(session_action_set, "tool") Tool used in session?
session_has_arg(session_arg_key_set, "key") Arg key used in session?
session_has_arg_in(session_action_history, "key", "tool") Arg key used with specific tool?
session_sequence(session_action_history, "a", "b") Check A occurred before B
session_time_since_action(session_action_history, "tool") Seconds since last call (-1 if never)

Standard CEL operators: ==, !=, &&, ||, !, .contains(), .startsWith(), .endsWith(), .matches() (regex), in (list membership), has() (key existence).

CEL hardening

  • Expression length: 1,024 characters maximum
  • Cost limit: 100,000 (prevents expensive expressions)
  • Nesting depth: 50 levels maximum
  • Evaluation timeout: 5 seconds per expression

MCP argument field names

MCP tools typically use these argument field names:

Field Examples
path File path for read/write operations
source, destination Source and destination for copy/move operations
command Command to execute
url URL for web requests
query Search query

Tip

Use action_arg_contains(arguments, "pattern") to search across all fields regardless of tool. For field-specific checks, use has():

(has(arguments.path) && arguments.path.contains("secret"))
|| (has(arguments.command) && arguments.command.contains("secret"))

Example policies

# Block access to files containing "secret"
action_arg_contains(arguments, "secret")

# Only admins can execute shell commands (set as deny rule)
action_name == "bash" && !("admin" in identity_roles)

# Block data exfiltration
dest_domain_matches(dest_domain, "*.pastebin.com") || dest_domain_matches(dest_domain, "*.ngrok.io")

# Restrict writes to a specific directory
action_name == "write_file"
  && !action_arg_contains(arguments, "/safe/workspace/")

# Block untrusted domains
dest_domain_matches(dest_domain, "*.untrusted.com")

Policy testing

Via Admin UI: Tools & Rules → Policy Test sandbox.

Via API:

curl -X POST http://localhost:8080/admin/api/v1/policy/evaluate \
  -H "Content-Type: application/json" \
  -H "X-CSRF-Token: test-token" \
  -H "Cookie: sentinel_csrf_token=test-token" \
  -d '{
    "action_type": "tool_call",
    "action_name": "read_file",
    "arguments": {"path": "/etc/passwd"},
    "identity_name": "test-agent",
    "identity_roles": ["agent"],
    "protocol": "mcp"
  }'

Note

The Policy Evaluate API generates audit records for each evaluation.

Policy templates

Seven pre-built security profiles you can apply with one click from the Admin UI (Tools & Rules → Use Template) or via API:

Template What it does
Safe Coding Allows read operations and writes to non-sensitive paths. Ideal for AI coding assistants.
File Server — Read Only Permits only file-system read operations (read_file, read_text_file, read_multiple_files, read_media_file, list_directory, list_directory_with_sizes, list_allowed_directories, search_files, list_files, get_file_info). Blocks all other tools including those from other servers.
Research Mode Allows reading, web searches, and writing to temporary directories. Blocks all other modifications.
Full Lockdown Blocks all tool calls unconditionally. Use when you need to completely disable agent activity.
Audit Only Allows all tool calls but logs everything for monitoring. No blocking, full visibility.
Data Protection Blocks writes to sensitive paths (.env, credentials, .ssh, /etc). Allows reads and other writes.
Anti-Exfiltration Detects and blocks data exfiltration patterns: reading sensitive files followed by sending data externally.

Templates create independent policies — modify or delete them without affecting the template.

Via API:

# List available templates
curl http://localhost:8080/admin/api/v1/templates

# Apply a template
curl -X POST http://localhost:8080/admin/api/v1/templates/read-only/apply

Session-aware policies

CEL functions that use session history for context-dependent decisions. These evaluate the sequence and frequency of actions within the current session.

Session CEL functions

Variable / Function Returns Use case
session_call_count int Total tool calls in session
session_write_count int Write operations in session
session_delete_count int Delete operations in session
session_duration_seconds int Session duration in seconds
session_cumulative_cost double Cumulative cost of all actions in session
session_sequence(session_action_history, "tool_a", "tool_b") bool True if tool_a was called before tool_b in this session
session_count_window(session_action_history, "tool_name", seconds) int Calls to tool_name in the last N seconds
session_count_for(session_action_history, "tool_name") int Total calls to a specific tool in session
session_time_since_action(session_action_history, "tool_name") int Seconds since last call to a specific tool
session_has_action(session_action_set, "tool_name") bool True if tool was called in this session
session_has_arg(session_arg_key_set, "key") bool True if any call in session had this argument key
session_count(session_action_history, "read") int Count actions by call type ("read", "write", "delete", "other") in the full session history
session_has_arg_in(session_action_history, "key", "tool") bool True if a specific argument key was used with a specific tool in this session

Example: anti-exfiltration

# Deny send_email if read_file was called earlier in the session
session_sequence(session_action_history, "read_file", action_name)

Apply this as a deny rule with tool_match: "send_*" to block any send operation after a file read.

Agent health variables

These variables reflect the calling agent's behavioral health metrics, enabling adaptive policies that respond to anomalous behavior.

Variable Type Description
user_deny_rate double Agent's deny rate (0.0–1.0) over recent history
user_drift_score double Behavioral drift score (0.0 = stable, 1.0 = highly anomalous)
user_violation_count int Total policy violations by this agent
user_total_calls int Total lifetime tool calls by this agent
user_error_rate double Agent's error rate (0.0–1.0)

Example: adaptive security

# Block writes for agents with high deny rate
user_deny_rate > 0.15 && tool_name.contains("write")
# Require approval for drifting agents
user_drift_score > 0.3 && tool_name.contains("delete")
# Rate-limit agents with many violations
user_violation_count > 50 && session_call_count > 10

Testing with session context

In the Policy Test playground, expand the Session Context section to add simulated previous actions. Each action has a tool name, call type (read/write/delete/other), and a "seconds ago" value.

Budget and quota

Per-identity usage limits enforced at the interceptor level. Configure via Connections → Identity → Quota button, or via API.

Quota fields

Field Description
enabled Must be true for the quota to be enforced. Defaults to false if omitted.
max_calls_per_session Maximum total tool calls per session
max_writes_per_session Maximum write operations per session
max_deletes_per_session Maximum delete operations per session
max_calls_per_minute Rate limit (calls per minute)
tool_limits Per-tool call limits (map of tool name → max calls, e.g. {"write_file": 10, "delete_file": 5})
action What happens when a limit is reached: deny (block) or warn (log only)

API

# Set quota for an identity
curl -X PUT http://localhost:8080/admin/api/v1/quotas/{identity_id} \
  -H "Content-Type: application/json" \
  -d '{"enabled": true, "max_calls_per_session": 100, "max_writes_per_session": 20, "tool_limits": {"delete_file": 5, "execute_command": 10}, "action": "deny"}'

# Get quota
curl http://localhost:8080/admin/api/v1/quotas/{identity_id}

# Remove quota
curl -X DELETE http://localhost:8080/admin/api/v1/quotas/{identity_id}

Live quota usage is visible in the Dashboard Active Sessions widget with color-coded progress bars.

Response transformation

Transform tool responses before they reach the agent. Configure via Tools & Rules → Transforms tab, or via API.

Transform types

Type What it does Key fields
redact Replace regex matches with a placeholder patterns (regex list), replacement (default: [REDACTED])
truncate Limit response size max_bytes, max_lines, suffix
inject Add text before/after the response prepend, append
dry_run Replace the real response with a mock response (JSON template)
mask Partially reveal matched values mask_patterns, visible_prefix, visible_suffix, mask_char

Each transform rule has a name, type, tool_match (glob pattern), priority, and enabled toggle.

Test sandbox

The Transforms tab includes a Test Transform sandbox. Paste sample text, set a tool name, and run against saved rules or custom JSON rules to see the transformed output.

API

# Create a redact transform
curl -X POST http://localhost:8080/admin/api/v1/transforms \
  -H "Content-Type: application/json" \
  -d '{
    "name": "redact-api-keys",
    "type": "redact",
    "tool_match": "*",
    "priority": 1,
    "enabled": true,
    "config": {
      "patterns": ["sk-[a-zA-Z0-9]{20,}"],
      "replacement": "[REDACTED]"
    }
  }'

# Test a transform
curl -X POST http://localhost:8080/admin/api/v1/transforms/test \
  -H "Content-Type: application/json" \
  -d '{"text": "My key is sk-abc123xyz456def789ghi", "rules": [{"name": "test", "type": "redact", "tool_match": "*", "enabled": true, "config": {"patterns": ["sk-[a-zA-Z0-9]{20,}"]}}]}'

Session recording

Record every tool call with full context for replay and analysis. Configure via Sessions → Recording Configuration.

Configuration

Setting Description
Enable Recording Master toggle for session recording
Record Payloads When off (privacy mode), only metadata is recorded — no request args or response body
Max File Size Maximum size in bytes for a single recording JSONL file
Retention Days Auto-delete recordings older than N days
Redact Patterns Regex patterns to redact from recorded payloads
Storage Directory Directory where recording files are stored (default: recordings)

Timeline replay

Click a session in the list to see a vertical timeline of every event:

  • Sequence number, timestamp, tool name, decision badge, latency
  • Click to expand: rule ID, reason, request arguments, response, transforms applied, quota state
  • Deny events highlighted with red border

Export

  • JSON — Full structured export of all events
  • CSV — Tabular export for spreadsheet analysis

API

# Enable recording
curl -X PUT http://localhost:8080/admin/api/v1/recordings/config \
  -H "Content-Type: application/json" \
  -d '{"enabled": true, "record_payloads": true, "retention_days": 30, "storage_dir": "recordings"}'

# List recordings
curl http://localhost:8080/admin/api/v1/recordings

# Get recording events
curl http://localhost:8080/admin/api/v1/recordings/{id}/events

# Export
curl http://localhost:8080/admin/api/v1/recordings/{id}/export

4. Agent Configuration

All agents connect to SentinelGate the same way: by configuring SentinelGate as an MCP server in their settings. Each agent has its own configuration format.

Claude Code

Option A: CLI command

claude mcp add --transport http sentinelgate http://localhost:8080/mcp \
  --header "Authorization: Bearer <your-api-key>"

Tip

Add -s user to install globally across all projects (e.g., claude mcp add -s user sentinelgate ...).

Option B: Settings file (~/.claude/settings.json)

{
  "mcpServers": {
    "sentinelgate": {
      "type": "http",
      "url": "http://localhost:8080/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-key>"
      }
    }
  }
}

Gemini CLI

Option A: CLI command

gemini mcp add --transport http -s user \
  --header "Authorization: Bearer <your-api-key>" \
  sentinelgate http://localhost:8080/mcp

Option B: Settings file (~/.gemini/settings.json)

{
  "mcpServers": {
    "sentinelgate": {
      "url": "http://localhost:8080/mcp",
      "type": "http",
      "headers": {
        "Authorization": "Bearer <your-api-key>"
      }
    }
  }
}

Important

At least one upstream MCP server must be configured in SentinelGate (Admin UI → Tools & Rules → Add Upstream) for Gemini to have access to tools.

Codex CLI

Option A: CLI command

export SG_KEY="<your-api-key>"
codex mcp add sentinelgate --url http://localhost:8080/mcp \
  --bearer-token-env-var SG_KEY

Option B: Settings file (~/.codex/config.toml)

[mcp_servers.sentinelgate]
url = "http://localhost:8080/mcp"
bearer_token_env_var = "SG_KEY"

Then launch with: SG_KEY="<your-api-key>" codex

Note

Codex does not persist the API key. Unlike Claude Code and Gemini CLI (which save the key in their settings files), Codex only stores the name of an environment variable. The variable must be set in your terminal each time. To make it permanent, add it to your shell profile:

echo 'export SG_KEY="<your-api-key>"' >> ~/.zshrc   # macOS/Linux (zsh)
echo 'export SG_KEY="<your-api-key>"' >> ~/.bashrc   # Linux (bash)

Alternative: You can also use direct headers instead of environment variables: [mcp_servers.sentinelgate.headers] with Authorization = "Bearer <your-key>". This avoids the env var requirement but embeds the key in the config file.

Cursor / Windsurf / IDE extensions

Add SentinelGate as an MCP server in your IDE's MCP settings (e.g. .cursor/mcp.json):

{
  "mcpServers": {
    "sentinelgate": {
      "type": "http",
      "url": "http://localhost:8080/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-key>"
      }
    }
  }
}

The exact configuration location depends on the IDE. Cursor uses .cursor/mcp.json, Windsurf uses its own MCP settings panel.

Python (MCP client library)

For Python agents that use an MCP client library, point the client at SentinelGate:

import httpx
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client

http_client = httpx.AsyncClient(
    headers={"Authorization": "Bearer <your-api-key>"}
)
async with streamable_http_client(
    "http://localhost:8080/mcp", http_client=http_client
) as (read, write, _):
    async with ClientSession(read, write) as session:
        await session.initialize()
        tools = await session.list_tools()
        result = await session.call_tool("read_file", {"path": "/tmp/test.txt"})

Node.js (MCP client library)

For Node.js agents that use an MCP client library:

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const transport = new StreamableHTTPClientTransport(
  new URL("http://localhost:8080/mcp"),
  { requestInit: { headers: { "Authorization": "Bearer <your-api-key>" } } }
);
const client = new Client({ name: "my-client", version: "1.0.0" });
await client.connect(transport);

const { tools } = await client.listTools();
const result = await client.callTool({ name: "read_file", arguments: { path: "/tmp/test.txt" } });

cURL (direct HTTP)

For testing or scripting, call the MCP endpoint directly:

# List available tools
curl -X POST http://localhost:8080/mcp \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'

# Call a tool
curl -X POST http://localhost:8080/mcp \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "read_file", "arguments": {"path": "/tmp/test.txt"}}}'

Note: These examples skip the MCP initialize handshake for brevity. A compliant MCP client should first send initialize, receive the session ID, then send initialized before making tool calls.

Complete reference: For Docker Compose networking, Kubernetes service DNS, cloud platforms, IDE setup, framework integration, and the Policy Decision API — see Connecting Agents.


5. Security Features

Content scanning

Two scanning engines protect against different threats:

Response scanning (IPI defense) — Scans tool responses for prompt injection patterns before forwarding to agents. Detects system prompt overrides, role hijacking, instruction injection, delimiter escapes, model delimiter escapes, hidden instructions, context switches, tool poisoning directives, system tag injection, DAN/jailbreak attempts, and instruction delimiter escapes.

Setting Behavior
enabled: true, mode: "enforce" Block responses containing detected prompt injection patterns
enabled: true, mode: "monitor" Allow but log a warning (recommended for initial deployment)
enabled: false No scanning
curl -X PUT http://localhost:8080/admin/api/v1/security/content-scanning \
  -H "Content-Type: application/json" \
  -d '{"enabled": true, "mode": "enforce"}'

Input scanning (PII/secrets) — Scans tool call arguments for sensitive data before forwarding to upstream servers:

Pattern Type Action Examples
Email, Credit Card, SSN, UK NI mask Replaced with type-specific labels: [REDACTED-EMAIL], [REDACTED-CC], [REDACTED-SSN], [REDACTED-NINO]
Phone numbers mask Replaced with [REDACTED-PHONE]
AWS/GCP/Azure/Stripe/GitHub keys, generic secrets block Request rejected

Configure via Admin UI (Security → Input Scanning) or API:

# Toggle input scanning
curl -X PUT http://localhost:8080/admin/api/v1/security/input-scanning \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}'

# Add whitelist exception (skip email detection for a specific tool)
curl -X POST http://localhost:8080/admin/api/v1/security/input-scanning/whitelist \
  -H "Content-Type: application/json" \
  -d '{"pattern_type": "email", "scope": "tool", "value": "send_email"}'

# Whitelist scopes:
#   "tool"  — skip for a specific tool (any agent)
#   "agent" — skip for a specific agent identity (any tool)
#   "path"  — skip for files matching a path pattern

Configurable pattern actions — Each pattern type can be individually configured to off, alert, mask, or block via the Admin UI (Security → Input Scanning → Pattern Types dropdown) or API:

# Change email detection from mask to block
curl -X PUT http://localhost:8080/admin/api/v1/security/input-scanning \
  -H "Content-Type: application/json" \
  -d '{"pattern_actions": {"email": "block"}}'

# Disable phone number detection entirely
curl -X PUT http://localhost:8080/admin/api/v1/security/input-scanning \
  -H "Content-Type: application/json" \
  -d '{"pattern_actions": {"phone_number": "off"}}'
Action Behavior
off Pattern is not scanned
alert Detect and log, but allow the request through
mask Replace matched content with [REDACTED-xxx] placeholder
block Reject the entire tool call

Pattern action overrides are persisted in state.json and survive restarts.

Input scanning events (content.pii_detected, content.secret_detected) appear in the Notification Center.

Cryptographic evidence

Every tool call decision is recorded as a tamper-proof cryptographic evidence chain. Each record is ECDSA P-256 signed with a hash chain linking it to the previous record. Evidence is enabled by default — the signing key is auto-generated on first boot.

YAML configuration (optional — defaults are sensible):

evidence:
  enabled: true
  key_path: "evidence-key.pem"      # Auto-generated if missing
  output_path: "evidence.jsonl"
  signer_id: "my-server"

Verify the evidence chain:

# Verify with private key
sentinel-gate verify --evidence-file evidence.jsonl --key-file evidence-key.pem

# Verify with public key (preferred for external auditors)
sentinel-gate verify --evidence-file evidence.jsonl --pub-key evidence-key.pub.pem

Evidence records include: identity, tool name, decision, policy matched, reason (omitempty), latency_micros (int64, microseconds), timestamp, chain hash, and ECDSA signature. Used by the Compliance module for EU AI Act Art. 13-15 evidence requirements.

Tool security

Continuously monitors upstream MCP server tools for unauthorized changes (tool poisoning). Fully automatic — no manual setup required.

How it works:

  1. Auto-baseline at first boot — When SentinelGate starts and discovers tools for the first time, the baseline is captured automatically. No admin action needed.
  2. Auto-baseline on upstream changes — When you add or remove an MCP server via the Admin UI or API, the baseline is updated automatically. You can also click Capture Baseline to refresh it manually at any time.
  3. Periodic re-discovery (every 5 minutes) — SentinelGate re-calls tools/list on ALL active upstreams periodically. If a tool's definition has changed since the baseline, drift is detected immediately.
  4. Drift check on restart — When an upstream is restarted, its tools are re-discovered and checked against the baseline.
  5. Auto-quarantine on schema change — If a tool's definition (description or input schema) has changed, the tool is automatically quarantined and blocked from execution until an admin reviews and accepts the change.

Drift types:

Type Severity Auto-quarantine Action required
added Warning Yes Review and un-quarantine if trusted
removed Warning No Investigate removal
changed Warning Yes Accept change or keep quarantined

Notifications — drift events (tool.changed, tool.new, tool.removed) appear in the Notification Center with upstream name and tool details.

Admin workflow for quarantined tools:

# Check current drift
curl http://localhost:8080/admin/api/v1/tools/drift

# Accept a legitimate change (updates baseline, does NOT remove quarantine)
curl -X POST http://localhost:8080/admin/api/v1/tools/accept-change \
  -H "Content-Type: application/json" \
  -d '{"tool_name": "updated_tool"}'

# Remove quarantine after accepting the change
curl -X DELETE http://localhost:8080/admin/api/v1/tools/quarantine/updated_tool

# Or quarantine manually if you spot something suspicious
curl -X POST http://localhost:8080/admin/api/v1/tools/quarantine \
  -H "Content-Type: application/json" \
  -d '{"tool_name": "suspicious_tool"}'

# List all quarantined tools
curl http://localhost:8080/admin/api/v1/tools/quarantine

# Re-capture baseline manually (rarely needed — baseline updates automatically)
curl -X POST http://localhost:8080/admin/api/v1/tools/baseline

Human-in-the-loop approval

High-risk actions can require human approval. When a policy returns approval_required, the action is held pending until approved via Admin UI or API.

Note: The approval_required action is available only through the Admin UI or API. YAML config files support only allow and deny.

Example via API:

# Create a policy with approval_required action
# (uses $COOKIE_JAR and $CSRF_TOKEN from the CSRF pattern in the Kill Switch section above)
curl -X POST http://localhost:8080/admin/api/policies \
  -b "$COOKIE_JAR" -H "X-CSRF-Token: $CSRF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "require-approval-for-deletes",
    "rules": [{
      "name": "approve-deletes",
      "tool_match": "delete_*",
      "action": "approval_required",
      "priority": 100
    }]
  }'
# List pending
curl http://localhost:8080/admin/api/v1/approvals

# Get decision context (session trail, agent history, assessment)
curl http://localhost:8080/admin/api/v1/approvals/{id}/context

# Approve with audit note
curl -X POST http://localhost:8080/admin/api/v1/approvals/{id}/approve \
  -d '{"note":"approved per cleanup procedure"}'

# Deny with reason and audit note
curl -X POST http://localhost:8080/admin/api/v1/approvals/{id}/deny \
  -d '{"reason":"suspicious activity","note":"blocked per policy"}'

When an approval is pending, the Admin UI Notification Center shows a notification with Review/Approve/Deny buttons. Clicking "Review" opens the Decision Context panel with:

  • Request Detail — tool, arguments, which policy triggered the hold, the CEL condition
  • Session Trail — the agent's recent actions in chronological order
  • Agent History — how many times this agent has used this tool (last 30 days)
  • Contextual Assessment — deterministic notes (target is staging, agent consulted docs, etc.)
  • Audit Note — free-text field included in the cryptographic evidence (EU AI Act Art. 14)

Events emitted: approval.hold, approval.approved, approval.rejected, approval.timeout.

Warning

Stdio-based upstream MCP servers (e.g., npx) may timeout while waiting for approval.

Behavioral drift detection

SentinelGate analyzes agent behavior over time and detects when an agent deviates from its baseline. No ML — purely statistical (KL divergence, threshold-based comparisons, distribution shift detection).

# Get drift reports for all agents
curl http://localhost:8080/admin/api/v1/drift/reports

# Get drift profile for a specific agent
curl http://localhost:8080/admin/api/v1/drift/profiles/{identity_id}

# Reset baseline
curl -X POST http://localhost:8080/admin/api/v1/drift/profiles/{identity_id}/reset

# Get drift config
curl http://localhost:8080/admin/api/v1/drift/config

Anomaly types detected:

  • tool_shift — Tool usage distribution changed significantly (e.g., bash went from 8% to 41%)
  • deny_rate — Denial rate increased beyond threshold
  • error_rate — Error rate increased beyond threshold
  • latency — Average latency changed significantly
  • temporal — Activity pattern shifted (e.g., nighttime activity where baseline shows none)
  • arg_shift — Argument keys changed (e.g., tool started receiving different parameters)

Drift score: 0.0 (no drift) to 1.0 (severe drift). Visible in Agent View KPI strip. The detail view shows a Score Components breakdown (deny rate, error rate, total calls, avg latency, temporal pattern, argument shift) comparing historical (14-day) vs current values, plus a tool distribution comparison.

Default thresholds (configurable): 14-day baseline window, 1-day current window, 20% tool shift, 10% deny/error rate change, 50% latency change, 0.30 KL divergence.

Events emitted: drift.anomaly (with anomaly details), drift.baseline_reset.

Access Review / Shadow Mode

SentinelGate observes actual tool usage and compares it against policy-granted permissions to identify over-privileged agents. Four modes:

  • disabled — No analysis
  • shadow — Report only (no notifications)
  • suggest — Report + notify admin of permission gaps
  • auto — Apply auto-tighten suggestions after grace period

Permission Gap Types:

  • never_used — Tool permitted but zero calls in observation window (default 14 days)
  • rarely_used — Tool used 1-2 times in window
  • temporal_excess — Tool active only in narrow time window (consider time-based restriction)

Least Privilege Score: (used tools / permitted tools) × 100. Higher = better. Agents with score < 50% are flagged with warning severity.

Auto-Tighten Suggestions: Generated CEL deny rules for over-privileged tools, scoped to the specific identity. Can be applied individually or in batch. Whitelisted tools (configurable) are never suggested for removal.

Configuration (runtime via API):

{
  "mode": "suggest",
  "learning_days": 14,
  "grace_period_days": 7,
  "whitelist_tools": ["health_check", "auth_verify"]
}

Events emitted: permissions.gap_detected (with score, gap count), permissions.auto_tighten_applied (with tool list).

API endpoints:

  • GET /admin/api/v1/permissions/health — All agents health reports
  • GET /admin/api/v1/permissions/health/{identity_id} — Single agent health
  • GET /admin/api/v1/permissions/suggestions/{identity_id} — Auto-tighten suggestions
  • POST /admin/api/v1/permissions/apply — Apply suggestions (body: {identity_id, suggestion_ids})
  • GET /admin/api/v1/permissions/config — Shadow mode config
  • PUT /admin/api/v1/permissions/config — Update shadow mode config

Namespace Isolation

Filter the tools/list response based on the caller's roles. An agent with the "marketing" role sees only marketing tools — finance tools don't exist in their universe. This is stronger than policy deny (which blocks but reveals the tool exists).

Configure from the admin UI via API — no YAML config needed. Default: disabled (all tools visible to all roles).

Modes per role:

  • Whitelist (visible_tools): only listed tools are visible. Supports glob patterns (read_*).
  • Blacklist (hidden_tools): listed tools are hidden, everything else visible.
  • No rule: role has no restrictions (all tools visible).

When an identity has multiple roles, visibility is the union — if any role grants visibility, the tool is shown.

API endpoints:

  • GET /admin/api/v1/namespaces/config — Current namespace configuration
  • PUT /admin/api/v1/namespaces/config — Update config (body: {enabled, rules})

Example config:

{
  "enabled": true,
  "rules": {
    "marketing": {"visible_tools": ["search", "read_file", "analytics_*"]},
    "intern": {"hidden_tools": ["delete_*", "exec_command"]},
    "admin": {}
  }
}

OpenTelemetry Export

Export traces and metrics to stdout in OpenTelemetry format. Enable/disable from the admin UI — no YAML config needed.

Traces: One span per tool call with attributes: sg.identity_id, sg.tool_name, sg.decision, sg.drift_score.

Metrics:

  • sg.tool_calls.total — Counter by tool, decision, identity
  • sg.tool_calls.duration — Histogram in milliseconds
  • sg.tool_calls.denied — Counter of denied calls
  • sg.approvals.total — Counter by identity, tool, outcome

Traces and metrics are written to stdout in OTel JSON format when enabled. Use log collection (Fluentd, Vector, etc.) to forward to your observability stack.

API endpoints:

  • GET /admin/api/v1/telemetry/config — Current telemetry configuration
  • PUT /admin/api/v1/telemetry/config — Update config (body: {enabled, service_name})

Webhook notifications

Configure a webhook URL to receive event notifications via HTTP POST:

webhook:
  url: "https://hooks.slack.com/services/..."
  secret: "my-hmac-secret"          # optional, HMAC-SHA256 signing
  events: ["approval.hold", "drift.anomaly"]  # optional, empty = all events

The webhook receives JSON payloads with type, source, severity, timestamp, requires_action, and payload fields. When secret is set, payloads are signed with HMAC-SHA256 in the X-Signature-256 header.

Red Team Testing

Built-in attack simulation that tests your policies against 30 MCP-specific attack patterns across 6 categories:

Category Patterns Tests
Tool Misuse 7 Unauthorized access, path traversal in names, role violations
Argument Manipulation 7 Command injection, SQL injection, SSRF, template injection
Prompt Injection (Direct) 5 Instruction override, system prompt, base64 encoding
Prompt Injection (Indirect) 5 Model delimiters, hidden instructions, context switching
Permission Escalation 4 Role claiming, impersonation, config modification
Multi-Step Attack 2 Recon-then-exploit, data exfiltration chains

Each vulnerability includes a suggested CEL remediation policy that can be applied with one click.

API endpoints:

  • POST /admin/api/v1/redteam/run — Run full suite or by category (body: {target_identity, roles, category})
  • POST /admin/api/v1/redteam/run/single — Run single pattern (body: {pattern_id, target_identity, roles})
  • GET /admin/api/v1/redteam/corpus — List available attack patterns
  • GET /admin/api/v1/redteam/reports — Recent scan reports
  • GET /admin/api/v1/redteam/reports/{id} — Specific report

Cost Tracking

Cost estimation and budget guardrails for tool calls. Estimates cost per call based on configurable per-tool rates, tracks cumulative costs by identity and tool, and enforces budgets when approached or exceeded.

Retroactive tracking: Costs include all calls made during the current month, even before enabling Cost Tracking. Historical data is calculated from audit logs when the feature is activated.

Features:

  • Per-tool cost configuration (default $0.01/call, customizable)
  • Per-identity monthly budgets with configurable action: Notify (alert only) or Block (deny all tool calls when exceeded)
  • Threshold alerts at 70%, 85%, and 100% of budget
  • Cost drill-down: by identity → by tool
  • Linear projection to end of period

API endpoints:

  • GET /admin/api/v1/finops/costs — Cost report for period (query: start, end)
  • GET /admin/api/v1/finops/costs/{identity_id} — Identity cost detail
  • GET /admin/api/v1/finops/budgets — Budget statuses (triggers alert check)
  • GET /admin/api/v1/finops/config — Current Cost Tracking configuration
  • PUT /admin/api/v1/finops/config — Update config (body: {enabled, default_cost_per_call, tool_costs, budgets, budget_actions, alert_thresholds}). Budget entries accept action: "notify" (default) or action: "block" (deny calls when exceeded). The budget_actions map overrides per-identity budget actions (values: "notify" or "block").

Agent Health Dashboard

Per-agent health metrics with trend analysis and baseline comparison. The health dashboard fuses into the Agent View (no separate page), providing deny rate, drift score, error rate, and violation tracking with 30-day sparklines and baseline comparison.

Health Metrics:

  • deny_rate — Percentage of denied calls (0.0 to 1.0)
  • drift_score — Behavioral drift score from Drift Detection (0.0 to 1.0)
  • error_rate — Percentage of error calls (0.0 to 1.0)
  • violation_count — Total policy violations (denials + scan blocks)

Health Status Classification:

  • healthy — All metrics below warning thresholds
  • attention — At least one metric above warning but below critical
  • critical — At least one metric above critical threshold

Default Thresholds (configurable via API):

  • Deny rate: warning 10%, critical 25%
  • Drift score: warning 0.30, critical 0.60
  • Error rate: warning 5%, critical 15%

CEL Variables for policy rules: user_deny_rate, user_drift_score, user_violation_count, user_total_calls, user_error_rate. See Agent health variables for full documentation and examples.

Health Trend: 30-day sparklines for deny rate, error rate, violations, and call volume visible in the Agent View detail.

Cross-Agent Health Overview: Standalone table comparing all agents sorted by health status severity, accessible via "Health Overview" button on the agent list page.

API endpoints:

  • GET /admin/api/v1/agents/{identity_id}/health — Agent health trend + baseline comparison
  • GET /admin/api/v1/health/overview — Cross-agent health overview
  • GET /admin/api/v1/health/config — Current health thresholds
  • PUT /admin/api/v1/health/config — Update thresholds (body: {deny_rate_warning, deny_rate_critical, ...})

Events emitted: health.alert (when status is attention or critical).


6. Admin UI

Available at http://localhost:8080/admin when the server is running.

Dashboard — Real-time stats: upstream count, tool count, allowed/denied/rate-limited counts, protocol distribution chart, framework activity widget, active sessions with live quota progress bars, upstream status, recent activity feed. Auto-refreshes via SSE.

Getting Started

Expandable use-case cards (MCP Proxy, Connect Your Agent) with numbered steps and copyable code snippets, plus feature cards (Policy Templates, Response Transforms, Budget & Quota, Session Recording) linking to the relevant pages.

Tools & Rules

Four tabs:

  • Tools & Rules — Tool list grouped by upstream (with allow/deny badges you can click to create/edit rules), policy rules (create/edit/delete with priority and CEL), "Use Template" for one-click policy templates. The Visual Policy Builder modal offers a condition builder with typed operators, a variable catalog (35+ variables across 8 categories), smart suggestions, real-time policy linter, and bidirectional CEL ↔ Builder sync.
  • Transforms — Response transform rules (redact, truncate, inject, dry_run, mask) with a test sandbox.
  • Policy Test — Policy evaluation playground with optional session context for testing session-aware rules.
  • Simulation — Run "what-if" analysis on policy changes: simulates candidate rules against recent audit records and shows allow→deny / deny→allow impact, impacted agents, and impacted tools.

Connections

Identity management (name + roles), API key management (cleartext shown once at creation), per-identity quota configuration (calls, writes, deletes, rate limits). Connect Your Agent section with 7 tabs: Claude Code, Gemini CLI, Codex CLI, Cursor/IDE, Python, Node.js, cURL — each with ready-to-use configuration snippets.

Activity

Unified timeline of all intercepted actions. Filter by decision (allow/deny), protocol (MCP, HTTP, WebSocket), tool, identity, time period (including custom date range). Click entries for full detail panel. CSV export.

Sessions

Session recording configuration (enable/disable, privacy mode, retention, redact patterns). Session list with filters (identity, date range, denies). Click a session for timeline replay with expandable event cards. Export to JSON or CSV.

Security

Content scanning (response: monitor/enforce modes; input: PII/secret detection with configurable pattern actions and whitelist), tool security baseline/drift/quarantine with diff viewer.

Access Review

Shadow mode analysis of agent permissions vs actual usage. Heat matrix with least privilege scores per agent, gap analysis (never used, rarely used, temporal excess), auto-tighten suggestions with one-click apply or edit in Policy Builder. Shadow mode config (disabled/shadow/suggest/auto) with configurable learning window.

Red Team

Interactive red team report with scorecard per attack category, vulnerability cards with expand/collapse, attack explanation, suggested CEL remediation policy, one-click apply or edit in Policy Builder, and re-test button for immediate verification. Configurable target identity and roles.

Cost Tracking

Cost explorer with budget progress bars, cost drill-down by identity and tool, budget status tracking, and linear projection. Configurable per-tool costs and per-identity budgets with Notify or Block actions. Retroactive: includes all calls from the current month even before enabling.

Agents

Unified agent view with drill-down per identity. Header card (name, roles, session, health status badge), KPI strip (calls, denied, deny%, drift, violations), 30-day health trend sparklines (deny rate, error rate, violations, call volume), tool usage breakdown with proportional bars, drift score components breakdown (deny rate, error rate, calls, latency, temporal, arg shift), and chronological activity timeline. "Health Overview" button shows cross-agent comparison table sorted by health severity.

Notifications

Action queue for events requiring attention (tool drift, content scan detections, permission gaps, red team vulnerabilities, budget alerts) and informational events. Real-time SSE updates, badge counter in sidebar. Actions: accept change, quarantine, view diff, navigate, apply suggestions.

Compliance

Coverage map for regulatory framework packs (EU AI Act Art. 13-15). Overall score bar, per-requirement cards with pass/fail evidence checks, and evidence bundle generator (JSON download). Disclaimer always visible.


7. Configuration Reference

SentinelGate works with zero configuration. Everything can be managed from the Admin UI. YAML is optional for advanced tuning.

YAML reference

Config loaded from (first found wins): ./sentinel-gate.yaml, $HOME/.sentinel-gate/sentinel-gate.yaml, /etc/sentinel-gate/sentinel-gate.yaml (Linux/macOS) or %ProgramData%\sentinel-gate\sentinel-gate.yaml (Windows). Both .yaml and .yml extensions are accepted at each path.

# Server
server:
  http_addr: "127.0.0.1:8080"     # Listen address (default: "127.0.0.1:8080")
  log_level: "info"               # debug, info, warn, error (default: "info")
  session_timeout: "30m"          # Admin session timeout (default: "30m")
  allowed_hosts: []               # Additional hostnames for DNS rebinding protection on /mcp
                                  # (default: [] — localhost/127.0.0.1/::1 always allowed)
                                  # Also settable via SENTINEL_GATE_ALLOWED_HOSTS env var (comma-separated)

# Rate limiting
rate_limit:
  enabled: true                   # (default: true)
  ip_rate: 100                    # Per-IP requests/minute (default: 100)
  ip_burst: 100                   # Per-IP burst size (default: same as ip_rate)
  user_rate: 1000                 # Per-identity requests/minute (default: 1000)
  user_burst: 1000                # Per-identity burst size (default: same as user_rate)
  cleanup_interval: "5m"          # (default: "5m")
  max_ttl: "1h"                   # (default: "1h")

# Audit
audit:
  output: "stdout"                # "stdout" or "file:///path" (default: "stdout")
  channel_size: 1000              # Async buffer size (default: 1000)
  batch_size: 100                 # Flush batch size (default: 100)
  flush_interval: "1s"            # (default: "1s")
  send_timeout: "100ms"           # (default: "100ms")
  warning_threshold: 80           # Warn at N% full (default: 80)
  buffer_size: 1000               # In-memory ring buffer for UI (default: 1000)

# Audit file rotation (when output is file)
audit_file:
  dir: ""
  retention_days: 7               # (default: 7)
  max_file_size_mb: 100           # (default: 100)
  cache_size: 1000                # (default: 1000)

# Cryptographic evidence (optional)
evidence:
  enabled: true                   # Enable signed evidence chain (default: true)
  key_path: "evidence-key.pem"    # ECDSA P-256 key (auto-generated if missing)
  output_path: "evidence.jsonl"   # Append-only evidence file
  signer_id: ""                   # Signer identifier (default: hostname)

# Webhook notifications (optional)
webhook:
  url: ""                         # HTTP endpoint to POST events to
  secret: ""                      # HMAC-SHA256 secret for signing payloads
  events: []                      # Event types to send (empty = all)

# Upstream MCP server (optional, can also configure via Admin UI)
upstream:
  command: ""                     # MCP executable path
  args: []                        # Arguments
  http: ""                        # URL for remote MCP server
  http_timeout: "30s"             # (default: "30s")

# Auth (optional, can also configure via Admin UI)
auth:
  identities:
    - id: "id-1"
      name: "my-agent"
      roles: ["agent"]
  api_keys:
    - key_hash: "sha256:abc..."   # Use `sentinel-gate hash-key` to generate
      identity_id: "id-1"

# Policies (optional, can also configure via Admin UI)
# YAML rules only support name, condition, action. Priority is determined by
# rule order (first rule = highest priority). tool_match is always "*".
# For full control over tool_match and priority, use the Admin UI or API.
policies:
  - name: "deny-secrets"
    rules:
      - name: "block-secret-files"
        condition: 'action_arg_contains(arguments, "secret")'
        action: "deny"

Environment variables

Override any YAML key with SENTINEL_GATE_ prefix, underscores for nesting:

SENTINEL_GATE_SERVER_HTTP_ADDR=:9090 sentinel-gate start
SENTINEL_GATE_RATE_LIMIT_ENABLED=true sentinel-gate start

Special variables:

  • SENTINEL_GATE_STATE_PATH — override state file location (default: ./state.json)

State file

State (policies, identities, API keys, upstreams from Admin UI) persists in state.json in the working directory. YAML loads first, then state.json overlays on top. Runtime changes via Admin UI always go to state.json.

Note

Policy IDs are preserved across restarts. You can reference policies by either ID or name.


8. CLI Reference

sentinel-gate start

Start the proxy server.

sentinel-gate start

sentinel-gate stop

Stop the running server. Reads PID from ~/.sentinelgate/server.pid and sends a graceful shutdown signal. Waits up to 10s, then force-kills if needed.

sentinel-gate version

Print version, commit hash, build date, Go version, OS/architecture.

sentinel-gate hash-key

Generate a SHA-256 hash for use in YAML config. Output format: sha256:<hex>. Deterministic.

sentinel-gate hash-key <api-key>

sentinel-gate reset

Reset to a clean state, removing all runtime configuration created via the Admin UI or API.

By default, removes state.json and its backup. This clears all upstreams, policies, identities, API keys, content scanning config, and quotas. After a reset, the next start boots clean — as if it were the first launch.

If you have a sentinel-gate.yaml config file, settings defined there will still be loaded. The reset only affects runtime state, not your YAML configuration.

Flag Default Description
--force false Skip the confirmation prompt
--include-audit false Also remove audit log files (if using file-based audit output)
--include-certs false Also remove TLS inspection CA certificates (~/.sentinelgate/)

What gets removed

Scope Files Content
Default ./state.json, ./state.json.bak All runtime state
--include-audit Audit log file / directory As configured in audit.output and audit_file.dir
--include-certs ~/.sentinelgate/ CA certificate and private key for TLS inspection
sentinel-gate reset                                              # Interactive confirmation
sentinel-gate reset --force                                      # Skip confirmation
sentinel-gate reset --include-audit --include-certs --force      # Full cleanup
sentinel-gate reset --state /etc/sentinel-gate/state.json --force  # Custom state path

Note

The server must be stopped before resetting. If no state files exist, the command prints "Nothing to reset" and exits cleanly.

Live Factory Reset (API)

If the server is running and you want to reset without stopping it, use the Admin API:

curl -X POST http://localhost:8080/admin/api/system/factory-reset \
  -H "Content-Type: application/json" \
  -d '{"confirm": true}'

This is also accessible from the Admin UI via the Command Palette (Cmd+K → type "reset").

The live factory reset removes all runtime state while the server keeps running:

Cleared Preserved
MCP servers (upstream connections stopped) Audit logs (compliance)
Policies and rules YAML config defaults
Identities and API keys Read-only resources from YAML
Quotas and transforms
Active sessions
Pending HITL approvals
Tool baseline and quarantine
All feature configs (scanning, recording, drift, telemetry, namespaces, Cost Tracking, health, permissions, evidence)
Policy evaluation history
Stats and notifications

After the reset, the system is in the same state as a fresh start — ready to be configured from the Admin UI or API.

Note

Connected agents are immediately disconnected. Concurrent resets are prevented (returns HTTP 409).

sentinel-gate verify

Verify the integrity of a cryptographic evidence chain file.

Flag Default Description
--evidence-file (required) Path to the JSONL evidence file
--key-file Path to the private key PEM file (mutually exclusive with --pub-key)
--pub-key Path to PEM public key file (preferred for external verification)

One of --key-file or --pub-key is required.

sentinel-gate verify --evidence-file evidence.jsonl --key-file evidence-key.pem
sentinel-gate verify --evidence-file evidence.jsonl --pub-key evidence-key.pub.pem

Checks: hash chain integrity (each record links to the previous), ECDSA signature verification for every record. Exits 0 if valid, 1 if tampered.

Global flags

Flag Default Description
--config ./sentinel-gate.yaml Config file path
--state ./state.json State file path

9. Admin API Reference

Admin endpoints are under http://localhost:8080/admin/api/. Health probes (/health, /readyz) are at the server root. Write operations require CSRF protection.

Important

CSRF required on all POST/PUT/DELETE calls. Get the token first:

COOKIE_JAR=$(mktemp)
curl -s -c "$COOKIE_JAR" http://localhost:8080/admin/api/auth/status > /dev/null
CSRF_TOKEN=$(grep sentinel_csrf_token "$COOKIE_JAR" | awk '{print $NF}')

Then add -b "$COOKIE_JAR" -H "X-CSRF-Token: $CSRF_TOKEN" to every write request. Most curl examples below omit these headers for brevity.

Note

Core CRUD endpoints (upstreams, policies, identities, keys, audit) use /admin/api/. Newer feature endpoints (policy evaluation, approvals, security, tool security) use /admin/api/v1/. Both prefixes are stable. The path for each endpoint is shown in the sections below.

Error responses

All admin API errors return a JSON body:

{"error": "descriptive error message"}

Common HTTP status codes:

Code Meaning
400 Bad request (invalid JSON, missing required field, validation failure)
403 CSRF token mismatch or admin access denied
404 Resource not found
409 Conflict (already bootstrapped, reset in progress)
429 Rate limited — check Retry-After header for seconds until next allowed request
503 Kill switch active or system unhealthy

Authentication and CSRF

GET    /admin/api/auth/status                Get auth status + CSRF token (no auth required)
# Get auth status + CSRF token
curl -c cookies.txt http://localhost:8080/admin/api/auth/status

# Use CSRF token for write operations
CSRF_TOKEN=$(grep sentinel_csrf_token cookies.txt | awk '{print $NF}')
curl -b cookies.txt -H "X-CSRF-Token: $CSRF_TOKEN" \
  -X POST http://localhost:8080/admin/api/policies \
  -H "Content-Type: application/json" \
  -d '{"name": "my-policy", ...}'

Upstreams

GET    /admin/api/upstreams                  List upstreams
POST   /admin/api/upstreams                  Add upstream
PUT    /admin/api/upstreams/{id}             Update upstream
DELETE /admin/api/upstreams/{id}             Remove upstream
POST   /admin/api/upstreams/{id}/restart     Restart upstream

Create upstream request:

{"name": "my-server", "type": "http", "url": "http://localhost:3000/mcp"}
{"name": "local-server", "type": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]}

Tools

GET    /admin/api/tools                      List discovered tools (includes conflicts)
POST   /admin/api/tools/refresh              Force re-discovery

Policies

GET    /admin/api/policies                   List policies
POST   /admin/api/policies                   Create policy
PUT    /admin/api/policies/{id}              Update policy
DELETE /admin/api/policies/{id}              Delete policy
DELETE /admin/api/policies/{id}/rules/{ruleId}  Delete a single rule from a policy
POST   /admin/api/policies/test              Test policy (sandbox)

Create policy example:

curl -X POST http://localhost:8080/admin/api/policies \
  -H "Content-Type: application/json" \
  -d '{
    "name": "deny-secrets",
    "rules": [{
      "name": "block-secret-files",
      "condition": "action_arg_contains(arguments, \"secret\")",
      "action": "deny",
      "priority": 20,
      "tool_match": "*"
    }]
  }'

Policy evaluation

POST   /admin/api/v1/policy/evaluate                      Evaluate policy (note: v1 in path)
GET    /admin/api/v1/policy/evaluate/{request_id}/status   Check evaluation status

Identities

GET    /admin/api/identities                 List identities
POST   /admin/api/identities                 Create identity
PUT    /admin/api/identities/{id}            Update identity
DELETE /admin/api/identities/{id}            Delete identity

Create identity request:

{"name": "my-agent", "roles": ["user"]}

API keys

GET    /admin/api/keys                       List all keys
POST   /admin/api/keys                       Create key (response: cleartext_key)
DELETE /admin/api/keys/{id}                  Delete key

Create key request:

{"identity_id": "<identity-uuid>", "name": "my-key"}

Create key response (cleartext shown only once):

{"id": "uuid", "identity_id": "uuid", "name": "my-key", "cleartext_key": "sg_..."}

Audit

GET    /admin/api/audit                      Query audit log (?limit=200)
GET    /admin/api/audit/stream               SSE event stream
GET    /admin/api/audit/export               CSV export

Approvals (HITL)

GET    /admin/api/v1/approvals               List pending approvals
GET    /admin/api/v1/approvals/{id}/context   Decision context (session trail, history, assessment)
POST   /admin/api/v1/approvals/{id}/approve  Approve (body: {"note":"..."})
POST   /admin/api/v1/approvals/{id}/deny     Deny (body: {"reason":"...","note":"..."})

Behavioral drift detection

GET    /admin/api/v1/drift/reports                          All drift reports
GET    /admin/api/v1/drift/profiles/{identity_id}           Drift profile for identity
POST   /admin/api/v1/drift/profiles/{identity_id}/reset     Reset baseline
GET    /admin/api/v1/drift/config                           Current thresholds
PUT    /admin/api/v1/drift/config                           Update drift detection configuration

Security — Content scanning

GET    /admin/api/v1/security/content-scanning              Get response scan mode
PUT    /admin/api/v1/security/content-scanning              Update response scan mode
GET    /admin/api/v1/security/input-scanning                Get input scan config
PUT    /admin/api/v1/security/input-scanning                Toggle input scanning
POST   /admin/api/v1/security/input-scanning/whitelist      Add whitelist exception
DELETE /admin/api/v1/security/input-scanning/whitelist/{id}  Remove whitelist exception
POST   /admin/api/v1/security/content-scanning/patterns      Add custom pattern
GET    /admin/api/v1/security/content-scanning/patterns      List all patterns (builtin + custom)
DELETE /admin/api/v1/security/content-scanning/patterns/{id}  Remove custom pattern

Security — Tool security

POST   /admin/api/v1/tools/baseline                      Create baseline snapshot
GET    /admin/api/v1/tools/baseline                      Get baseline
GET    /admin/api/v1/tools/drift                         Get drift report
POST   /admin/api/v1/tools/accept-change                 Accept a drift change
POST   /admin/api/v1/tools/quarantine                    Quarantine a tool
DELETE /admin/api/v1/tools/quarantine/{tool_name}        Un-quarantine a tool
GET    /admin/api/v1/tools/quarantine                    List quarantined tools

Policy lint

POST   /admin/api/policies/lint                          Lint a CEL expression

Notifications

GET    /admin/api/v1/notifications                       List active notifications
GET    /admin/api/v1/notifications/count                 Pending count (badge)
GET    /admin/api/v1/notifications/stream                SSE real-time stream
POST   /admin/api/v1/notifications/{id}/dismiss          Dismiss notification
POST   /admin/api/v1/notifications/dismiss-all           Dismiss all

Agents

GET    /admin/api/v1/agents/{identity_id}/summary        Agent detail summary
GET    /admin/api/v1/agents/{identity_id}/health          Agent health trend + baseline
POST   /admin/api/v1/agents/{identity_id}/acknowledge    Acknowledge agent health alert

Agent Health

GET    /admin/api/v1/health/overview                     Cross-agent health overview
GET    /admin/api/v1/health/config                       Health alert thresholds
PUT    /admin/api/v1/health/config                       Update health thresholds

Compliance

GET    /admin/api/v1/compliance/packs                    List compliance packs
GET    /admin/api/v1/compliance/packs/{id}               Get pack details
POST   /admin/api/v1/compliance/packs/{id}/coverage      Analyze coverage
POST   /admin/api/v1/compliance/bundles                  Generate evidence bundle
GET    /admin/api/v1/compliance/evidence                Get evidence configuration
PUT    /admin/api/v1/compliance/evidence                Update evidence configuration

Simulation

POST   /admin/api/v1/simulation/run                      Run policy simulation

Red Team Testing

POST   /admin/api/v1/redteam/run                         Run attack suite (full or by category)
POST   /admin/api/v1/redteam/run/single                  Run single attack pattern
GET    /admin/api/v1/redteam/corpus                      List available attack patterns
GET    /admin/api/v1/redteam/reports                     Recent scan reports
GET    /admin/api/v1/redteam/reports/{id}                Specific scan report

Cost Tracking

GET    /admin/api/v1/finops/costs                        Cost report for period
GET    /admin/api/v1/finops/costs/{identity_id}          Identity cost detail
GET    /admin/api/v1/finops/budgets                      Budget statuses
GET    /admin/api/v1/finops/config                       Cost Tracking configuration
PUT    /admin/api/v1/finops/config                       Update Cost Tracking configuration

Quotas

GET    /admin/api/v1/quotas                           List all quotas
GET    /admin/api/v1/quotas/{identity_id}             Get quota for identity
PUT    /admin/api/v1/quotas/{identity_id}             Set/update quota
DELETE /admin/api/v1/quotas/{identity_id}             Remove quota

Sessions

GET    /admin/api/v1/sessions/active                  List active sessions
DELETE /admin/api/v1/sessions/{id}                   Terminate an active session

Recordings

GET    /admin/api/v1/recordings                       List recordings
GET    /admin/api/v1/recordings/{id}                  Get recording detail
GET    /admin/api/v1/recordings/{id}/events           Get recording events
GET    /admin/api/v1/recordings/{id}/export           Export recording (CSV/JSON)
DELETE /admin/api/v1/recordings/{id}                  Delete recording
GET    /admin/api/v1/recordings/config                Get recording config
PUT    /admin/api/v1/recordings/config                Update recording config

Templates

GET    /admin/api/v1/templates                        List policy templates
GET    /admin/api/v1/templates/{id}                   Get template detail
POST   /admin/api/v1/templates/{id}/apply             Apply template (creates policies)

Transforms

GET    /admin/api/v1/transforms                       List transform rules
POST   /admin/api/v1/transforms                       Create transform rule
PUT    /admin/api/v1/transforms/{id}                  Update transform rule
GET    /admin/api/v1/transforms/{id}                  Get a single transform rule
DELETE /admin/api/v1/transforms/{id}                  Delete transform rule
POST   /admin/api/v1/transforms/test                  Test transform in sandbox

Namespace Isolation

GET    /admin/api/v1/namespaces/config                Get namespace config
PUT    /admin/api/v1/namespaces/config                Update namespace config

Access Review (Shadow Mode)

GET    /admin/api/v1/permissions/health                        All agents permission gap analysis
GET    /admin/api/v1/permissions/health/{identity_id}          Single agent permission gaps
GET    /admin/api/v1/permissions/suggestions/{identity_id}     Least privilege suggestions
POST   /admin/api/v1/permissions/apply                         Apply suggestion as policy
GET    /admin/api/v1/permissions/config                        Shadow mode config
PUT    /admin/api/v1/permissions/config                        Update shadow mode config

Telemetry (OpenTelemetry)

GET    /admin/api/v1/telemetry/config                 Get OTel config
PUT    /admin/api/v1/telemetry/config                 Update OTel config

System

GET    /admin/api/stats                      Dashboard stats
GET    /admin/api/system                     System info
POST   /admin/api/system/factory-reset       Reset all runtime state to clean
POST   /admin/api/v1/bootstrap               One-shot bootstrap (upstreams, identities, policies)
POST   /admin/api/v1/system/kill             Activate kill switch (requires {"reason": "..."})
GET    /admin/api/v1/system/kill             Get kill switch status
POST   /admin/api/v1/system/resume           Deactivate kill switch

Factory reset request body:

{"confirm": true}

Response:

{
  "success": true,
  "upstreams_removed": 3,
  "policies_removed": 2,
  "identities_removed": 4,
  "keys_removed": 6,
  "quotas_removed": 2,
  "transforms_removed": 1,
  "recordings_deleted": 2,
  "sessions_cleared": 3,
  "approvals_cancelled": 0,
  "quarantine_cleared": 1,
  "stats_reset": true,
  "notifications_reset": true,
  "skipped_read_only": ["identity:admin-from-yaml"]
}

Returns HTTP 409 if a reset is already in progress. Audit logs are intentionally preserved.

Health

GET    /health                               Liveness probe (component health)
GET    /readyz                               Readiness probe (bootstrap, tools, kill switch)

/health — Liveness probe. Returns HTTP 200 when core components are operational.

Response:

{
  "status": "healthy",
  "checks": {
    "session_store": "ok",
    "rate_limiter": "ok",
    "audit": "ok: 0/1000 (0%)",
    "upstreams": "ok",
    "goroutines": "16"
  },
  "version": "2.1.3"
}

When audit records are dropped, an additional audit_drops key appears:

{
  "checks": {
    "audit_drops": "42 dropped"
  }
}

Returns HTTP 503 with "unhealthy" when audit buffer exceeds 90% capacity.

/readyz — Readiness probe. Returns HTTP 200 when the system is ready to serve traffic. Returns HTTP 503 with details when not ready. Use this for Kubernetes readinessProbe. For Docker HEALTHCHECK and other container restart-triggers, use /health instead — this prevents a persistent kill switch from causing a restart loop.

Response:

{
  "ready": true,
  "checks": {
    "tools_discovered": "ok: 14 tools",
    "upstreams": "ok",
    "kill_switch": "ok: inactive",
    "bootstrapped": "ok"
  }
}

When the kill switch is active, ready is false and kill_switch shows "not ready: kill switch active". Agents should poll for "ready": false to detect kill switch activation.

Authentication for MCP and Admin endpoints

Endpoint Authentication method
MCP proxy (/mcp) Authorization: Bearer <key> (API key generated at bootstrap or via Admin UI)
Admin API Session cookie from GET /admin/api/auth/status + X-CSRF-Token header

Admin API access control

The Admin API and dashboard have no native authentication (no username/password). Access control is network-level:

  • By default (binary): the admin API is bound to localhost only. Only processes on the same machine can reach it.
  • In Docker (SENTINEL_GATE_ADMIN_OPEN=true): the localhost check is disabled because Docker port forwarding doesn't preserve the original client IP. Access control is delegated to Docker network isolation.
  • Cloud platforms (Cloud Run, Smithery, Modal): When the PORT environment variable is set, SentinelGate binds to all interfaces (0.0.0.0:<PORT>) instead of localhost. The Docker image sets PORT=8080 by default.
  • In production: place a reverse proxy (Caddy, nginx) with authentication in front of the admin API, or use Kubernetes NetworkPolicy to restrict access.

Warning

SENTINEL_GATE_ADMIN_OPEN=true makes the admin API and dashboard accessible from ANY IP that can reach the port. Docker's -p 8080:8080 binds to 0.0.0.0 by default (all interfaces), not just localhost. On shared networks, use -p 127.0.0.1:8080:8080 to restrict access.

For production on public servers: use a firewall, a reverse proxy with authentication, or SSH tunnel (ssh -L 8080:localhost:8080 yourserver).

Risk matrix:

Environment ADMIN_OPEN Port Exposed Risk Action
Local machine, bare metal not set localhost only None None
Local machine, Docker true 0.0.0.0:8080 by default Low Use -p 127.0.0.1:8080:8080 on shared networks
Private server, Docker true internal port Low Firewall recommended
Public server, Docker true public port High Firewall mandatory or reverse proxy with auth

This is consistent with how most infrastructure tooling (Prometheus, Redis, Traefik) approaches the problem — the network is the primary security boundary. API keys protect the MCP proxy endpoint (/mcp), not the admin interface.


10. Multi-Agent Sessions

Multiple agents can connect to the same SentinelGate instance simultaneously. Each agent uses its own identity and API key.

# Terminal 1: Start the server
sentinel-gate start

# Terminal 2: Agent 1 (e.g., Claude Code) connects with key-1
# Terminal 3: Agent 2 (e.g., Gemini CLI) connects with key-2
# Terminal 4: Agent 3 (e.g., Python script) connects with key-3

Per-agent isolation

Each agent connects with a separate API key linked to a distinct identity. This gives you:

  • Separate audit trails — every action is tagged with the agent's identity
  • Per-agent policies — use identity_name or identity_roles in CEL rules to apply different policies to different agents
  • Per-agent quotas — set different usage limits for each identity
  • Independent sessions — each agent has its own session context for session-aware policies

Shared tool pool

All agents see the same set of upstream tools (unless filtered by namespace role-based visibility rules). When an upstream is added or removed, all connected agents receive a notifications/tools/list_changed notification.


11. Troubleshooting

Server won't start

Port in use: SENTINEL_GATE_SERVER_HTTP_ADDR=:9090 sentinel-gate start

Corrupt state file: SentinelGate automatically falls back to state.json.bak if the primary file is corrupt. If both files are corrupt, delete them and re-bootstrap: rm state.json state.json.bak && sentinel-gate start

Windows — permission denied on evidence-key.pem or state.json: state files and the evidence signing key default to the current working directory. Running sentinel-gate.exe from a read-only location — e.g. C:\Program Files\... without administrator rights, or as a Windows Service with no WorkingDirectory set — fails at startup with permission denied. Fix: run the binary from a writable folder (your user directory works), or set explicit paths via state_path, evidence.key_path, and evidence.output_path in sentinel-gate.yaml. When running as a Windows Service, always set WorkingDirectory to a writable path.

MCP connection issues

  • Agent can't connect: Verify the MCP URL is http://localhost:8080/mcp and the Authorization: Bearer <key> header is set correctly.
  • Tools not appearing: Wait a few seconds after adding an upstream for discovery. Check Admin UI → Tools & Rules to verify tools are listed.
  • Upstream timeout: Stdio servers (npx) are single-threaded. Too many parallel requests overwhelm the pipe. Restart the upstream from Admin UI.
  • Tool list not refreshing: Ensure your agent supports notifications/tools/list_changed. If not, disconnect and reconnect the agent.

Authentication

  • MCP proxy: Authorization: Bearer <key>
  • Admin API: The CSRF token is set as a sentinel_csrf_token cookie on any GET request. Read this cookie and send it back as the X-CSRF-Token header on POST/PUT/DELETE requests.

Tip

API key not working? Use cleartext_key from creation response (not the hash). Keys are loaded at boot and on creation.

Audit log empty

Check that traffic is flowing through the MCP proxy. The Policy Evaluate API also generates audit records.

Rule never matches

  • Set tool_match: "*" (when creating rules via the API, tool_match defaults to "*" if omitted; YAML config rules always match all tools — the tool_match field is only available in the API)
  • Check priority — higher wins
  • Use Policy Test sandbox in Admin UI

Policy IDs

Policy IDs are UUIDs generated at creation time and preserved across restarts in state.json. You can reference policies by either ID or name.

Audit buffer filling

Auto-recovers with adaptive flushing (4x faster at >80%). If audit_drops is non-zero, increase audit.channel_size.


12. FAQ

How do I connect my agent to SentinelGate?

Configure SentinelGate as an MCP server in your agent's settings. The Admin UI Connections page has ready-to-use configuration snippets for Claude Code, Gemini CLI, Codex CLI, Cursor/IDE, Python, Node.js, and cURL. See Agent Configuration for details.

My agent uses an MCP client library. How do I protect it?

Point the MCP client at http://localhost:8080/mcp instead of the real MCP server. Add the real server as upstream in Admin UI → Tools & Rules → Add Upstream. Create an identity + API key for authentication.

Can I connect multiple agents at once?

Yes. Each agent connects with its own API key and identity. All agents share the same upstream tools (unless filtered by namespace role-based visibility rules). See Multi-Agent Sessions.

What about Codex?

Codex supports MCP — configure SentinelGate as an MCP server in ~/.codex/config.toml. See Agent Configuration.

Are native agent tools (Read, Write, Bash) intercepted?

SentinelGate intercepts MCP tool calls only. If an agent has native tools that do not go through MCP, those are not intercepted by SentinelGate. The protection applies to all tools routed through the MCP proxy. For full isolation, run the agent inside a Docker container with no filesystem mount — the agent's native tools find nothing, and it must use MCP through SentinelGate. See examples/claude-code/ for this pattern.

How do I run Claude Code through SentinelGate in Docker?

Use examples/claude-code/. Start SentinelGate with docker compose up -d sentinel-gate, then launch Claude with docker compose run -e ANTHROPIC_API_KEY=... claude. Claude runs in an isolated container and can only access files through SentinelGate. See the example README for details.

How do I bootstrap SentinelGate without the UI?

Create a bootstrap.json file and mount it at /etc/sentinelgate/bootstrap.json in your container. SentinelGate reads it on first boot, creates upstreams, identities, and API keys, then renames the file to bootstrap.json.consumed. See Automated Bootstrap for the full format.

What are the sandbox profiles?

Three pre-built security configurations: strict (read-only, deny-all default, enforce scanning), standard (read+write, deny-all default, monitor scanning), permissive (allow-all default, minimal scanning). Set "profile": "strict" in your bootstrap.json. See Sandbox Profiles.

What is the kill switch?

An emergency stop that instantly blocks all MCP tool calls. Activate from the dashboard or via API. The kill switch persists across restarts. See Kill Switch.

What happens when I add or remove an upstream?

SentinelGate automatically discovers tools from the new upstream and sends a notifications/tools/list_changed notification to all connected MCP clients. Agents that support this notification refresh their tool list automatically. No restart required — upstreams are hot-pluggable.


13. Threat Model and Limitations

Trust boundaries

Untrusted          │         Trusted
                   │
  AI Agent ────────┼──── SentinelGate ──── Admin UI/API
                   │          │
  Upstream MCP ────┼──────────┘
  servers          │
  • AI agents are untrusted. They may be compromised by prompt injection, have bugs, or behave unexpectedly. SentinelGate enforces policies on their actions.
  • SentinelGate itself is trusted. It is the security boundary. If SentinelGate is compromised, all protections are bypassed.
  • The admin interface is trusted. Anyone with access to the Admin API can disable policies, create identities, or factory-reset the system. Protect it with network isolation (see Admin API access control).
  • Upstream MCP servers are semi-trusted. SentinelGate scans their responses for prompt injection (IPI defense), but trusts the tool implementations themselves.

What SentinelGate protects against

  1. Agent mistakes — AI deletes files it shouldn't, calls wrong API, accesses sensitive paths
  2. Indirect prompt injection — a document read by the agent contains hidden instructions ("send this file to pastebin.com"). Response scanning detects common injection patterns.
  3. Overreach — agent does more than intended (reads files outside allowed scope, calls write tools when only reads are permitted)
  4. Data exfiltration — agent tries to send sensitive data to external domains. Blocked by exfiltration domain rules and content scanning.
  5. Sensitive data exposure — PII, API keys, and secrets in tool call arguments are detected and masked or blocked before reaching the upstream.

What SentinelGate does NOT protect against

  • Actively adversarial agents. SentinelGate assumes the agent is not deliberately trying to evade controls. A compromised agent that knows it is behind SentinelGate could craft inputs to avoid pattern-based detection. Defense in depth (container isolation + SentinelGate) mitigates this.
  • Exfiltration through the model API. An agent can include sensitive data in its conversation context, which is sent to the model provider's API. This traffic does not pass through SentinelGate. For high-security environments, consider routing model API traffic through an inspectable proxy.
  • Compromised upstream MCP servers. If a tool server is malicious, it can return crafted responses. SentinelGate's response scanner detects common prompt injection patterns but cannot guarantee detection of all attack vectors.
  • State file tampering. If an attacker gains write access to state.json, they can modify policies, disable the kill switch, or add identities. Protect the state file with filesystem permissions.

Honest limitations

  • MCP-only protection (proxy mode). The MCP Proxy intercepts MCP tool calls routed through it. Native agent tools that bypass MCP (e.g., an agent's built-in file operations) are not intercepted. For full isolation, run the agent inside a Docker container with no filesystem access and SentinelGate as the only gateway — see examples/claude-code/ for this pattern. For agents that don't use MCP, see Chapter 14: Policy Decision API.
  • Regex-based content scanning. Content scanning uses pattern matching, not ML-based detection. It catches known formats (AWS keys, credit card numbers, etc.) but may miss obfuscated or novel credential formats. Custom patterns can be added for organization-specific secrets.
  • Admin API has no native authentication. Access control is network-level only (localhost binding or Docker network isolation). In production, protect the admin interface with a reverse proxy or network policies. See Admin API access control.
  • Stdio upstream timeout during approval. MCP servers via stdio (npx) may close the connection while waiting for human approval.
  • Tool poisoning detection is near-real-time. Drift detection runs every 5 minutes and on every upstream restart. Changed tools are auto-quarantined immediately. However, calls made during the window between a tool change and the next re-discovery cycle are not retroactively blocked.
  • No native TLS. Transport encryption requires a reverse proxy (see Production Deployment section).
  • Audit logs in the Admin UI are not cryptographically protected. Only the evidence chain (ECDSA P-256 + hash chain) provides tamper-proof records. See Cryptographic Evidence.
  • API keys created via UI or API are stored as Argon2id hashes in state.json (OWASP-recommended parameters: 47 MiB memory, 1 iteration, 1 parallelism). Keys seeded from YAML config use SHA-256 hashes for fast O(1) lookup at startup. The cleartext key is shown only once at creation time. Keys do not expire automatically — revoke unused keys via the Admin UI or API.

14. Policy Decision API (PDP)

The problem

SentinelGate's MCP Proxy (Chapter 4) is powerful: it intercepts every tool call, applies policies, scans content, enforces rate limits, and provides a complete audit trail. But it only works for MCP traffic. If your agent calls REST APIs, runs SQL queries, executes shell commands, or uses any protocol that isn't MCP, the proxy can't help — it never sees that traffic.

The solution

SentinelGate also operates as a Policy Decision Point (PDP) — a well-established security pattern used by Open Policy Agent (OPA), AWS Verified Permissions, and Google's Zanzibar. Instead of intercepting traffic, it answers a simple question: "Is this action allowed?"

Your agent calls SentinelGate's evaluate endpoint before executing an action. SentinelGate evaluates the action against your CEL policies (the same policies used by the MCP Proxy) and returns allow, deny, or approval_required. The agent then decides whether to proceed.

MCP Proxy mode (Chapter 4):
  Agent  ──traffic──>  SentinelGate  ──traffic──>  MCP Server
                       (intercepts, evaluates, enforces)

Policy Decision API mode (this chapter):
  Agent  ──"can I?"──>  SentinelGate  ──"yes/no"──>  Agent  ──traffic──>  Any tool
                        (evaluates only)

Key differences from the MCP Proxy

MCP Proxy Policy Decision API
How it works Agent connects to SentinelGate as its MCP server. All tool calls are intercepted. Agent asks SentinelGate "can I do this?" before each action. Traffic never flows through SentinelGate.
Security stack Full: policies + content scanning + rate limiting + kill switch + session tracking + audit Policy evaluation + audit only
Enforcement Physical — the agent cannot bypass it Cooperative — the agent must choose to obey
Protocol MCP only Any — REST, gRPC, SQL, shell, custom
Agent code changes None You must add SDK calls before each action

Which agents can use it?

Only agents whose code you control. CLI agents and IDE extensions are closed applications — you can't inject policy-check code into their tool-calling logic.

Agent MCP Proxy Policy Decision API
Claude Code, Gemini CLI, Codex CLI, Cursor Yes No — closed applications, can't modify their code
Python/Node.js MCP SDK Yes Yes, but MCP Proxy gives full security stack
LangChain, CrewAI, AutoGen Yes Yes, but MCP Proxy gives full security stack
Custom agent (non-MCP) No — doesn't use MCP Yes — this is the primary use case

Recommendation: If your agent uses MCP, use the MCP Proxy. It's more secure (physical enforcement, full security stack) and requires zero code changes. Use the Policy Decision API only for agents that operate outside the MCP protocol.

Quick example

A Python agent that calls a REST API — before executing, it asks SentinelGate:

from sentinelgate import SentinelGateClient, PolicyDeniedError
import requests

client = SentinelGateClient()
# Reads SENTINELGATE_SERVER_ADDR and SENTINELGATE_API_KEY from environment

# Before calling the REST API, ask SentinelGate
try:
    client.evaluate("api_call", "delete_user",
                    arguments={"user_id": "123"},
                    destination={"domain": "api.myapp.com"})
    # Allowed — proceed
    requests.delete("https://api.myapp.com/users/123")
except PolicyDeniedError as e:
    # Denied — don't execute
    print(f"Blocked by rule: {e.rule_name}{e.reason}")

The policies are the same CEL rules you use with the MCP Proxy:

action_name.startsWith("delete_") → deny
"admin" in identity_roles → allow
dest_domain_matches(dest_domain, "*.production.com") → deny

Setup

  1. Deploy SentinelGate (same as always)
  2. Create policies via the Admin UI or bootstrap — the same CEL rules work for both the MCP Proxy and the PDP
  3. No upstreams needed — SentinelGate is not in the traffic path
  4. Install an SDK in your agent: pip install sentinelgate (Python), npm install @sentinelgate/sdk (Node.js), or go get github.com/sentinel-gate/sdk-go (Go)
  5. Set environment variables on your agent process:
    SENTINELGATE_SERVER_ADDR=http://sentinel-gate:8080
    SENTINELGATE_API_KEY=your-api-key
  6. Add evaluate calls before each action in your agent code

Admin API access

The evaluate endpoint lives on the admin API (/admin/api/v1/policy/evaluate), which is restricted to localhost by default. If your agent runs in a separate container, set SENTINEL_GATE_ADMIN_OPEN=true on the SentinelGate service. This is already the default in Docker.

CSRF required. All POST requests to the admin API require a CSRF token. Send a matching token in both the X-CSRF-Token header and the sentinel_csrf_token cookie (double-submit pattern). Generate any random token and include it in both places — see the curl example in Quick Start.

Response schema

Field Type Description
decision string "allow", "deny", or "approval_required"
request_id string Unique ID for this evaluation
reason string Why the decision was made
rule_id string ID of the matched rule (empty on default deny/allow)
rule_name string Name of the matched rule (empty on default deny/allow)
help_text string Guidance for the agent (deny only)
help_url string Link to policy documentation (deny only)
latency_ms integer Evaluation time in milliseconds

Features

  • Same CEL policy engine as the MCP Proxy — all your existing rules work
  • Approval workflow — the evaluate endpoint can return approval_required, enabling human-in-the-loop. The SDKs poll for approval automatically (30 polls, 2-second intervals)
  • Audit trail — every evaluate call is logged in the audit trail, visible in the Admin UI alongside MCP Proxy audit events
  • Caching — the SDKs cache allow decisions (default: 5 seconds TTL, up to 1000 entries) to minimize latency
  • Fail-open / fail-closed — configurable behavior when SentinelGate is unreachable: open (allow, default) or closed (deny)

What you lose compared to the MCP Proxy

Be aware of what the Policy Decision API does NOT do:

  • No content scanning — SentinelGate cannot detect PII, secrets, or credentials in your agent's data because it never sees the actual traffic
  • No rate limiting — rate limits are enforced in the proxy; the PDP is a lightweight check
  • No kill switch — the emergency stop only blocks MCP Proxy traffic
  • No physical enforcement — if your agent has a bug and skips the evaluate call, the action executes without governance

For the complete SDK reference (constructors, methods, environment variables, error handling), see connecting-agents.md — Section 10.


SentinelGate is licensed under the GNU Affero General Public License v3.0.