dtwo Policy Store

Freeze the Zapier Toolset (No Self-Expansion)

In its default agentic mode, Zapier MCP exposes meta-tools that let the agent widen its own blast radius mid-session : enable zapier action and auto…

Direction
ingress
Rego package
zapier.ingress.freeze_toolset
App
zapier
Bundle
soc2
Published
Minimum gateway
1.0.0b24
Schema version
1.0.0
Checksum
sha256:98e71dcc7eebd87653a9f2a62a46e7cef9640596c7dd49fa39db2934e74c9894

zapierconstrain-aggregatoringresssoc2

What this policy does

Direction: ingress (tool_pre_invoke) Default: deny self-modifying meta-tools for non-admins, allow otherwise Package: zapier.ingress.freeze_toolset

What it does

In its default agentic mode, Zapier MCP exposes meta-tools that let the agent widen its own blast radius mid-session: enable_zapier_action and auto_provision_mcp add new actions to the toolset, write_code_action creates an arbitrary code-execution action, and create_zapier_skill / update_zapier_skill / delete_zapier_skill persist Markdown instructions that future sessions auto-load — a prompt-injection persistence vector that outlives the conversation.

This policy denies those six self-modifying meta-tools unless the caller's IdP groups claim includes automation-admins, converting the self-expanding aggregator into a fixed-capability connector. disable_zapier_action and every read/execute meta-tool (execute_zapier_read_action, execute_zapier_write_action, list_enabled_zapier_actions, discover_zapier_actions, list_zapier_skills, get_zapier_skill, get_configuration_url, send_feedback) pass through, so the agent can still exercise — and narrow — its existing toolset, it just cannot grow it.

Missing identity claims fail closed: a caller with no groups claim (or no claims at all) is not exempt and is denied.

Compliance alignment

  • SOC 2 CC6.6 — supports boundary protection against external threats: the gateway's security boundary around the Zapier connector stays fixed instead of being re-drawable by the agent (or by injected instructions) mid-session.
  • SOC 2 CC6.8 — supports the prevention of unauthorized software: write_code_action creates arbitrary code-execution actions and enable_zapier_action / auto_provision_mcp install new capabilities into the agent's toolset; this policy restricts all three to an authorized admin group.
  • SOC 2 CC9.2 — supports vendor/business-partner risk management: Zapier is a single vendor surface that can reach 9,000+ downstream apps, and this policy pins what that surface is allowed to become to an admin-controlled configuration.

Tool name matching

The policy matches the six frozen meta-tools by suffix:

  • *enable_zapier_action
  • *auto_provision_mcp
  • *write_code_action
  • *create_zapier_skill
  • *update_zapier_skill
  • *delete_zapier_skill

The DTwo gateway prefixes tool names with the configured MCP server name (e.g. zapier-mcp-enable_zapier_action), and that prefix is not standardized — matching on the suffix keeps the policy portable. Verify the exact names your gateway sends with the dump-input debug technique before relying on this in production.

disable_zapier_action does not match the enable_zapier_action suffix (the preceding character differs), so narrowing the toolset stays available to everyone by design.

Argument shape

None. This policy decides purely on the tool name and the caller's identity claims — it never inspects input.payload.args, so it is immune to argument-shape drift in Zapier's meta-tools.

Identity is read via object.get(input.subject, "claims", {}) and object.get(claims, "groups", []); the groups claim is expected to be an array of strings as emitted by the tenant's IdP.

Examples

Allowed — read/execute meta-tool, any caller

{
  "input": {
    "action": "tool_pre_invoke",
    "resource": { "name": "zapier-mcp-execute_zapier_read_action", "type": "tool" },
    "subject": { "sub": "auth0|dev", "claims": { "groups": ["engineering"] } },
    "payload": { "name": "zapier-mcp-execute_zapier_read_action", "args": {} }
  }
}

allow = true, no reason.

Denied — non-admin tries to enable a new action

{
  "input": {
    "action": "tool_pre_invoke",
    "resource": { "name": "zapier-mcp-enable_zapier_action", "type": "tool" },
    "subject": { "sub": "auth0|dev", "claims": { "groups": ["engineering"] } },
    "payload": { "name": "zapier-mcp-enable_zapier_action", "args": { "action": "gmail_send_email" } }
  }
}

allow = false, reason = "This Zapier connector's toolset is frozen (...)".

Allowed — automation admin enables a new action

{
  "input": {
    "action": "tool_pre_invoke",
    "resource": { "name": "zapier-mcp-enable_zapier_action", "type": "tool" },
    "subject": { "sub": "auth0|admin", "claims": { "groups": ["engineering", "automation-admins"] } },
    "payload": { "name": "zapier-mcp-enable_zapier_action", "args": { "action": "gmail_send_email" } }
  }
}

allow = true, no reason.

Composition

This policy freezes the toolset's shape; it does not constrain what the already-enabled toolset can do. Useful companions:

  • A read-only-posture ingress policy denying execute_zapier_write_action for non-approved groups — one rule fences every write across 9,000 apps.
  • An app-blocklist policy that inspects the action identifier inside execute_zapier_read_action / execute_zapier_write_action arguments (e.g. deny finance apps outside a finance group).
  • A content policy that treats the instructions argument as content and scans it for non-corporate recipients or PII patterns — Zapier's server-side AI fills unspecified fields from instructions after the gateway has already passed the call.
  • For Zapier's classic (manual configuration) mode, a default-deny-unknown-tools allowlist policy pinned to the per-account tool inventory.

Known limitations

  • Agentic mode only. The six frozen meta-tools exist only in Zapier MCP's dynamic tool-discovery (agentic) mode. In classic manual-configuration mode the policy is inert but harmless — classic tool names are <app>_<action> shapes that do not end in these suffixes.
  • Group name is a placeholder. Replace automation-admins with your IdP's real group name at import time, and confirm your IdP actually emits a groups claim in the access token (many IdPs require explicit configuration to do so). Callers whose tokens carry no groups claim are denied — including would-be admins.
  • Group comparison is exact and case-sensitive. Membership is a whole-string == on each groups element: Automation-Admins, automation-admins-plus, or the admin name emitted under a different claim (e.g. roles) never match — those callers are denied (fail closed). Match the placeholder to your IdP's group string exactly, including case.
  • groups must be an array. If your IdP emits groups as a single string or a space-delimited string, the membership check never matches and all callers are denied the frozen tools (fail closed). Adjust the is_automation_admin rule if your IdP uses a non-array shape.
  • Skill reads still pass. list_zapier_skills / get_zapier_skill are allowed, so a previously poisoned skill written before this policy was attached can still be loaded. Audit existing skills once when attaching this policy; the freeze prevents new persistence, not the reading of old state.
  • Tool names verified against Zapier's official MCP docs (docs.zapier.com, mid-2026). If Zapier renames or adds self-modifying meta-tools, extend frozen_suffixes accordingly — a default-deny-unknown-tools companion policy catches such drift automatically.
  • Name drift is the residual bypass. Suffix matching (endswith) is exact on the trailing bytes of input.resource.name, so any extended variant of a frozen name does not match and is allowed — a hypothetical enable_zapier_action_v2, but equally a name carrying a trailing space or newline (enable_zapier_action\n). Tool names are set by the upstream MCP server, not by the caller, so this is not a caller-controlled bypass on Zapier's hosted server; but this policy cannot anticipate names that do not exist yet. Pair it with the default-deny-unknown-tools allowlist companion if you need drift to fail closed.
  • Matching keys on input.resource.name. This is the canonical PARC tool-name field, reliably populated on every tool_pre_invoke hook and carrying the same value as the legacy payload.name alias. The policy never reads payload.name, so it does not depend on the deprecated alias.

Compliance note. This policy supports alignment with the cited framework controls on the MCP path only. No policy or bundle makes an organization compliant with any framework; web-UI, native-API, and in-app access are outside the gateway's reach by design. Validate against your own compliance program before relying on it.

Policy source (Rego)

package zapier.ingress.freeze_toolset

# Deny-by-default: only the explicit allow rules below permit the request.
default allow := false

# IdP group exempted from the freeze. PLACEHOLDER — map to your tenant's real
# IdP group name at import time.
admin_group := "automation-admins"

# The six self-modifying Zapier meta-tools (agentic mode). Each one lets the
# agent change its own capability set:
#   enable_zapier_action / auto_provision_mcp — add new actions to the toolset
#   write_code_action                         — create an arbitrary code-execution action
#   create/update/delete_zapier_skill         — persist instructions future sessions auto-load
# Matched by suffix because the gateway prefixes tool names with the configured
# MCP server name (e.g. `zapier-mcp-enable_zapier_action`).
frozen_suffixes := [
    "enable_zapier_action",
    "auto_provision_mcp",
    "write_code_action",
    "create_zapier_skill",
    "update_zapier_skill",
    "delete_zapier_skill",
]

# The tool being called is one of the frozen self-modifying meta-tools.
# Note: `disable_zapier_action` does NOT end with `enable_zapier_action`
# (preceding character differs), so narrowing the toolset always passes.
is_frozen_tool if {
    name := lower(input.resource.name)
    some suffix in frozen_suffixes
    endswith(name, suffix)
}

# Caller is an automation admin. Fails closed: if `subject`, `claims`, or
# `groups` is missing (or `groups` is not an array), no membership is found
# and the caller is not exempt.
is_automation_admin if {
    claims := object.get(input.subject, "claims", {})
    groups := object.get(claims, "groups", [])
    some group in groups
    group == admin_group
}

# Pass through every tool that does not modify the toolset — including
# disable_zapier_action and all read/execute meta-tools.
allow if {
    not is_frozen_tool
}

# Automation admins may modify the toolset.
allow if {
    is_frozen_tool
    is_automation_admin
}

reasons contains "This Zapier connector's toolset is frozen: enabling actions, provisioning tools, code actions, and Zapier skill changes are restricted to automation admins. Ask an automation admin to provision the action out-of-band, then retry with your existing toolset. If you believe this is a false positive, contact your InfoSec team." if {
    is_frozen_tool
    not is_automation_admin
}

reason := joined if {
    count(reasons) > 0
    reason_list := sort([r | some r in reasons])
    joined := concat("; ", reason_list)
}

Canonical source: policy.md on GitHub · raw · raw on this site (.md)