Deny Stripe API-Write Escape Hatch
Denies the stripe api write meta-tool — the single raw passthrough on the official Stripe MCP server that can execute any Stripe POST, PATCH, PUT, or DELETE…
- Direction
- ingress
- Rego package
stripe.ingress.deny_escape_hatches_api_write- App
- stripe
- Bundles
- soxsoc2
- Published
- Minimum gateway
- 1.0.0b24
- Schema version
- 1.0.0
- Checksum
sha256:d3c004edbb6c5f65a9f8185b2372061a12e018d9d1340d79663eb5539eecfa37
stripedeny-escape-hatchesingresssoxsoc2
What this policy does
Direction: ingress (tool_pre_invoke)
Default: deny
Package: stripe.ingress.deny_escape_hatches_api_write
What it does
Denies the stripe_api_write meta-tool — the single raw passthrough on the
official Stripe MCP server that can execute any Stripe POST, PATCH,
PUT, or DELETE method (payouts, transfers, account mutations, refunds,
subscription changes) and would otherwise bypass every named-tool policy —
in two layers:
- Role gate: callers whose IdP
groupsclaim does not includefinanceorbilling-admincannot use the passthrough at all. - Endpoint hard stop: even for those groups, the call is denied when
the serialized argument object contains a money-movement or account
token —
payouts,transfers,topups,financial_connections, Connectaccounts, or any connected-account id (acct_…). The account tokens are bare (not/v1/accounts) so they catch account mutation and Connect money routing regardless of how the passthrough names the field (a path string, aresourcekey, or atransfer_data.destination/on_behalf_ofid).
All other tools pass through unchanged. The check runs at ingress, so a blocked call never reaches Stripe and no side effect occurs.
Compliance alignment
- PCI DSS 7.2.1 / 7.2.2 — supports the least-privilege access model: the raw write passthrough is a privileged channel into the payment account, and this policy restricts it to defined roles with defined endpoint limits. 7.2.5 — supports least privilege for the agent's application account by narrowing what its Stripe grant can reach over MCP.
- SOC 2 CC6.1 — supports logical access security over protected assets; CC6.3 — supports role-based access and least privilege on the one tool that collapses Stripe's entire write surface into a single name.
- SOX ITGC (access to programs & data) — supports least-privilege access to a financial system's write path; Rule 13a-15(f)(3) — supports safeguarding of assets by blocking payout, transfer, top-up, and account-mutation endpoints outright on the agent channel.
Why ingress and not egress
stripe_api_write executes irreversible, externally visible writes — a
payout that has left the balance cannot be recalled by redacting the
response. Ingress denial is the only placement that actually prevents the
action.
Tool name matching
Matches by suffix on lower(input.resource.name):
*stripe_api_write— the official server's passthrough (verified from docs.stripe.com/mcp)*api_write— broader stem for renamed deployments that keep the suffix
The DTwo gateway prefixes tool names with the configured MCP server name
(e.g. stripe-mcp-stripe_api_write), and that prefix is not standardized —
suffix matching keeps the policy portable. Verify the exact name your
gateway sends with the dump-input debug technique before relying on this
in production.
Argument shape
The exact argument field names of stripe_api_write (e.g. path vs
method vs params) are not published and are unverified — the
landscape research could not capture a live schema without an authenticated
tools/list. The policy therefore does not index any specific key: it
serializes the whole of input.payload.args with json.marshal and
matches the endpoint tokens case-insensitively on lower(...) as
substrings. This makes the endpoint hard stop hold regardless of the real
key shape, including tokens nested arbitrarily deep in the argument object.
Examples
Allowed — finance caller, non-money endpoint
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "stripe-mcp-stripe_api_write", "type": "tool" },
"subject": { "sub": "auth0|cfo", "claims": { "groups": ["finance"] } },
"payload": {
"name": "stripe-mcp-stripe_api_write",
"args": { "path": "/v1/customers", "method": "POST", "params": { "name": "Acme" } }
}
}
}
allow = true, no reason.
Denied — caller outside finance/billing-admin
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "stripe-mcp-stripe_api_write", "type": "tool" },
"subject": { "sub": "auth0|dev", "claims": { "groups": ["engineering"] } },
"payload": {
"name": "stripe-mcp-stripe_api_write",
"args": { "path": "/v1/customers", "method": "POST" }
}
}
}
allow = false, reason directs the caller to the dedicated named tools.
Denied — money-movement endpoint, even for finance
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "stripe-mcp-stripe_api_write", "type": "tool" },
"subject": { "sub": "auth0|cfo", "claims": { "groups": ["finance"] } },
"payload": {
"name": "stripe-mcp-stripe_api_write",
"args": { "path": "/v1/payouts", "method": "POST", "params": { "amount": 500000 } }
}
}
}
allow = false for everyone — money movement goes through the dedicated
named tools or the Stripe dashboard.
Composition
This policy closes the passthrough so it cannot reach a surface the named- tool policies restrict. Pair it with its Stripe companions from the same family set:
- the refund-cap policy (PF-09), which bounds
*create_refundamounts — without this policy,stripe_api_writecould issue an uncapped refund directly against/v1/refunds; - the role-gate / read-only policy (PF-12), which restricts the named
write tools (
*create_*,*update_*,*cancel_subscription, …) to the same finance groups.
Layer all of them with Stripe Restricted API Key (RAK) scoping — DTwo policy and key scoping are complementary control planes, not either/or.
Known limitations
- Group names are placeholders — replace
financeandbilling-adminwith your IdP's group names at import time. Missing or malformedgroupsclaims fail closed (the caller is treated as unprivileged). - Unverified argument schema. The endpoint tokens are matched as substrings of the serialized argument object because the passthrough's field names are unverified. Capture a live schema from your gateway and tighten the match to the real endpoint key if you need fewer false positives.
- Substring false positives. A privileged caller writing a benign value
that merely mentions a token (e.g. a description string containing
"payouts", or any argument carrying a connected-account
acct_…id) is denied. On Connect platforms theaccounts/acct_tokens will deny a broad range of connected-account operations — this is the deliberate fail-closed trade-off for the account hard stop; the deny reason carries an escalation hint. - Parameter-level money movement not fully covered. The hard stop keys on
endpoint/account tokens, not on every money-moving parameter. A Connect
charge that routes money via
transfer_data.destinationoron_behalf_ofis caught because those values carry anacct_…id, but a direct-chargeapplication_fee_amountwith no account reference carries none of the tokens and passes the endpoint check (the group gate still applies). Money movement that must name a destination account is covered; fee-only parameters on non-money endpoints are a residual — pair with RAK scoping. - Missing args pass the endpoint check. A privileged caller invoking
the passthrough with no arguments at all serializes to
{}and passes the endpoint hard stop (the group gate still applies). Such a call carries no endpoint and fails at the Stripe API anyway. - Obfuscation residual. Endpoint strings encoded (base64, URL-escaped, split across fields) would not match the tokens — though such values would also not be valid Stripe method identifiers. RAK scoping is the backstop control plane.
- Scope. Legacy per-resource write tools (
create_refund,update_subscription, …) are governed by the companion role-gate and refund-cap policies, not this one. Stripe Treasury preview tool names are unpublished/unverified and are not covered. Composio'sSTRIPE_*action slugs do not share theapi_writesuffix and are out of scope.
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.deny_escape_hatches_api_write
# Deny-by-default: only the explicit allow rules below permit the request.
default allow := false
# IdP groups permitted to touch the raw API-write passthrough at all.
# Placeholders — replace with your IdP's group names at import time.
allowed_groups := {"finance", "billing-admin"}
# Endpoint tokens that indicate money movement or account mutation. Matched
# case-insensitively as substrings of the serialized argument object because
# the passthrough's argument field names are unverified (see Known
# limitations in the description).
blocked_endpoint_tokens := [
# /v1/payouts — money out of the Stripe balance to an external account
"payouts",
# /v1/transfers — Connect money movement between accounts
"transfers",
# /v1/topups — funding the Stripe balance from a bank account
"topups",
# accounts — Connect account creation/mutation/deletion. Bare token (NOT
# "/v1/accounts") so it matches whatever shape the passthrough uses to
# express the endpoint (a "/v1/accounts" path, a "resource": "accounts"
# key, external_accounts/bank_accounts sub-resources). "/v1/accounts" alone
# would only catch the full-path form and miss the others.
"accounts",
# acct_ — any reference to a connected-account id (destination charges,
# transfer_data.destination, on_behalf_of, direct account updates) is
# Connect money-movement / account-mutation surface expressed by id rather
# than by endpoint path.
"acct_",
# /v1/financial_connections — linked bank-account sessions and data
"financial_connections",
]
# Tool name, lowercased; empty string when the resource block is absent.
tool_name := lower(object.get(object.get(input, "resource", {}), "name", ""))
# The official Stripe server's raw write passthrough. Suffix match keeps the
# policy portable across gateway server-name prefixes. Verify the exact name
# your gateway sends with the dump-input debug technique.
is_api_write_tool if {
endswith(tool_name, "stripe_api_write")
}
# Broader stem for renamed deployments that keep the api_write suffix.
is_api_write_tool if {
endswith(tool_name, "api_write")
}
# Caller belongs to a group allowed to use the passthrough. Missing or
# malformed claims fail closed: no groups -> not privileged.
caller_is_privileged if {
claims := object.get(object.get(input, "subject", {}), "claims", {})
groups := object.get(claims, "groups", [])
some g in groups
lower(g) in allowed_groups
}
# Serialize the whole argument object so the endpoint check holds regardless
# of the passthrough's (unverified) argument key shape.
serialized_args := lower(json.marshal(object.get(object.get(input, "payload", {}), "args", {})))
args_reference_blocked_endpoint if {
some token in blocked_endpoint_tokens
contains(serialized_args, token)
}
# Any tool other than the API-write passthrough is out of this policy's scope.
allow if {
not is_api_write_tool
}
# The passthrough is allowed only for privileged callers, and never toward
# money-movement or account endpoints.
allow if {
is_api_write_tool
caller_is_privileged
not args_reference_blocked_endpoint
}
reasons contains "The Stripe API-write passthrough can execute any Stripe write and is restricted to the finance and billing-admin groups. Use the dedicated named Stripe tools for routine changes, or ask your Stripe administrator for access. Contact your InfoSec team if this was a false positive." if {
is_api_write_tool
not caller_is_privileged
}
reasons contains "This Stripe API-write call references a money-movement or account endpoint (payouts, transfers, topups, /v1/accounts, financial_connections), which is blocked for every caller on the agent channel. Use the dedicated named Stripe tools or the Stripe dashboard for money movement. Contact your InfoSec team if this was a false positive." if {
is_api_write_tool
args_reference_blocked_endpoint
}
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