dtwo Policy Store

Role-Gate All Zapier Writes

Zapier MCP is an aggregator: one connector proxies actions across 9,000+ apps, and every create/update/delete/send funnels through a small, predictable…

Direction
ingress
Rego package
zapier.ingress.role_gate_writes
App
zapier
Bundles
soc2gdpr-ccpa
Published
Minimum gateway
1.0.0b24
Schema version
1.0.0
Checksum
sha256:0657060c12f34db60b571c536cbb01c3687297054dda09015fc9d7c175e8aece

zapierrole-gate-writesingresssoc2gdpr-ccpa

What this policy does

Direction: ingress (tool_pre_invoke) Default: deny writes for non-approved groups, allow reads for everyone Package: zapier.ingress.role_gate_writes

What it does

Zapier MCP is an aggregator: one connector proxies actions across 9,000+ apps, and every create/update/delete/send funnels through a small, predictable naming surface. In agentic (dynamic tool discovery) mode, all writes go through a single meta-tool, execute_zapier_write_action — a Gmail delete and a Salesforce record update both arrive as the same tool name. In classic (manual configuration) mode, each enabled action is its own <app>_<action> tool whose name carries a write verb (send_, create_, update_, delete_, remove_) — e.g. gmail_send_email, google_sheets_create_row.

This policy denies all of them unless the caller's IdP groups claim includes automation-writers, while reads (execute_zapier_read_action, discovery/list meta-tools, and classic find_/get_ tools) stay open to everyone. The result is a read-only-by-default posture for the whole aggregator: one rule fences writes across every proxied app.

Missing identity claims fail closed: a caller with no groups claim (or no claims at all) is not an approved writer and is denied all writes — reads remain available.

Compliance alignment

  • SOC 2 CC6.1 / CC6.3 — supports logical access security and role-based least privilege: write access to every app behind the Zapier connector is granted only to an authorized IdP group, evaluated per call. CC6.2 — supports credential de-provisioning effect: removal from the IdP group revokes write access on the next call, with no per-app work.
  • PCI DSS 7.2.1 / 7.2.2 — supports a least-privilege access model over the aggregator's reach into cardholder-adjacent apps (payment, invoicing, commerce actions all transit the same write funnel). 7.2.5 — supports least privilege for the application account: the broad per-app OAuth grants held Zapier-side are narrowed to read-only on the MCP path for non-writers.
  • HIPAA §164.308(a)(4) — supports information access management for connectors that can reach PHI-bearing apps; §164.502(b)/§164.514(d) — supports minimum-necessary, role-based limits (reads only, unless the role warrants writes); §164.312(a)(1) — supports access control decided on per-call, per-user identity. §164.308(a)(3) — supports workforce-security termination effect via live IdP claims.
  • GDPR Art. 25 — supports data protection by default on the agent channel: the aggregator's default capability is read-only. Art. 29 / 32(4) — supports processing only on the controller's instructions: agents acting for unapproved users cannot mutate personal data in downstream processors. CCPA §1798.100(e) — supports reasonable security over consumer data reachable through the connector.
  • SOX (ITGC — access to programs and data) — supports least-privilege access to financial systems reachable through Zapier (accounting, billing, ERP actions); SoD (COSO P10) — supports initiate/approve separation by keeping record-mutation ability out of unapproved hands.

Tool name matching

Case-insensitive, on the tool-name suffix (the DTwo gateway prefixes tool names with the configured MCP server name, e.g. zapier-mcp-execute_zapier_write_action, and that prefix is not standardized — suffix matching keeps the policy portable):

  • Agentic write funnel — name ends with execute_zapier_write_action (exact meta-tool name, verified against Zapier's official MCP docs).
  • Classic writes — name contains a write-verb substring: send_, create_, update_, delete_, remove_.

Everything else passes: execute_zapier_read_action, list_enabled_zapier_actions, discover_zapier_actions, list_zapier_skills, get_zapier_skill, get_configuration_url, and classic find_/get_ tools (e.g. quickbooks_online_find_customer).

Note the verb-substring rule also catches the agentic skill meta-tools (create_zapier_skill, update_zapier_skill, delete_zapier_skill) and send_feedback — intentional, since all four are writes (skill changes persist instructions future sessions auto-load). See Known limitations for the composition consequence.

Verify the exact names your gateway sends with the dump-input debug technique before relying on this in production.

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 tools (including the underdocumented execute_zapier_*_action envelope).

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 funnel, any caller (no claims needed)

{
  "input": {
    "action": "tool_pre_invoke",
    "resource": { "name": "zapier-mcp-execute_zapier_read_action", "type": "tool" },
    "subject": { "sub": "auth0|analyst", "claims": {} },
    "payload": { "name": "zapier-mcp-execute_zapier_read_action", "args": {} }
  }
}

allow = true, no reason.

Denied — non-writer hits the agentic write funnel

{
  "input": {
    "action": "tool_pre_invoke",
    "resource": { "name": "zapier-mcp-execute_zapier_write_action", "type": "tool" },
    "subject": { "sub": "auth0|analyst", "claims": { "groups": ["engineering"] } },
    "payload": { "name": "zapier-mcp-execute_zapier_write_action", "args": { "instructions": "email the report to the team" } }
  }
}

allow = false, reason = "Zapier write actions are restricted (...)".

Allowed — approved writer sends via a classic-mode tool

{
  "input": {
    "action": "tool_pre_invoke",
    "resource": { "name": "zapier-mcp-gmail_send_email", "type": "tool" },
    "subject": { "sub": "auth0|ops", "claims": { "groups": ["engineering", "automation-writers"] } },
    "payload": { "name": "zapier-mcp-gmail_send_email", "args": { "to": "team@example.com", "subject": "report" } }
  }
}

allow = true, no reason.

Composition

This policy fences who may write; it does not constrain what a permitted write contains or which apps it reaches. Useful companions:

  • zapier/freeze-toolset — stops the agent from widening its own toolset (enable_zapier_action, auto_provision_mcp, write_code_action, skill persistence); this policy assumes the toolset is what admins configured.
  • zapier/guard-external-send — content controls on the writes that approved writers do make.
  • zapier/default-deny-unknown-tools — classic mode's tool inventory is per-account; an allowlist catches write actions whose names use a verb this policy does not list.
  • zapier/mask-pan-egress — reads stay open under this policy, so pair with egress redaction for what those reads return.

Known limitations

  • Group name is a placeholder. Replace automation-writers 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 all writes — including would-be writers.
  • 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 every caller is denied writes (fail closed). Adjust is_approved_writer if your IdP uses a non-array shape.
  • The verb list is not exhaustive. Classic-mode action names are per-account and only a handful are verified from public docs; a write action whose name carries a different verb (e.g. add_, post_, move_, archive_) would pass as a "read". Extend write_verbs for your inventory, or pair with default-deny-unknown-tools so unlisted names are denied by default.
  • Agentic write meta-tools overlap freeze-toolset. create_/update_/delete_zapier_skill match the verb rule, so when both policies are attached, changing skills requires being in both the admin and writer groups. The toolset self-modification meta-tools — enable_zapier_action, disable_zapier_action, auto_provision_mcp, and write_code_action — carry no listed verb and are not gated here; they are writes-of-capability, not writes-of-data, and are out of this policy's scope by design. Attach freeze-toolset to cover them (note freeze-toolset's own candidate list omits disable_zapier_action, so confirm it enumerates every self-modification tool your server exposes).
  • Unidentifiable calls fail closed. A request whose resource.name is missing, null, or an empty string cannot be classified as a read or a write, so it does not match the read-open branch and falls through to default allow := false (denied). Real gateway traffic always carries a tool name; this guard only removes a fail-open corner and never blocks a legitimately named read.
  • Verb matching sees the whole prefixed name. A gateway server name containing a verb substring (e.g. a server named zapier-send_prod) would classify every tool on it as a write. Name servers without send_/create_/update_/delete_/remove_ substrings.
  • Reads are open by design. execute_zapier_read_action still proxies reads across every enabled app, and Zapier's server-side AI resolves instructions after the gateway passes the call. This policy is a write fence, not a data-access control — compose with egress redaction and app-scoping policies for read-side exposure.
  • Tool names verified against Zapier's official MCP docs (docs.zapier.com, mid-2026) for the agentic meta-tools; classic-mode examples (gmail_send_email, google_sheets_create_row, quickbooks_online_find_customer) come from third-party client docs — treat other classic names as unverified until observed on a live server.

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.role_gate_writes

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

# IdP group approved to perform Zapier writes. PLACEHOLDER — map to your
# tenant's real IdP group name at import time.
writer_group := "automation-writers"

# Classic-mode write verbs. Zapier classic (manual configuration) tools are
# named `<app>_<action>` (e.g. gmail_send_email, google_sheets_create_row);
# a name carrying one of these substrings is treated as a write. The list is
# intentionally conservative — extend it for per-account action inventories
# that use other verbs (add_, post_, ...), or pair with a
# default-deny-unknown-tools allowlist to catch what this misses.
write_verbs := [
    "send_",    # externally visible sends (email, chat, social posts)
    "create_",  # record/row/page/event creation
    "update_",  # record mutation
    "delete_",  # destructive; Zapier offers no undo
    "remove_",  # destructive; Zapier offers no undo
]

# Agentic mode: every write across 9,000+ proxied apps funnels through this
# single meta-tool. Matched by suffix because the gateway prefixes tool names
# with the configured MCP server name
# (e.g. `zapier-mcp-execute_zapier_write_action`).
is_agentic_write if {
    endswith(lower(input.resource.name), "execute_zapier_write_action")
}

# Classic mode: per-action tools whose name carries a write verb. This also
# intentionally catches the agentic skill meta-tools (create_/update_/
# delete_zapier_skill) and send_feedback — all of them are writes.
is_classic_write if {
    name := lower(input.resource.name)
    some verb in write_verbs
    contains(name, verb)
}

is_write_tool if {
    is_agentic_write
}

is_write_tool if {
    is_classic_write
}

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

# A resolvable, non-empty tool name is required before anything is treated as
# a read. Without this guard a call whose `resource.name` is missing, null, or
# empty would make `is_write_tool` undefined/false and sail through the
# read-open branch below — a fail-open on tool identity. Requiring the name to
# be a non-empty string makes an unidentifiable call fall through to
# `default allow := false` (fail closed).
has_tool_name if {
    is_string(input.resource.name)
    input.resource.name != ""
}

# Reads stay open to everyone: execute_zapier_read_action, discovery/list
# meta-tools, and classic find_/get_ tools carry no write verb.
allow if {
    has_tool_name
    not is_write_tool
}

# Approved writers may write.
allow if {
    is_write_tool
    is_approved_writer
}

reasons contains "Zapier write actions are restricted to members of the automation-writers group; read actions remain available to everyone. Ask your IdP administrator to add you to automation-writers if your role requires write access through this connector. If you believe this is a false positive, contact your InfoSec team." if {
    is_write_tool
    not is_approved_writer
}

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)