Affected repo: stripe/ai, tools/python (shipped as stripe-agent-toolkit 0.7.0+)
Pinned commit: 3ca4090 (main, 2026-08-28)
Compared release: stripe-agent-toolkit 0.6.1 (PyPI sdist)
PUBLIC-READY
R05P2-M1 (policy-dialect fail-open), instance R06P2-F1
Honesty ledger: EXECUTED (PoC at pin, offline stub MCP), PROVED (code diff 0.6.1 vs pin), DERIVED (LangChain docs drift observed 2026-08-27)
Summary
The v0.6.x Python toolkit filtered its tool list through is_tool_allowed(tool, configuration) using the configuration["actions"] dialect (deny by default: a tool is exposed only if every action it declares is explicitly enabled). The v0.7.x MCP rewrite removed that filter entirely. Configuration is now a TypedDict(total=False) with only a context key, and nothing validates, warns about, or rejects unknown keys.
As a result, code written for 0.6.x that passes a least-privilege actions policy, plus any code copied from the live LangChain integration docs (which still show configuration={"actions": {"payment_links": {"create": True}}} at the time of writing), silently hands the agent every tool the Stripe API key permits, including create_invoice, send_invoice, create_refund, and create_payment_intent.
The only remaining bounds on the agent's payment calls are the key's server-side permissions and a non-fatal recommendation to use restricted keys. There is no amount bound, no payee allowlist, and no human gate anywhere in the package (zero occurrences of an approval mechanism in the Python source at the pin).
Steps to reproduce (offline, dummy key)
PoC: poc/f1_stripe_actions_dropped.py in the R06P2 workspace. It stubs StripeMcpClient._do_connect and call_tool with a rule-based fake that returns a fixed ten-tool list (the snake_case names documented in MIGRATION.md), so no network or real key is used.
- Construct the pinned toolkit with the exact 0.6.x dialect config:
{"actions": {"payment_links": {"create": True}}}
- Call
get_tools().
- Invoke the
create_refund tool with an agent-chosen amount.
Observed (deterministic across two runs, poc/out/f1_run1.txt equals f1_run2.txt):
- tools handed to the agent (10): create_customer, list_customers, create_product, create_price, create_payment_link, create_invoice, send_invoice, create_refund, create_payment_intent, list_balance
- warnings mentioning the removed
actions key: NONE
- direct
create_refund call with amount: 999999 reached the MCP layer and returned {"id": "fake_create_refund", "status": "succeeded"}
- reference: under 0.6.1
is_tool_allowed semantics the same config exposes exactly one tool, create_payment_link
Impact
A developer who upgrades from 0.6.x (or copies the current LangChain docs example) keeps a config they read as "payment link creation only", while the upgraded toolkit exposes all write and refund paths the API key allows to the LLM agent. Because the config dict is accepted without error or warning, the regression is invisible until an agent misuses a tool. This is a fail-open policy dialect transition: the policy layer did not tighten or error, it vanished.
Suggested fix
Any of the following, in rough order of value:
- Validate
configuration in ToolkitCore.__init__: reject or at least DeprecationWarning on unknown top-level keys, with a dedicated message when actions is present ("tool permissions moved to your Restricted API Key; this key is ignored").
- Emit the same warning from
create_stripe_agent_toolkit when actions is present.
- Offer an in-toolkit allowlist or denylist of MCP tool names so framework users can bound the toolset client-side again, and document it as the replacement for
actions.
- Coordinate with LangChain to update the integration docs page for StripeAgentToolkit to the v0.7.x API.
Notes
- Side observation at the pin: the sync path
StripeTool._run calls asyncio.get_event_loop() when no loop is running, which raises RuntimeError on Python 3.12; the async path works. Worth a separate bug report.
MIGRATION.md does document the actions removal for users who read it; the security issue is that the package accepts the old dialect silently and a major docs surface still teaches it.
Affected repo: stripe/ai, tools/python (shipped as
stripe-agent-toolkit0.7.0+)Pinned commit: 3ca4090 (main, 2026-08-28)
Compared release: stripe-agent-toolkit 0.6.1 (PyPI sdist)
PUBLIC-READY
R05P2-M1 (policy-dialect fail-open), instance R06P2-F1
Honesty ledger: EXECUTED (PoC at pin, offline stub MCP), PROVED (code diff 0.6.1 vs pin), DERIVED (LangChain docs drift observed 2026-08-27)
Summary
The v0.6.x Python toolkit filtered its tool list through
is_tool_allowed(tool, configuration)using theconfiguration["actions"]dialect (deny by default: a tool is exposed only if every action it declares is explicitly enabled). The v0.7.x MCP rewrite removed that filter entirely.Configurationis now aTypedDict(total=False)with only acontextkey, and nothing validates, warns about, or rejects unknown keys.As a result, code written for 0.6.x that passes a least-privilege
actionspolicy, plus any code copied from the live LangChain integration docs (which still showconfiguration={"actions": {"payment_links": {"create": True}}}at the time of writing), silently hands the agent every tool the Stripe API key permits, includingcreate_invoice,send_invoice,create_refund, andcreate_payment_intent.The only remaining bounds on the agent's payment calls are the key's server-side permissions and a non-fatal recommendation to use restricted keys. There is no amount bound, no payee allowlist, and no human gate anywhere in the package (zero occurrences of an approval mechanism in the Python source at the pin).
Steps to reproduce (offline, dummy key)
PoC:
poc/f1_stripe_actions_dropped.pyin the R06P2 workspace. It stubsStripeMcpClient._do_connectandcall_toolwith a rule-based fake that returns a fixed ten-tool list (the snake_case names documented in MIGRATION.md), so no network or real key is used.{"actions": {"payment_links": {"create": True}}}get_tools().create_refundtool with an agent-chosen amount.Observed (deterministic across two runs,
poc/out/f1_run1.txtequalsf1_run2.txt):actionskey: NONEcreate_refundcall withamount: 999999reached the MCP layer and returned{"id": "fake_create_refund", "status": "succeeded"}is_tool_allowedsemantics the same config exposes exactly one tool,create_payment_linkImpact
A developer who upgrades from 0.6.x (or copies the current LangChain docs example) keeps a config they read as "payment link creation only", while the upgraded toolkit exposes all write and refund paths the API key allows to the LLM agent. Because the config dict is accepted without error or warning, the regression is invisible until an agent misuses a tool. This is a fail-open policy dialect transition: the policy layer did not tighten or error, it vanished.
Suggested fix
Any of the following, in rough order of value:
configurationinToolkitCore.__init__: reject or at leastDeprecationWarningon unknown top-level keys, with a dedicated message whenactionsis present ("tool permissions moved to your Restricted API Key; this key is ignored").create_stripe_agent_toolkitwhenactionsis present.actions.Notes
StripeTool._runcallsasyncio.get_event_loop()when no loop is running, which raisesRuntimeErroron Python 3.12; the async path works. Worth a separate bug report.MIGRATION.mddoes document theactionsremoval for users who read it; the security issue is that the package accepts the old dialect silently and a major docs surface still teaches it.