Default-Deny Unknown Zapier Tools
Maintains an allowlist of audited Zapier tool-name suffixes and denies any tool call whose name does not match an allowlisted entry, with an alert-worthy…
- Direction
- ingress
- Rego package
zapier.ingress.default_deny_unknown_tools- App
- zapier
- Bundle
- soc2
- Published
- Minimum gateway
- 1.0.0b24
- Schema version
- 1.0.0
- Checksum
sha256:02eac99cb206dca8825fa27847b1bc7ec63475983995675b08e593b2a6d74b17
zapierdefault-deny-unknown-toolsallowlistingresssoc2
What this policy does
Direction: ingress (tool_pre_invoke)
Default: deny — only allowlisted tool names pass
Package: zapier.ingress.default_deny_unknown_tools
What it does
Maintains an allowlist of audited Zapier tool-name suffixes and denies any tool call whose name does not match an allowlisted entry, with an alert-worthy reason that tells the operator to treat the deny as upstream drift. Everything not explicitly audited is blocked before it reaches the Zapier MCP server. A missing or empty tool name also fails closed.
This posture is mandatory for Zapier rather than optional hardening. Zapier MCP runs in one of two mutually exclusive modes per server, and the two modes produce disjoint, independently drifting tool namespaces:
- Agentic mode (dynamic tool discovery — Zapier's default): 15 static
meta-tools (
execute_zapier_read_action,execute_zapier_write_action, the action-management, skill, config, and feedback tools). The names are fixed, but the meta-tools proxy 40,000+ actions across 9,000+ apps. - Classic mode (manual configuration): one per-account
<app>_<action>tool for each action the server owner enabled at mcp.zapier.com (e.g.gmail_send_email,slack_send_message). This set changes silently whenever an action is added, renamed, or removed — Zapier can add tools to a running server with no client-visible change.
A blocklist can never keep up with a tool surface the upstream account defines; only an allowlist pinned to what you have actually audited can. With this policy attached, a renamed or newly enabled Zapier action appearing mid-session becomes a hard deny surfaced for review instead of an implicitly-trusted new capability.
Pin the allowlist to YOUR server mode at import time
The shipped allowed_tool_suffixes array is the agentic-mode default:
the 15 verified meta-tool names from Zapier's own documentation. That is the
correct pin only for servers running dynamic tool discovery.
Classic-mode operators must replace the array with their own enabled
<app>_<action> inventory, pinned per tenant. Each Zapier MCP server is
per-account: export the list of actions you enabled at mcp.zapier.com and
pin the allowlist to exactly that audited set. There is no canonical
classic-mode inventory this policy could ship — your enabled actions are
yours alone. After pinning, any action later enabled upstream (by an owner,
a teammate, or Zapier itself) is denied until you re-audit and add it, which
is the point: the audited surface stays fixed even though the upstream one
does not.
Compliance alignment
- SOC 2 CC6.1 — supports logical access security over protected assets: through the single Zapier connector the agent can only reach capabilities that were explicitly reviewed and enumerated.
- SOC 2 CC6.6 — supports boundary protection: tools added upstream at mcp.zapier.com do not become reachable through the gateway boundary without an explicit allowlist change.
- SOC 2 CC6.8 — supports prevention of unauthorized software: new upstream actions and renamed tool variants are unauthorized-by-default on the agent path.
- SOC 2 CC7.2 / CC7.3 — deny decisions from this policy surface tool drift (new/renamed upstream tools) as observable gateway events that feed anomaly monitoring and event evaluation.
- GDPR Art. 25 — supports data protection by design and by default on the agent channel: the default state of any new Zapier-proxied, data-bearing tool is "inaccessible until audited."
Tool name matching
The DTwo gateway prefixes tool names with the configured MCP server name as
<server-name>-<tool-name> (e.g. zapier-mcp-execute_zapier_read_action),
and that prefix is not standardized across deployments. The policy therefore
matches case-insensitively on lower(input.resource.name) in two ways:
- Exact match against an allowlisted suffix (covers unprefixed names), or
- Suffix match requiring the
-separator — the name must end with-<suffix>. Requiring the separator stops an unaudited tool whose name merely ends with an allowlisted string (e.g. a classic-mode tool namedgmail_execute_zapier_write_actionends withexecute_zapier_write_actionbut is underscore-glued, not a gateway prefix) from riding through on suffix matching.
Both branches first require the raw (pre-lowercase) tool name to consist
only of the ASCII set real tool names use — [A-Za-z0-9._-]. This is
checked before lower() runs, which closes a Unicode case-folding evasion:
lower() folds a handful of non-ASCII code points onto ASCII letters (e.g.
the Kelvin sign U+212A → k), so without the guard a tool registered as
send_feedbac<U+212A> would fold to the allowlisted send_feedback and
pass — even though it is a visibly different, un-audited name. A name
containing any character outside that ASCII set is denied.
The shipped allowlist is the 15 agentic-mode meta-tools verified from
Zapier's documentation: execute_zapier_read_action,
execute_zapier_write_action, list_enabled_zapier_actions,
discover_zapier_actions, enable_zapier_action, disable_zapier_action,
auto_provision_mcp, write_code_action, get_configuration_url,
list_zapier_skills, get_zapier_skill, create_zapier_skill,
update_zapier_skill, delete_zapier_skill, send_feedback.
Verify the exact names your gateway sends with the dump-input debug technique before relying on this in production.
Argument shape
This policy only inspects the tool name (input.resource.name). It
reads no arguments, so it is insensitive to argument-shape differences
between modes. Missing resource or resource.name resolves to "" via
object.get, which matches nothing — the call is denied (fail closed). A
malformed resource that is not an object at all (a string or array) makes
the name lookup undefined, which also lands on the default deny — still
with the surfaced reason. A
non-string name (null, number, object, or array — a malformed or
hostile request) is coerced to "" rather than passed to lower(); without
that guard lower() would raise a built-in type error that leaves allow
and the deny reason undefined, so the call would deny without a surfaced
reason. With the guard it is a clean, reasoned deny. A string name
containing any character outside [A-Za-z0-9._-] (non-ASCII letters,
whitespace, control characters) likewise never reaches the allow branches
and is denied.
Examples
Allowed
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "zapier-mcp-execute_zapier_read_action", "type": "tool" },
"payload": {
"name": "zapier-mcp-execute_zapier_read_action",
"args": { "instructions": "Find my three most recent Gmail messages" }
}
}
}
allow = true, no reason.
Denied
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "zapier-mcp-gmail_send_email", "type": "tool" },
"payload": {
"name": "zapier-mcp-gmail_send_email",
"args": { "instructions": "Email the Q3 forecast to my manager" }
}
}
}
allow = false,
reason = "This Zapier tool is not on the audited allowlist pinned for this gateway (...)".
Composition
This policy is the outer gate — it decides which tool names exist for agents. It deliberately does not constrain how the allowlisted tools are used; pair it with companion Zapier policies:
- A toolset-freeze ingress policy that denies (or group-gates) the
self-modifying meta-tools —
enable_zapier_action,auto_provision_mcp,write_code_action, and the skill-write tools. The shipped allowlist keeps their names reachable because they are part of the verified agentic surface; a freeze policy controls who may actually call them. - A read-only posture policy denying
execute_zapier_write_action(and, classic mode,send_/create_/update_/delete_suffixes) for all but an approved group — one rule fences every write across 9,000 apps. - An instructions-as-content policy: every Zapier tool accepts an
instructionsstring that Zapier's server-side AI uses to fill unspecified fields (recipients, bodies, record IDs), so name- and structured-field-level controls alone are bypassable by construction. - An egress PII redaction policy on
execute_zapier_read_actionand classic*_find_*/*_get_*responses — aggregator reads return raw app data with no source-app DLP.
Known limitations
- Agentic mode hides drift inside arguments. The 15 meta-tool names
never change, but
enable_zapier_actionandauto_provision_mcpwiden whatexecute_zapier_read_action/execute_zapier_write_actioncan reach without any new tool name appearing. Name pinning cannot see that — in agentic mode this policy fixes the name surface, not the capability surface. Pair with a toolset-freeze policy (see Composition) or remove the self-expansion suffixes from the allowlist. - The shipped allowlist is agentic-mode only. A classic-mode server
behind this policy as shipped will have every tool denied, because
classic
<app>_<action>names are not on the list. That is fail-closed by design, but it means classic-mode pinning (see "Pin the allowlist") is a required deployment step, not a tuning step. - Classic-mode names are per-account and mostly unverified. Only a
handful of classic tool names (
gmail_send_email,slack_send_message,google_sheets_create_row,notion_create_page,google_calendar_create_event,quickbooks_online_find_customer) were verifiable from public third-party docs; the full inventory is defined by each account. Treat any other name as unverified until observed on your live server via the dump-input technique. - Name-based trust only. The policy audits tool names, not behavior. An upstream change that repurposes an allowlisted name for different behavior bypasses the intent while matching the letter. Re-audit when Zapier ships mode or meta-tool changes.
- Suffix matching trusts the
<server-name>-prefix convention. A tool literally named<anything>-send_feedback(separator included) would match thesend_feedbackentry even though it is a different tool — and<anything>includes the empty string, so a name that is just-send_feedback(leading separator, no prefix) also matches, even though no real gateway produces an empty server name. Exact-name pinning (replace suffix entries with full gateway names) closes both forms if your deployment needs it. - The allowlist applies to every ingress hook, not just tool calls. The
policy checks only
input.resource.name— it does not scope toinput.action == "tool_pre_invoke"orresource.type == "tool". On a pipeline that also carriesprompt_pre_fetch/resource_pre_fetchhooks, every prompt or resource fetch is denied (with this policy's tool-drift reason) unless its name coincidentally matches an allowlisted suffix — in which case it is allowed. Both directions are safe for the Zapier surface (Zapier MCP exposes tools only), but attach this policy to a Zapier-dedicated pipeline, or add aninput.actionguard, if your gateway serves prompts or resources you care about. - ASCII-only tool names. Matching requires the raw tool name to be
[A-Za-z0-9._-]— the shape all verified Zapier tool names and typical gateway server-name prefixes take. This is deliberate (it blocks Unicode case-fold spoofing), but a deployment whose configured MCP server name contains other characters (spaces,@,/, non-ASCII) would see even its legitimate tools denied; rename the server to an ASCII slug, or relax the character class, if so. - No identity-based exemptions. All callers face the same allowlist. If
you need a break-glass group that can call unaudited tools, add a
separate
allow ifbranch gated oninput.subject.claimsgroups.
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.default_deny_unknown_tools
# Deny-by-default: a tool call is allowed only via allowlist membership below.
# A missing or empty tool name matches nothing and is therefore denied.
default allow := false
# Audited Zapier tool-name suffixes — AGENTIC-MODE DEFAULT: the 15 verified
# meta-tools from Zapier's dynamic-tool-discovery mode (docs.zapier.com/mcp).
# Classic-mode operators: REPLACE this array with your tenant's enabled
# `<app>_<action>` inventory from mcp.zapier.com (see the policy description —
# per-tenant pinning is a required deployment step in classic mode).
allowed_tool_suffixes := [
# Execution funnel — read/write proxies for every enabled action
"execute_zapier_read_action",
"execute_zapier_write_action",
# Action management — enable/auto_provision/write_code are SELF-MODIFYING
# (the agent widens its own toolset); keep them listed only if a companion
# toolset-freeze policy gates who may call them (see Composition)
"list_enabled_zapier_actions",
"discover_zapier_actions",
"enable_zapier_action",
"disable_zapier_action",
"auto_provision_mcp",
"write_code_action",
# Config
"get_configuration_url",
# Skills — create/update persist instructions future sessions auto-load
# (prompt-injection persistence vector); gate with a companion policy
"list_zapier_skills",
"get_zapier_skill",
"create_zapier_skill",
"update_zapier_skill",
"delete_zapier_skill",
# Feedback
"send_feedback",
]
# Raw tool name straight from the request. Missing resource/name resolves to ""
# via object.get and matches nothing (fail closed).
raw_tool_name := object.get(object.get(input, "resource", {}), "name", "")
# Tool name, lowercased. A non-string name (null, number, object, array — a
# malformed or hostile request) is coerced to "" instead of being handed to
# lower(), which would raise a built-in type error and leave `allow`/`reason`
# undefined. Coercing keeps the decision a clean, reasoned deny (fail closed).
tool_name := lower(raw_tool_name) if is_string(raw_tool_name)
tool_name := "" if not is_string(raw_tool_name)
# Character-class guard on the RAW (pre-lowercase) name. Real Zapier tool names
# (agentic meta-tools and classic `<app>_<action>` tools) and gateway
# `<server-name>-` prefixes use only ASCII letters, digits, underscore, dot,
# and the `-` separator. Checking the raw name BEFORE lower() closes a Unicode
# case-folding evasion: lower() folds some non-ASCII code points onto ASCII
# letters (e.g. the Kelvin sign U+212A -> "k"), so a tool registered as
# `send_feedbac<U+212A>` would otherwise fold to the allowlisted
# `send_feedback` and slip through the default-deny gate despite being a
# visibly different, un-audited name. Guarded by is_string so a non-string
# name still yields a clean, reasoned deny (no built-in type error).
raw_name_is_plain_ascii if {
is_string(raw_tool_name)
regex.match(`^[A-Za-z0-9._-]+$`, raw_tool_name)
}
# Exact match — covers deployments where the gateway sends the bare tool name.
allow if {
raw_name_is_plain_ascii
some suffix in allowed_tool_suffixes
tool_name == suffix
}
# Prefixed match — the DTwo gateway names tools `<server-name>-<tool-name>`.
# Requiring the `-` separator before the suffix stops unaudited tools whose
# names merely end with an allowlisted string (e.g. a classic-mode
# `gmail_execute_zapier_write_action` is underscore-glued, not a gateway
# prefix) from slipping through.
allow if {
raw_name_is_plain_ascii
some suffix in allowed_tool_suffixes
endswith(tool_name, sprintf("-%s", [suffix]))
}
reason := "This Zapier tool is not on the audited allowlist pinned for this gateway, so it is denied by default. Zapier can add, rename, or newly enable tools on a running server with no client-visible change — treat this deny as a drift alert and report it to your security team for review. If the tool is expected, ask a gateway admin to audit it and add its name suffix to the allowlist." if not allow Canonical source: policy.md on GitHub · raw · raw on this site (.md)
Related policies
Airtable: Redact PII in Record Reads
Scans the responses of the Airtable record-read tools — the calls that return row fields values — and rewrites high-confidence PII shapes to a fixed…
Asana: Redact PII in Task & Comment Reads
On the Asana MCP read path, this transform scans the free-text business fields that ride back in task, comment/story, and status-update responses — notes,…
BigQuery: Redact PII in Query Results
Scans the content returned by BigQuery's result-returning tools and rewrites high-confidence PII shapes to fixed, non-recoverable redaction tokens before the…
Block Agent Email to External Recipients
Blocks agent-initiated Microsoft 365 email sends when any recipient address falls outside a corporate-domain allowlist.
Block BigQuery Exfiltration and Cross-Project Writes
Inspects the raw GoogleSQL string carried by BigQuery SQL tools and denies any statement that moves data out of the tenant's own project — even when the call…
bigqueryguard-warehouse-exportingresssqlexfiltrationsoc2pci-dssgdpr-ccpa
Block Bulk Export & External Staging (Snowflake)
Blocks Snowflake SQL-execution tool calls whose query text moves whole tables off the Snowflake perimeter — bulk export to cloud storage or a stage, and…
snowflakeguard-warehouse-sqlexportexfiltrationingresssoc2pci-dssgdpr-ccpa