Skip to main content
Sign in →

Policy Engine

Fine-grained rules that control which MCP tools agents can call, under what conditions, and whether violations are enforced or silently observed in shadow mode.

How It Works

The policy engine runs inside the ShieldAgent proxy pipeline, evaluated after authentication and before the security scanner. Every tools/call request is matched against your tenant's rule set. Other MCP methods (tools/list, resources/read, etc.) pass through without policy checks.

Request → Auth → Policy → Security → Upstream MCP Server

Default: implicit deny. If no policy rule matches a given (agentId, toolName) request, the engine denies it. Tools without explicit allow rules are blocked by default — no configuration required to stay safe.

Policy Schema

Each policy binds a tool name to an action and optionally adds conditions that must all be satisfied for the rule to fire.

FieldTypeDescription
toolNamestringThe MCP tool name this rule applies to (e.g. bash, read_file, query_database)
actionallow | deny | shadowWhat to do when the rule matches
agentIdUUID | nullScope to a specific agent. null = applies to all agents in the tenant
conditionsarray | nullArray of condition objects (AND logic). If omitted, the rule matches unconditionally

Actions

ActionBehavior
allowPermit the tool call. Conditions are still checked.
denyBlock the tool call and return an error to the agent.
shadowUsed by the template system. Excluded from the live rule set — shadow mode enforcement is controlled separately via the cascade (see below).

Scoping & Precedence

Agent-specific rules (non-null agentId) take priority over tenant-wide rules (agentId: null). Within the same specificity level, deny rules run before allow rules and conditional rules run before unconditional ones.

Condition Types

Conditions are optional predicates on a rule. When multiple conditions are present, all must match (AND logic). If any condition fails, the rule is skipped and evaluation continues to the next rule.

param_contains — Substring Match

Checks whether a request parameter contains a given substring (case-sensitive). Use dot-notation to address nested fields like arguments.command.

json
// Deny any bash call where the command contains "rm -rf"
{
  "toolName": "bash",
  "action": "deny",
  "conditions": [
    {
      "type": "param_contains",
      "param": "arguments.command",
      "value": "rm -rf"
    }
  ]
}

param_matches — Regex Match

Checks whether a request parameter matches an ECMAScript regular expression. Useful for path allowlists or structured value patterns.

json
// Allow read_file only for paths under /app/data/
{
  "toolName": "read_file",
  "action": "allow",
  "conditions": [
    {
      "type": "param_matches",
      "param": "arguments.path",
      "pattern": "^\/app\/data\/"
    }
  ]
}

time_window — UTC Hour Restriction

Restricts tool access to specific UTC hours using a half-open range [start, end). Combine with other conditions to enforce business-hours-only access.

json
// Allow query_database only during business hours (09:00–17:00 UTC)
{
  "toolName": "query_database",
  "action": "allow",
  "conditions": [
    {
      "type": "time_window",
      "allowedHours": [9, 17]
    }
  ]
}

rate_limit — Invocation Cap

Declares a maximum invocation rate for the tool. The policy evaluator records the threshold; enforcement is handled by the proxy's dedicated rate-limit pipeline stage.

json
// Cap send_email to 10 calls per minute
{
  "toolName": "send_email",
  "action": "allow",
  "conditions": [
    {
      "type": "rate_limit",
      "maxPerMinute": 10
    }
  ]
}

Shadow Mode

Shadow mode lets you roll out new policies safely. When enabled, the engine evaluates each request as normal but does not block it even if a deny rule matches. The decision is logged to the audit trail with outcome: 'shadow' and a shadowDeny: true flag so you can review impact before enforcing.

Three-Level Cascade

Shadow mode is resolved from most specific to least specific — the first non-null value wins:

1. Per-binding (set per agent-to-server connection, nullable)
↓ if null
2. Per-agent (set per agent, default true)
↓ if null
3. Global Tenant default (Settings → Security) (default true)
LevelHow to configure
GlobalDisable shadow mode in Settings → Security to enforce everywhere
Per-agentPATCH /tenants/:tenantId/agents/:agentId with {"shadowMode": false}
Per-bindingPATCH /tenants/:tenantId/agents/:agentId/mcp-bindings/:mcpServerId with {"shadowMode": false}
Recommended rollout: start globally in shadow mode (the default), tighten per-agent as you validate each agent's traffic, then set shadow mode disabled via Settings once all agents are confirmed clean.

Policy Templates

Templates are pre-built rule sets that you can apply to a tenant in one API call. Applying a template creates one policy record per rule, scoped tenant-wide. ShieldAgent ships system templates for common security, compliance, and development scenarios; you can also create custom templates for organization-specific rule sets.

TypeEditableDescription
SystemRead-onlyShipped with ShieldAgent. Security, compliance, and development presets.
CustomFull CRUDCreated by your team. Organization-specific rule sets for your toolchain.

Creating and Applying a Custom Template

bash
# Create a custom template
curl -s -X POST https://api.shieldagent.io/tenants/:tenantId/policy-templates \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Restrict destructive tools",
    "description": "Deny dangerous shell commands for production agents",
    "category": "security",
    "rules": [
      {
        "toolName": "bash",
        "action": "deny",
        "conditions": [{"type": "param_contains", "param": "arguments.command", "value": "rm -rf"}]
      },
      {
        "toolName": "bash",
        "action": "deny",
        "conditions": [{"type": "param_contains", "param": "arguments.command", "value": "DROP TABLE"}]
      }
    ]
  }'

# Apply a template (creates one policy per rule)
curl -s -X POST https://api.shieldagent.io/tenants/:tenantId/policy-templates/:templateId/apply \
  -H 'Authorization: Bearer <token>'

Hot-Reload Architecture

Policy changes take effect without restarting the proxy. The proxy maintains an in-memory compiled rule set per tenant and refreshes it automatically.

1.

Cold-start hydration: On startup, the proxy loads cached policies for instant availability — no wait for the first poll.

2.

Periodic poll: At a configurable interval (default 30 s), the proxy fetches the latest compiled policy set for each active tenant from the Management API.

3.

Change detection: Rules are hashed before recompilation is triggered. No-op polls have zero overhead.

4.

Push invalidation: For critical changes (e.g. a new deny rule), use the API to force an immediate re-fetch without waiting for the next poll cycle.

SettingDefaultDescription
Policy hot-reloadtrueSet to false to disable hot-reload entirely
Reload interval30000Polling interval in milliseconds
Management API URLManagement API base URL. Required to enable hot-reload

API Reference

All policy endpoints require a bearer token. See Authentication for details.

Policies

POST/tenants/:tenantId/policies·policy:writeCreate a policy rule
GET/tenants/:tenantId/policies·policy:readList all policies. Filter by ?agentId= to scope to one agent
GET/tenants/:tenantId/policies/:policyId·policy:readGet a single policy
PUT/tenants/:tenantId/policies/:policyId·policy:writeUpdate a policy (partial — include only changed fields)
DELETE/tenants/:tenantId/policies/:policyId·policy:deleteDelete a policy
GET/tenants/:tenantId/policies/compiled·policy:readCompiled rule set used by the proxy. Excludes shadow-action rules
POST/tenants/:tenantId/policies/invalidate·policy:writeForce immediate hot-reload in the proxy

Policy Templates

GET/policy-templates·policy:readList system templates and your tenant's custom templates
GET/policy-templates/:templateId·policy:readGet a template
POST/tenants/:tenantId/policy-templates·policy:writeCreate a custom template
PUT/tenants/:tenantId/policy-templates/:templateId·policy:writeUpdate a custom template (system templates are read-only)
DELETE/tenants/:tenantId/policy-templates/:templateId·policy:writeDelete a custom template
POST/tenants/:tenantId/policy-templates/:templateId/apply·policy:writeApply a template — creates one policy per rule, all scoped tenant-wide

Example — Create a policy

bash
curl -s -X POST https://api.shieldagent.io/tenants/:tenantId/policies \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "toolName": "bash",
    "action": "deny",
    "agentId": null,
    "conditions": [
      {
        "type": "param_contains",
        "param": "arguments.command",
        "value": "rm -rf"
      }
    ]
  }'

Required permissions

PermissionGrants
policy:readView policies, templates, and compiled rules
policy:writeCreate / update policies and templates, invalidate cache, apply templates
policy:deleteDelete policies and custom templates

Audit Trail

Every policy evaluation is appended to the immutable audit trail. Key fields on each event:

FieldValue / meaning
outcomeallowed, denied, human_review_required, or shadow
matchedRuleIdUUID of the first matching rule; null for implicit deny
shadowDenytrue when shadow mode converted a deny to an allow
reasonHuman-readable explanation of the decision