Safeguard.sh Documentation Center

Guard Policies

Author and manage the policies that decide whether a given MCP tool call is allowed, denied, or monitored by Guard.

Guard Policies

A Guard policy is the set of rules that Guard evaluates against every MCP request flowing through the Guard SDK or a Guard proxy deployment. Each rule says, in effect, "when a request looks like this, take this action" — allow it through, deny it outright, or let it through while flagging it for monitoring. This page covers how policies are structured, what they can match on, and how a change you make reaches every running instance of your agents.

This is a different mechanism from Guardrails & Enforcement, which gates CI/CD, registries, admission, and runtime against SBOM/CVE/license/signing rules using BLOCK / WARN / AUTO_FIX effects. Guard policies instead govern live MCP tool calls at request time, with allow / deny / monitor actions. If you're looking for supply-chain policy-as-code for your pipeline, see that page instead.

Where policies are managed

You author and edit Guard policies from your Safeguard workspace — there's no local policy file to write or check into source control. Whatever the SDK or a proxy is enforcing at any moment is a live pull of whatever's currently saved in your workspace. A policy is scoped to your organization and made up of an ordered list of rules, each of which can be edited, reordered, or removed independently.

Under the hood, a policy is its own record with an id, name, description, an is_default flag, and a top-level priority, on top of its list of rules. Your organization can have more than one policy record stored, but at any moment exactly one — the one with is_default: true — is the policy Guard actually loads and enforces. The SDK's background sync and the proxy both resolve to that same default policy; a policy you create or edit without marking it default is saved but never evaluated against live traffic. The Safeguard workspace UI manages this for you when you edit "your policy," but it matters if you're also driving policies through the API below.

Keep exactly one policy per organization marked as the default. The lookup Guard uses to resolve "the" default policy doesn't apply any tie-breaking order across multiple is_default: true rows for the same org — if more than one exists, which one gets loaded isn't guaranteed to be consistent.

A policy's own top-level priority field is separate from the priority on each of its rules — it only affects the order policies are listed in (for example, in the dashboard or a GET /api/v1/policies call), not which policy is active. Rule-level priority, covered next, is what determines match order within the active policy.

Managing policies via the API

Most day-to-day edits happen in the Safeguard workspace UI, but the same CRUD operations are available as a REST API if you want to manage policy from a script or your own tooling. Requests are authenticated the same way as other dashboard API calls — a bearer JWT from your Safeguard session (see RBAC, Teams & Organizations).

# List policies for your organization
curl https://<your-guard-host>/api/v1/policies \
  -H "Authorization: Bearer <your-jwt>"

# Fetch a single policy
curl https://<your-guard-host>/api/v1/policies/<policy-id> \
  -H "Authorization: Bearer <your-jwt>"

# Create a policy
curl -X POST https://<your-guard-host>/api/v1/policies \
  -H "Authorization: Bearer <your-jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production policy",
    "description": "Default enforcement for the prod org",
    "is_default": true,
    "priority": 0,
    "rules": []
  }'

# Update a policy (replaces its fields, including the full rules list)
curl -X PUT https://<your-guard-host>/api/v1/policies/<policy-id> \
  -H "Authorization: Bearer <your-jwt>" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Production policy", "is_default": true, "priority": 0, "rules": [] }'

# Delete a policy
curl -X DELETE https://<your-guard-host>/api/v1/policies/<policy-id> \
  -H "Authorization: Bearer <your-jwt>"

POST and PUT both take the full policy object, including its complete rules array — there's no separate endpoint for adding or editing a single rule, so send the whole rule list each time. Fields your JSON body doesn't recognize are silently ignored rather than rejected, so a typo'd field name (like match_tool instead of match_tools) won't surface as a validation error — it just won't do anything. Check field names against the example policy below if a rule doesn't seem to be having the effect you expect.

How a request is evaluated

When a request comes in, Guard checks it against your policy's rules in priority order, highest priority first. The first rule that matches wins — its action is applied and evaluation stops there. If no rule matches, Guard falls back to a default decision, which flags the request for monitoring rather than silently allowing or blocking it.

If your organization doesn't have any policy marked default yet, the same fallback applies at the whole-policy level — Guard flags all traffic for monitoring rather than allowing or blocking it, until you create a policy and mark it as the default.

If two or more rules share the same priority value, Guard doesn't guarantee a stable order between them — treat equal priorities as "order unspecified," not "evaluated in the order I wrote them in." Give rules distinct priority values whenever their relative order matters.

What a rule can match on

A rule can combine any of the following conditions. All conditions on a rule must be satisfied for it to match; if a rule doesn't specify a given condition at all, that condition is treated as "matches anything."

Match onBehavior
MethodMatches the MCP JSON-RPC method exactly (case-insensitive) — for example, restricting a rule to tools/call requests only.
Tool nameMatches the name of the tool being called, using glob-style wildcards (* for any sequence of characters, ? for a single character), case-insensitive. A rule with a tool-name match only ever applies to requests that actually carry a tool name — it's skipped for requests where a tool name isn't part of the payload.
Agent IDMatches an exact agent identifier, letting you scope a rule to a specific calling agent rather than every agent in your org.

Beyond these top-level match conditions, a rule's input filters give you finer-grained content matching against the request body itself — for example, blocking a request whose parameters match a given pattern, rejecting oversized input, or catching malformed encoding. See Input and output filters below.

There's currently no separate "server name" match condition distinct from tool name and agent ID. If you need to scope a rule to tools exposed by a particular MCP server, match on the tool names (or naming convention) that server exposes, or scope by agent ID if that server is only ever called by a specific agent.

Actions

Each rule resolves to exactly one of three actions:

ActionEffect
allowThe request proceeds normally.
denyThe request is blocked before it reaches the tool. You can attach a reason that's surfaced back to the caller explaining why.
monitorThe request proceeds, but is flagged so you can review it later — useful for rules you're still tuning, or traffic you want visibility into without blocking it yet.

A rule can also carry a rate limit (a request count over a time window) and jailbreak / anomaly detection thresholds, which feed into Guard's request analysis independently of the match conditions above:

{
  "rate_limit": { "limit": 100, "window_sec": 60 },
  "jailbreak_threshold": 0.75,
  "anomaly_threshold": 0.80
}

Both thresholds are scores Guard's analysis compares an incoming request against — the higher the value, the more confident Guard has to be before treating a request as a jailbreak attempt or an anomaly. If a rule doesn't set one of these thresholds, it inherits the organization-wide default (0.75 for jailbreak, 0.80 for anomaly, unless your deployment is configured with different values) rather than being left unset.

Input and output filters

Filters are attached to a rule and run when that rule matches — input filters against the incoming request, output filters against the tool's response.

Input filters run before the tool call executes:

TypeWhat it does
block_patternDenies the request if its parameters match a given regular expression.
max_lengthDenies the request if its parameters exceed a configured length.
encoding_checkDenies the request if its parameters contain invalid encoding.
jailbreak_checkDenies the request if its parameters match known prompt-injection / jailbreak phrasing (for example, "ignore previous instructions," "you are now in DAN mode," attempts to reveal a system prompt, and similar patterns).

Output filters run on the tool's response before it's returned to the caller:

TypeWhat it does
pii_maskDetects and masks common PII patterns in the response — email addresses, SSNs, credit card numbers, phone numbers, IP addresses, API keys, JWTs, and bearer tokens.
column_redactRedacts specific named columns/fields from structured (tabular) output.
row_limitCaps the number of rows returned.
regex_maskMasks any part of the response matching a given regular expression.

Filter behavior is defined generically by type — exactly which filter types and parameters are available to you may expand over time. Treat the tables above as the concepts currently supported rather than an exhaustive, frozen schema.

Example policy

Putting the pieces above together, here's what a policy with two rules looks like as the underlying JSON object — the same shape the workspace UI (and the API described above) is ultimately editing:

{
  "id": "8f14e45f-ceea-4d2f-8b5b-3f1a2c9e9a1b",
  "org_id": "3fa0e6d2-2c1a-4b9b-9a2e-8e0c1d6f7a90",
  "name": "Production policy",
  "description": "Default enforcement for the prod org",
  "is_default": true,
  "priority": 0,
  "rules": [
    {
      "id": "block-jailbreak-support-tools",
      "name": "Block jailbreak attempts against support tools",
      "description": "Deny anything that looks like a prompt-injection attempt on customer-support tool calls",
      "action": "deny",
      "deny_reason": "Request blocked: matched jailbreak/prompt-injection pattern",
      "match_methods": ["tools/call"],
      "match_tools": ["support_*"],
      "input_filters": [
        { "type": "jailbreak_check" },
        { "type": "max_length", "max_length": 8000 }
      ],
      "jailbreak_threshold": 0.75,
      "priority": 100
    },
    {
      "id": "default-pii-mask",
      "name": "Mask PII and limit exported rows",
      "action": "allow",
      "match_methods": ["tools/call"],
      "match_tools": ["export_*", "query_*"],
      "output_filters": [
        { "type": "pii_mask" },
        { "type": "column_redact", "columns": ["ssn", "internal_notes"] },
        { "type": "row_limit", "limit": 500 }
      ],
      "priority": 10
    }
  ],
  "created_at": "2026-06-01T12:00:00Z",
  "updated_at": "2026-07-01T09:30:00Z"
}

A few things worth noting in this shape:

  • Rules are evaluated highest rule priority first — the jailbreak rule (100) is checked before the PII-masking rule (10).
  • match_tools uses glob patterns (support_*, export_*, query_*) rather than exact tool names.
  • deny_reason is only meaningful on deny rules — it's surfaced back to the caller and recorded on the resulting audit event as rule_id / rule_name / deny_reason. See Audit Trail & Alerts.
  • Input filters and output filters are independent lists — a rule can carry either, both, or neither.

How policy changes propagate

Policy changes you make in your Safeguard workspace don't need to be pushed or deployed anywhere. Both the Guard SDK and Guard proxy deployments continuously pull the live policy for your organization in the background, so an edit you save reaches every running SDK instance and proxy within moments — no restart, redeploy, or config reload required on your side.

If a running instance temporarily can't reach Safeguard to refresh its policy, it keeps enforcing the last policy it successfully fetched rather than failing open (allowing everything) or failing closed (blocking everything) while it retries.

FAQ & Troubleshooting

Do I need to write policy files or check anything into source control? No. Policies live in your Safeguard workspace and are pulled live by the SDK and proxy — there's nothing to author or deploy in your own repo.

What happens if no rule in my policy matches a request? It falls through to a default decision that flags the request for monitoring rather than silently allowing it, so unmatched traffic doesn't go unnoticed while you're still building out rules.

Why isn't a rule with a tool-name match doing anything on non-tool requests? A rule that matches on tool name only ever applies to requests that carry a tool name in the first place (like tools/call). It's intentionally skipped for other request types rather than matching everything when the tool name is empty.

Can two rules match the same request? Only the highest-priority matching rule applies — evaluation stops at the first match. If you expect both rules to fire, check their priority ordering.

How is this different from the guardrails described elsewhere in the docs? Guardrails & Enforcement governs your software supply chain — CI, registries, admission, and runtime — using YAML policy-as-code with BLOCK / WARN / AUTO_FIX effects. Guard policies are a separate system that governs live MCP tool calls at request time, with allow / deny / monitor actions.

I created a second policy, but its rules don't seem to be enforced. Only the policy marked is_default: true for your org is the one Guard actually loads and evaluates — other policies you create via the API are stored but inert until you make them the default. See Where policies are managed.

Two of my rules have the same priority — which one wins? Don't rely on it being consistent. Guard sorts rules by priority but doesn't guarantee a stable tie-break order between rules that share the same value — give the rules distinct priorities if their relative order matters.

I added a field to my rule's JSON and nothing happened. Unrecognized fields in a policy create/update request are silently ignored rather than rejected, so a misspelled field name (like match_tool instead of match_tools) won't error — it just won't take effect. Compare your rule against the example policy above if a change doesn't seem to be doing anything.

What are the jailbreak and anomaly thresholds actually comparing against? They're the minimum score Guard's request analysis has to produce before that signal counts as a match — set per rule, or inherited from your org's defaults (0.75 for jailbreak, 0.80 for anomaly) when a rule doesn't specify its own. See Audit Trail & Alerts for where these scores show up on individual requests, and Guard Webhooks for alerting on them.

  • Guard SDK — embed policy enforcement directly in your MCP server or agent process.
  • Guard Proxy — enforce policy for MCP traffic without modifying your agent's code.
  • Servers & Agents — register the agents that match_agent_ids targets by ID; there's no separate server match condition, so scoping a rule to a server's tools works by tool name instead (see the note above).
  • Audit Trail & Alerts — every rule match is recorded here, with the rule_id / rule_name / deny_reason behind each decision.
  • Guard Webhooks — get notified when jailbreak/anomaly scores or blocked-request ratios cross a threshold.
  • RBAC, Teams & Organizations — the JWT used to authenticate calls to the policies API.
  • Guardrails & Enforcement — the separate policy-as-code system for CI/CD, registries, admission, and runtime.

On this page