Skip to main content
Policy rules are a pre-dispatch policy layer: simple regex allow/deny lists for four kinds of request content. Each check is a synchronous RE2 regex match with no evaluator run, enforced at the gateway in microseconds. To run an evaluator on request or response content instead, see Guardrails. Four kinds of patterns:
  • Tools: matched against the names of tools the client declares.
  • MCP servers: matched against MCP server names, URLs the client declares.
  • URLs: heuristically matched against outbound URLs in tool-call arguments.
  • Models: regex-matched against the resolved model id (distinct from the glob-based models_allowed allowlist, see Models dimension vs models_allowed).
Each has a deny list and an optional allow list.
Enforcement point: policy-rule checks run immediately after authentication and rate limiting, before guardrails, budget reconciliation, or the upstream provider. The tools, MCP, and URL dimensions judge the request body as sent. The models dimension runs one step later, at model resolution, and judges the resolved model. A blocked request never incurs a provider token cost, a guardrail evaluator run, or a budget debit. This is the cheapest possible reject path in the dispatcher.

How it works

Deny semantics: any value matching a deny regex is blocked → 403. Allow semantics: if allow is non-null, it becomes an allowlist, only values matching allow pass through. Anything else is blocked. allow: null means “no allowlist, deny-only.” Regex flavour: patterns are compiled as Go RE2. This is a linear-time regex engine, lookbehind, backreferences, and unbounded lookahead are not supported (by design, to eliminate ReDoS risk). Escape dots literally and anchor your patterns.

What gets checked

Tools

OpenAI Chat Completions: matched against tools[].function.name. Anthropic Messages: matched against tools[].name. Blocked → 403 tool_not_allowed with the offending name in error.message. (In v1, block attribution flows via the X-LangWatch-Gateway-Request-Id response header → trace correlation; a dedicated langwatch.policy.violation span attribute is a v1.1 observability follow-up.)

MCP servers

If the request declares mcp_servers (on the Anthropic Messages API), each .name and .url is matched. Blocked → 403 tool_not_allowed (we conflate MCP denials with tool denials for the error envelope; the policies_triggered field disambiguates).

URLs

Every http:// and https:// URL appearing anywhere in the request body is extracted and evaluated against policy_rules.urls. Extraction is deliberately permissive: a URL present in the payload is a URL the model could be asked to fetch, regardless of which JSON key it was nested under, user messages, tool-call arguments, system prompts, all covered. Trailing markdown/JSON punctuation (.,;:)]} etc.) is stripped and duplicates are collapsed before matching. First URL that hits a deny rule (or falls outside a non-null allow list) → 403 url_not_allowed before the request reaches any upstream provider. Attribution via request-id correlation (see §Tools above).
Enforcement vs egress control. This layer stops a request from reaching a provider when it contains a disallowed URL. It does not prevent a provider from returning a URL that a downstream tool-runner then fetches, that requires an egress proxy on the node running the tool call, which is out of scope for the gateway. Pair blocked URLs with egress controls for defence in depth.

Models

The models dimension is RE2-matched against the resolved model id, the model that serves the request and gets billed, not the string that the caller typed. Alias resolution runs first. Then the rules judge its result. This is policy-by-regex (e.g. “block anything matching ^gpt-4(-turbo)?$”), distinct from the static models_allowed glob allowlist on the VK. Judging the resolved id has two effects:
  • An alias that points to a denied model is blocked. An alias named gpt-4 does not go around a rule that denies ^gpt-4(-turbo)?$.
  • A rule that denies a name that resolves to a different model does not block. If gpt-4 is an alias for azure/my-eastus-deployment, a deny on ^gpt-4$ never fires. The denied name never runs.
Rules match the two spellings of the resolved model, the bare id and the provider-qualified id. ^gpt-4.* and ^openai/gpt-4.* both reach openai/gpt-4o. Write one form. You do not need both. The other three dimensions read the request body as sent. The gateway judges tools, MCP servers, and URLs as the client wrote them. Blocked → 403 model_not_allowed. Attribution via request-id correlation (see §Tools above).

Models dimension vs models_allowed

Both gate which models a VK can call, but they solve different problems: They compose: a request passes only if both checks allow it. The gateway applies models_allowed first, at model resolution. The regex rules then judge the resolved model. The two checks read the resolved model in the two spellings, so an alias cannot go around them. Use models_allowed unless you need regex semantics, then reach for policy_rules.models.

Common policies

Block shell execution

Only allow approved filesystem paths

Only allow official MCPs

Outbound URL allowlist

Configuring via the UI

On the VK edit screen (Gateway → Virtual Keys → Edit), the Policy Rules section has four rows, one per dimension, each with a pair of monospace textareas: The section header links to the relevant 403 error codes (tool_not_allowed, url_not_allowed, model_not_allowed) and calls out the fail-closed behaviour: if any regex is invalid, the VK fails with 503 service_unavailable, never silent-bypass. Fix the pattern, save the VK, and the next request picks it up (the bundle refresh invalidates the broken compiled cache). Leave a row empty to skip that dimension entirely.

Common presets

The drawer exposes common starting points (click the helper links next to each textarea):
  • No shell execution: ^shell\..*, ^bash$, ^exec$, ^run_command$ in Tools → Deny.
  • Read-only filesystem: ^filesystem\.(write|delete|chmod|move)$ in Tools → Deny.
  • Official MCPs only: ^@modelcontextprotocol/.*, ^@anthropic-ai/.* in MCP → Allow.
  • Internal URLs only: ^https?://api\.internal\.com/.* in URLs → Allow.
  • Block legacy GPT-4: ^gpt-4(-turbo)?$ in Models → Deny.

Performance

Pattern checks are synchronous RE2 regex evaluations; total latency is sub-microsecond even for VKs with dozens of patterns. Patterns are compiled lazily on the first request that touches a bundle and cached on the bundle; cache auto-invalidates on config refresh.

Fail-closed behaviour

If the VK’s policy_rules config is malformed (e.g. an invalid regex), the gateway does not silently bypass policy. Instead the request is rejected with 503 service_unavailable and the gateway WARN-logs policy_rules_compile_failed + policy_rules_broken with the dimension + error text. This is deliberate: a policy engine that stops enforcing on bad config is worse than one that errors loudly. (A dedicated langwatch.policy.broken span attribute is a v1.1 observability follow-up; v1 operators correlate via the request-id header.) Fix the policy via langwatch virtual-keys update <id> (or the UI) and subsequent requests resume immediately, the bundle refresh invalidates the broken compiled cache.

Example: blocked tool response

The param field pinpoints the offending argument so client SDKs can surface a targeted error to end users. See API: Errors for the full envelope.

Combining with guardrails

Policy rules are the first policy check on a request, before any guardrail call. If a pattern matches, the request fails fast without incurring a guardrail evaluator run. Use patterns for deterministic rules (tool names, URL allowlists) and guardrails for anything that needs semantic analysis (is this prompt an injection attempt?).

Auditing

Every block emits:
  • AuditLog row (gateway shape) with actor (user or service principal, service-account VKs use actor=svc_<projectId>), VK id, policy kind, matched pattern, offending value (truncated to 64 chars), request id.
  • X-LangWatch-Gateway-Request-Id response header for joining the 403 back to the trace stream.
(A dedicated langwatch.policy.violation=<kind>:<pattern> span attribute is tracked as a v1.1 observability follow-up. In v1, use the audit log + request-id correlation to identify repeat offenders and tune patterns.)
Last modified on August 15, 2026