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 Shadow mode (tenant default) (default true)
LevelHow to configure
GlobalDashboard: Settings → Security → Shadow Mode toggle. SDK: client.tenants.update({ shadowMode: false })
Per-agentDashboard: Agents → [agent] → Settings → Shadow Mode toggle. SDK: client.agents.update(agentId, { shadowMode: false })
Per-bindingDashboard: Agents → [agent] → MCP Servers → [server] → Shadow Mode. SDK: client.agents.updateBinding(agentId, mcpServerId, { shadowMode: false })
Recommended rollout: Recommended rollout: start globally in shadow mode (the default), tighten per-agent as you validate each agent's traffic, then set shadow mode = off 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

typescript
import ShieldAgent from '@shieldagent/sdk';

const client = new ShieldAgent();

// Create a custom template
const template = await client.policyTemplates.create({
  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)
await client.policyTemplates.apply(template.id);

Live Policy Updates

Policy changes take effect automatically — no restart or redeployment needed. When you create, update, or delete a policy in the dashboard or via the API, the change is picked up by the proxy within seconds. For time-sensitive changes (such as adding a new deny rule), you can force an immediate update from the dashboard or API.

Managing Policies in the Dashboard

The dashboard is the recommended way to create, review, and manage policies:

  1. 1Go to Policies in the left sidebar to see the full rule list for your tenant.
  2. 2Click New Policy to create a rule — set tool name, action, optional agent scope, and conditions.
  3. 3Use the search and filter bar to find policies by tool name, action, or agent.
  4. 4Click any policy row to edit or delete it. Changes hot-reload to the proxy automatically.
  5. 5Go to Settings → Security to toggle shadow mode globally for the tenant.

SDK & Dashboard

All policy operations require authentication. See Authentication for details.

Policies

Create a policy ruleDashboard: Policies → New Policy. SDK: client.policies.create({ toolName, action, ... })
List all policiesDashboard: Policies page. SDK: client.policies.list() — filter by agentId to scope to one agent
Update a policyDashboard: Policies → click row → edit. SDK: client.policies.update(policyId, { ... })
Delete a policyDashboard: Policies → click row → Delete. SDK: client.policies.delete(policyId)
Force immediate hot-reloadDashboard: Policies → Invalidate Cache. SDK: client.policies.invalidateCache()

Policy Templates

List all templatesDashboard: Policies → Templates tab. SDK: client.policyTemplates.list()
Create a custom templateDashboard: Policies → Templates → New Template. SDK: client.policyTemplates.create({ name, rules, ... })
Update a custom templateDashboard: Policies → Templates → click row → edit. SDK: client.policyTemplates.update(templateId, { ... })
Apply a templateDashboard: Policies → Templates → click row → Apply. SDK: client.policyTemplates.apply(templateId)

Example — Create a policy

typescript
import ShieldAgent from '@shieldagent/sdk';

const client = new ShieldAgent();

await client.policies.create({
  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
Policy Engine