Read-Only Stripe by Default (Role-Gate Billing Writes)
Establishes a read-only-by-default Stripe posture over the MCP path. The named write and destructive billing tools —
- Direction
- ingress
- Rego package
stripe.ingress.role_gate_writes- App
- stripe
- Bundles
- soc2pci-dsssoxgdpr-ccpa
- Published
- Minimum gateway
- 1.0.0b24
- Schema version
- 1.0.0
- Checksum
sha256:39f42427691ac49396db200c521de055b6091fe155b8d33c1e0b8ea83e20125b
striperole-gate-writesingressleast-privilegerbacsoc2pci-dsssoxgdpr-ccpa
What this policy does
Direction: ingress (tool_pre_invoke)
Default: deny gated billing writes unless the caller is in an allowed IdP group; allow everything else
Package: stripe.ingress.role_gate_writes
What it does
Establishes a read-only-by-default Stripe posture over the MCP path. The named write and destructive billing tools —
*create_customer,*create_product,*create_price,*create_payment_link*create_invoice,*create_invoice_item,*finalize_invoice,*create_coupon*update_subscription,*update_dispute,*cancel_subscription
— are denied unless the caller's IdP groups claim includes finance or
billing-admin. Read, search, and fetch tools (*list_*, *search_stripe_resources,
*fetch_stripe_resources, *stripe_api_read, *retrieve_balance,
*get_stripe_account_info, documentation/search tools) always pass — they never match
the gated suffix list, so the blocklist design leaves them untouched.
Missing identity fails closed for the gate: if the gateway populates no subject,
no claims, or no groups claim, the caller is not in an allowed group and the
write is denied. Reads remain available to everyone regardless of claims.
Refunds (*create_refund) and the *stripe_api_write passthrough are intentionally
out of scope — they are governed by the dedicated refund-cap and escape-hatch
companion policies (see Composition). This keeps the policy to the single job of
gating ordinary billing writes.
Compliance alignment
- SOC 2 CC6.1 — supports logical access security over a protected financial system by restricting who can mutate Stripe billing objects through the agent channel; CC6.3 — supports role-based access and least privilege: writes are tied to named IdP groups, read-only is the default for everyone else; CC6.2 — because the gate reads live IdP claims per call, deprovisioning a user from the
financegroup revokes agent write access at the next token; PI1.2 — supports input authorization: billing inputs enter Stripe only from authorized roles. - PCI DSS 7.2.1 / 7.2.2 — supports a least-privilege access model over the cardholder-adjacent billing environment: access to modify customers, invoices, subscriptions, and disputes is limited to job-classified roles; 7.2.5 — supports least privilege for application/system accounts by narrowing what the agent's broad OAuth grant or restricted key can actually be used for on the MCP path.
- SOX ITGC — access to programs and data — supports least-privilege access to a financial system that feeds revenue records (invoices, subscriptions, coupons, disputes); SoD (COSO Principle 10) — supports initiate/approve separation by keeping billing mutations out of non-finance hands.
- GDPR Art. 25 — supports data protection by design/default on the agent channel: creating customer objects (name, email — PII) requires an authorized role; Art. 29 / 32(4) — supports processing only on the controller's instructions: unauthorized actors (including a prompt-injected agent acting for a non-finance user) cannot mutate personal or billing data; Art. 5(1)(b) — supports purpose limitation; CCPA §1798.100(e) — supports reasonable security procedures over consumers' personal information.
Tool name matching
Matching is by suffix on lower(input.resource.name) — the DTwo gateway prefixes
tool names with the configured MCP server name (e.g. stripe-mcp-create_invoice),
and that prefix is not standardized. Suffix matching keeps the policy portable across:
- Official legacy
@stripe/mcp(≤ v0.8.x)verb_resourcenames — all eleven gated names verified from thestripe/airepo history (the Claude Desktop.dxtmanifest still ships them). - Community
noun_verbnames (atharvagupta2003/mcp-stripestyle, e.g.customer_create) — the inverted forms of the same eleven tools are also gated. Onlycustomer_createis verified from that repo; the other inversions are defensive and unverified (see Known limitations). - Official current server (mcp.stripe.com) — its per-resource write surface is
collapsed into
stripe_api_write, which this policy deliberately does not match (see Composition). Its read/meta tools never match the gated suffixes and pass.
Verify the exact names your gateway sends with the dump-input debug technique before relying on this in production.
Argument shape
None. The decision is made purely from the tool name and the caller's identity
(input.subject.claims.groups); input.payload.args is never inspected. The
tool name is read from both input.resource.name and input.payload.name,
and the gate fires if either ends with a gated suffix — so a call that carries
the tool name only in the payload (a missing/empty resource.name) is still
gated rather than falling through to the read-only allow branch. The groups
claim is expected to be an array of strings (the common IdP shape); group
comparison is case-insensitive. Any other shape (single string, CSV, object,
null) fails closed — the caller is treated as not in an allowed group.
Examples
Allowed — read tool, no identity needed
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "stripe-mcp-list_customers", "type": "tool" },
"payload": { "name": "stripe-mcp-list_customers", "args": { "limit": 10 } }
}
}
allow = true, no reason.
Allowed — billing write from a finance-group member
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "stripe-mcp-create_invoice", "type": "tool" },
"subject": {
"sub": "google-apps|ap@example.com",
"claims": { "groups": ["finance", "employees"] }
},
"payload": {
"name": "stripe-mcp-create_invoice",
"args": { "customer": "cus_123", "days_until_due": 30 }
}
}
}
allow = true, no reason.
Denied — billing write without an allowed group
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "stripe-mcp-create_customer", "type": "tool" },
"subject": {
"sub": "google-apps|dev@example.com",
"claims": { "groups": ["engineering"] }
},
"payload": {
"name": "stripe-mcp-create_customer",
"args": { "name": "Jane Doe", "email": "jane@example.com" }
}
}
}
allow = false, reason = "Stripe billing writes (creating or updating customers, products, prices, payment links, invoices, coupons, subscriptions, or disputes) are restricted to the finance and billing-admin groups — your account is not in either group. Read and search tools remain available. Ask a billing administrator to make this change, or contact your InfoSec team if you believe you should have write access."
Composition
This policy is single-purpose: it gates ordinary billing writes by role. Pair it with:
- Refund cap (
apps/stripe, PF-09 family) — governs*create_refund/*refund_create: amount ceilings and deny-on-full-refund. Refunds move money and deserve their own thresholds, not just a group gate. - Escape-hatch deny (
apps/stripe, PF-22 family) — governs*stripe_api_write, the current official server's generic POST/PATCH/PUT/DELETE passthrough. Without that companion, a caller on the current server bypasses this policy's gate entirely. - Dispute-submit gate (PF-15 family) —
update_disputewithsubmit: truefiles evidence irreversibly with the card network; a human-approval policy can strip or denysubmiteven for finance-group members. - Egress PII redaction / PAN masking (PF-02 / PF-01 families) — the read tools this policy leaves open are bulk PII egress channels (customer lists with emails).
Known limitations
- Group names are placeholders — replace
financeandbilling-adminwith your IdP's group names at import time. If your IdP emits roles under a different claim (e.g. Auth0permissions, or a namespaced claim likehttps://acme.com/groups), change the claim key incaller_groups. - No claims → no writes. If the gateway's
jwt_audienceis misconfigured or the IdP omits thegroupsclaim, every gated write is denied for everyone. That is the intended fail-closed direction, but verify claims withdtwo-list-claimsor the dump-input technique before rollout. groupsmust be an array. A string-valued claim ("finance") or CSV ("finance,hr") does not iterate and fails closed. Adaptcaller_groupsif your IdP emits a non-array shape.- Unverified community suffixes. Of the
noun_verbinversions, onlycustomer_createis verified from the community server's source; the other ten are defensive guesses. Over-matching on the deny side fails safe, but a community tool with a different name (e.g. a hypotheticalinvoice_send) would not be gated. - Enumerated allowlist, not a
create_*/update_*wildcard. The gate matches a fixed list of eleven known write/destructive tools (plus their community inversions), not everycreate_*/update_*tool. This is deliberate — a broad*create_*wildcard would also catchcreate_refund, which is intentionally delegated to the refund-cap companion. The trade-off: money-relevant writes that exist under other names are not gated here. In particular the communityatharvagupta2003/mcp-stripeserver exposespayment_intent_create(and, by the samenoun_verbconvention, plausiblycharge_create/payout_create/transfer_create/topup_create); none of these are on the list, so an unauthorized caller can invoke them through this policy. Charges, payment intents, payouts, and transfers are money movement — gate them with the PF-09gate-money-movementcompanion (and RAK scoping), not this role gate. On the official current server the same operations arrive asstripe_api_write(see below), which this policy also does not match. - Not a complete write freeze.
*stripe_api_write,*create_refund, andstripe_report(whose report creation is a write) pass through this policy by design — deploy the companion policies above for full coverage. Composio'sSTRIPE_*catalog (~415 auto-generated tools) and Stripe's unpublished Treasury "agentic finance" preview tools use different names and are not covered by these suffixes. - MCP path only. The Stripe dashboard, direct API keys, and webhooks are outside the gateway's reach — layer this policy with Restricted API Key (RAK) scoping rather than treating either as sufficient alone.
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 stripe.ingress.role_gate_writes
# Read-only Stripe by default: deny the gated billing writes below unless the
# caller's IdP groups include an allowed billing role. Everything else passes.
default allow := false
# Ordinary billing write / destructive tool suffixes to gate.
# The gateway prefixes tool names with the configured MCP server name
# (e.g. `stripe-mcp-create_invoice`), so we match on the suffix.
# NOTE: `*create_refund` and `*stripe_api_write` are intentionally absent —
# they are governed by the dedicated refund-cap and escape-hatch policies.
gated_write_suffixes := [
# Official legacy @stripe/mcp (<= v0.8.x) verb_resource names — verified
# from the stripe/ai repo history and the Claude Desktop .dxt manifest.
"create_customer",
"create_product",
"create_price",
"create_payment_link",
"create_invoice",
"create_invoice_item",
"finalize_invoice",
"create_coupon",
"update_subscription",
"update_dispute",
"cancel_subscription",
# Community noun_verb inversions (atharvagupta2003/mcp-stripe style).
# Only customer_create is verified from that repo's source; the rest are
# defensive inversions — over-matching on the deny side fails safe.
"customer_create",
"product_create",
"price_create",
"payment_link_create",
"invoice_create",
"invoice_item_create",
"invoice_finalize",
"coupon_create",
"subscription_update",
"dispute_update",
"subscription_cancel",
]
# IdP groups permitted to perform billing writes. PLACEHOLDERS — replace with
# your IdP's group names at import time. Compared case-insensitively.
allowed_groups := {"finance", "billing-admin"}
# All tool-name fields this call carries. The gateway normally populates
# resource.name; we also consider payload.name so the gate still fires when
# resource.name is absent — a gated write is never allowed merely because the
# name field the policy reads happened to be empty (fail-safe).
candidate_tool_names := {name |
some raw in [
object.get(object.get(input, "resource", {}), "name", ""),
object.get(object.get(input, "payload", {}), "name", ""),
]
name := lower(raw)
}
# The call targets one of the gated billing write tools (matched on either the
# resource name or the payload name).
is_gated_write if {
some name in candidate_tool_names
some suffix in gated_write_suffixes
endswith(name, suffix)
}
# Caller's IdP groups. Missing subject/claims/groups resolves to [] — the
# membership check below then never fires, so the gate fails closed.
caller_groups := object.get(object.get(object.get(input, "subject", {}), "claims", {}), "groups", [])
# Caller is in at least one allowed billing group (case-insensitive).
caller_in_allowed_group if {
some group in caller_groups
allowed_groups[lower(group)]
}
# Reads, searches, fetches, and anything else not on the gated list pass.
allow if {
not is_gated_write
}
# Gated billing writes require an allowed IdP group.
allow if {
is_gated_write
caller_in_allowed_group
}
reasons contains "Stripe billing writes (creating or updating customers, products, prices, payment links, invoices, coupons, subscriptions, or disputes) are restricted to the finance and billing-admin groups — your account is not in either group. Read and search tools remain available. Ask a billing administrator to make this change, or contact your InfoSec team if you believe you should have write access." if {
is_gated_write
not caller_in_allowed_group
}
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)
Used in these guides
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