Módulo 5: Credential And Secrets Security

2. Cómo n8n guarda las credenciales

Descripción

Al terminar esta lección vas a poder explicar, con precisión de operador, qué es una credencial de n8n por dentro: dónde vive físicamente, qué parte de ella viaja en el JSON de un workflow y qué parte no sale nunca, y qué significa exactamente que esté cifrada en reposo. Vas a conocer el modelo de dos llaves que n8n usa hoy —una llave de instancia que protege a una llave de datos, y por qué esa separación es lo que hace posible rotar el cifrado sin desastres—. Y vas a enfrentar el punto donde este modelo se rompe si te descuidas: qué se ve y qué no se ve cuando exportas tus credenciales, incluida la opción del comando de exportación que convierte un respaldo rutinario en el archivo más peligroso del servidor.

Esto importa porque casi todas las decisiones de las lecciones siguientes dependen de entender bien este modelo. Cuándo puedes compartir un archivo sin preocuparte y cuándo no. Por qué una credencial no se puede copiar de una instancia a otra sin más. Por qué el nodo Code no necesita —ni debe— ver un token. Por qué un respaldo de la base de datos sin la llave es una caja fuerte sin combinación. Todo eso sale del modelo de almacenamiento, y quien no lo tiene claro toma decisiones por intuición, que en seguridad es una mala consejera.

Conexión con el módulo: la lección 1 estableció que la credencial es el activo y levantó el inventario de Terra Market. Esta lección abre la credencial y mira lo que hay adentro, que es lo que te permite razonar sobre todo lo demás. La lección 3 va a operar sobre el contenido de esa credencial —los permisos que lleva—; la 4 y la 5 van a cambiar de dónde sale su valor; la 6 va a decidir quién puede verla; y la 8 va a verificar que todo esto quedó bien. Una frontera importante desde ya: el montaje de la llave de cifrado —generarla, colocarla, respaldarla, qué pasa si se pierde— está desarrollado a fondo en la guía de Self-Hosting y Operaciones, módulo 3, lección 5, y no lo vamos a repetir. Aquí damos por hecho que la llave está bien puesta y profundizamos en el modelo: cómo se guarda una credencial, qué se cifra con qué, y qué sale en un export.

La caja de seguridad y el recibo

Empecemos por la imagen, porque es la que separa las dos cosas que todo el mundo confunde.

Cuando alguien renta una caja de seguridad en un banco, se lleva a casa dos cosas muy distintas. Se lleva un recibo con el número de la caja, el nombre del titular y la sucursal. Y deja en el banco el contenido de la caja: lo que de verdad importa.

El recibo se puede fotocopiar, dejar sobre la mesa, mandar por correo. No abre nada. Dice "existe una caja número 412 a nombre de Ana en la sucursal centro", y eso, por sí solo, no le sirve a nadie para llevarse nada. El contenido, en cambio, no sale del banco jamás; para llegar a él hay que ir físicamente, identificarse, y usar la llave.

Una credencial de n8n funciona igual, y esta es la distinción central de toda la lección:

  • La referencia es el recibo: el identificador y el nombre de la credencial. Vive dentro del JSON del workflow, viaja en cada export, se versiona en el repositorio, aparece en cada copia. Y no abre nada.
  • El valor es el contenido de la caja: la llave literal del ERP, la contraseña, el token. Vive solo dentro de la instancia de n8n, cifrado, y no sale en el JSON del workflow ni en ninguna copia de él.

Veámoslo con order-sync de Terra Market. Si abres el JSON de ese workflow y buscas el nodo que escribe en el ERP, encuentras algo con esta forma:

{
  "name": "Push order to ERP",
  "type": "n8n-nodes-base.httpRequest",
  "parameters": {
    "url": "https://erp.terramarket.example/api/v1/orders",
    "method": "POST",
    "authentication": "genericCredentialType",
    "genericAuthType": "httpHeaderAuth"
  },
  "credentials": {
    "httpHeaderAuth": {
      "id": "17",
      "name": "erp_api"
    }
  }
}

Lee con atención el bloque credentials. Dice tres cosas: que este nodo se autentica con una credencial de tipo httpHeaderAuth, que su identificador interno es 17, y que se llama erp_api. No dice cuál es la llave. No aparece ninguna cadena que abra el ERP. Ese JSON se puede subir a un repositorio, compartir con un compañero o pegar en un ticket sin filtrar el secreto —siempre y cuando el resto del workflow tampoco lo tenga escrito, que es una condición aparte y a la que volveremos—.

La regla que sale de aquí, y que conviene grabar:

La referencia viaja; el valor se queda. El workflow dice cuál credencial usar, no cuál es la credencial.

Anatomía de una credencial

Ahora abramos la caja. Una credencial de n8n, vista como el objeto que la instancia guarda, tiene cuatro partes:

1. El identificador. Un número o cadena corta que n8n asigna al crearla (17 en nuestro ejemplo). Es lo que usa el motor para encontrarla. No es secreto.

2. El nombre. El texto que tú le pones: erp_api, carrier_api, llm_token. Sirve para que los humanos la reconozcan en la lista. Tampoco es secreto —y por eso conviene que el nombre sea descriptivo y no incluya, por ejemplo, un fragmento de la llave, cosa que la gente hace más de lo que uno creería—.

3. El tipo. Qué clase de autenticación es: httpHeaderAuth, oAuth2Api, smtp, y así. El tipo determina qué campos tiene la credencial. Una de tipo Header Auth pide un nombre de cabecera y un valor; una OAuth2 pide identificador de cliente, secreto de cliente y direcciones de autorización; una SMTP pide servidor, puerto, usuario y contraseña. Tampoco es secreto.

4. Los datos. Aquí está el secreto: el contenido de esos campos. La llave literal, la contraseña, el token, el secreto de cliente. Esta es la única parte que se cifra, y es la única que no sale nunca en el JSON de un workflow.

Piénsalo como una ficha de biblioteca: número, título y categoría están a la vista de cualquiera en el catálogo; lo que está bajo llave es el contenido. Los tres primeros campos son metadatos —información sobre la credencial—; el cuarto es la credencial misma.

Esta anatomía explica un comportamiento de la interfaz que a veces confunde. Cuando abres una credencial ya guardada en n8n, ves su nombre y su tipo con normalidad, pero los campos secretos aparecen enmascarados o vacíos: no te los muestra de vuelta. No es un error ni un descuido de la interfaz. Es coherente con el modelo: los metadatos son legibles, los datos están cifrados y n8n evita mostrarlos. Si necesitas cambiar el valor, lo escribes de nuevo; no lo lees para copiarlo.

Qué significa "cifrado en reposo", con manzanas

Vale la pena definir este término bien, porque se usa mucho y se entiende poco.

Cifrar es transformar un dato legible en un revoltijo ilegible mediante una operación matemática que usa una llave. La operación es reversible solo si tienes la llave: con ella, el revoltijo vuelve a ser el dato original; sin ella, es basura. La palabra clave es reversible con llave: no es que el dato se pierda, es que queda encerrado.

En reposo significa "mientras está guardado", en contraste con en tránsito, que significa "mientras viaja por la red". Son dos protecciones distintas y complementarias:

  • En tránsito lo resuelve HTTPS: mientras la llave viaja de tu navegador al servidor de n8n, o del servidor de n8n al ERP, va cifrada por el canal. Eso es el candado del navegador, y es materia de la guía de Self-Hosting y Operaciones, módulo 4.
  • En reposo lo resuelve el cifrado de credenciales de n8n: mientras la llave está guardada en la base de datos, está cifrada. Eso es esta lección.

Piénsalo como el correo. Meter una carta en un sobre sellado para que el cartero no la lea es protección en tránsito. Guardar esa misma carta en una caja con candado cuando llega a tu casa es protección en reposo. Un sobre sellado no protege una carta que después dejas abierta sobre la mesa, y un candado no protege una carta que mandaste en una postal. Hacen falta las dos, y ninguna sustituye a la otra.

¿Por qué importa tanto el cifrado en reposo en el caso concreto de n8n? Por dos escenarios muy realistas:

Escenario 1: alguien obtiene una copia de la base de datos. Puede ser un respaldo que terminó en un lugar equivocado, un disco que se dio de baja sin borrar, o un acceso indebido al servidor de Postgres. Sin cifrado en reposo, esa copia contendría las llaves de Terra Market en texto plano: el ERP, el transportista, el proveedor de IA, todo. Con cifrado en reposo, contiene revoltijos inútiles sin la llave de cifrado.

Escenario 2: alguien con acceso legítimo a la base de datos no debería ver las llaves. El administrador de la base de datos de Terra Market necesita hacer su trabajo —respaldos, índices, mantenimiento— y para eso necesita acceso. Pero su trabajo no incluye conocer la llave del ERP. El cifrado en reposo separa esas dos cosas: puede administrar la base sin poder leer los secretos que guarda.

Ese segundo escenario es el que suele sorprender, y es una idea que vale la pena tener presente todo el módulo: la seguridad no solo protege de gente malintencionada de afuera; también separa responsabilidades entre gente legítima de adentro. No es desconfianza; es higiene organizativa. Que el administrador de la base no pueda leer la llave del ERP le quita un problema de encima, además de quitártelo a ti.

El modelo de dos llaves

Aquí viene la parte que profundiza de verdad, y que hace falta conocer porque cambia cómo se piensa la rotación.

Podrías imaginar que n8n cifra cada credencial directamente con la N8N_ENCRYPTION_KEY y ya. Esa fue la idea original y sigue siendo el modelo mental básico —y es exactamente el que enseña la guía de Self-Hosting y Operaciones cuando te explica cómo montar la llave—. Pero tiene un problema práctico enorme, y conviene verlo para entender la solución.

El problema: si todas las credenciales están cifradas directamente con la llave de instancia, cambiar esa llave significa descifrar y volver a cifrar absolutamente todo, y mientras tanto la instancia está en un estado a medio camino. Cualquier interrupción en ese proceso deja credenciales cifradas con una llave y otras con otra. Es una operación de corazón abierto, y por eso durante mucho tiempo el consejo fue simplemente "fija la llave al principio y no la cambies nunca".

La solución que n8n implementa hoy es un patrón clásico en criptografía: cifrado en sobres, o dos niveles de llave. Funciona así:

  • La llave de instancia (N8N_ENCRYPTION_KEY) es la llave maestra. Según la documentación, se fija al desplegar y no cambia. Su único trabajo es proteger a la otra llave.
  • La llave de datos (data encryption key) es la que de verdad cifra el contenido de tus credenciales. n8n la guarda en la base de datos, ella misma cifrada con la llave de instancia. Esta es la que se rota.

La analogía que lo aclara: imagina una caja fuerte grande en la oficina. Adentro no guardas los documentos directamente; guardas el llavero de todos los archiveros del piso. Los documentos están en los archiveros, cerrados con las llaves del llavero. Si quieres cambiar las cerraduras de los archiveros, no tienes que abrir ni tocar la caja fuerte grande: cambias las llaves del llavero, y la caja fuerte sigue exactamente igual. La caja fuerte grande —la llave de instancia— es la que nunca cambia, porque su trabajo es proteger el llavero, no los documentos.

¿Qué te compra esto como operador? Poder rotar el cifrado de tus credenciales periódicamente sin tocar la llave de instancia y sin la operación de corazón abierto. Es la diferencia entre "la llave de cifrado se pone una vez y se reza" y "el cifrado de los datos se renueva cada tanto, como se renueva una contraseña".

La rotación, con sus advertencias

Según la documentación oficial, la rotación de llaves de cifrado es una función de instancias self-hosted, la habilita el dueño de la instancia, y se activa con una variable de entorno:

# .env — habilita la rotación de llaves de cifrado.
# Va en TODAS las instancias: el proceso principal y todos los workers.
N8N_ENV_FEAT_ENCRYPTION_KEY_ROTATION=true

El flujo, tal como lo describe la documentación:

  1. Confirmar que todas las instancias —proceso principal y workers— comparten el mismo valor de N8N_ENCRYPTION_KEY. Esto ya lo sabes del modo cola: es la condición para que un worker pueda descifrar lo que el principal cifró.
  2. Poner la variable en todas ellas y reiniciar. n8n genera y guarda la llave de datos inicial por sí mismo.
  3. Verificar en Settings > Data Encryption Keys que la función quedó activa.
  4. Rotar cuando corresponda: desde esa misma pantalla con el botón de rotar, o con una llamada POST al endpoint /encryption/keys de la API.

Y ahora las advertencias, que la documentación marca con claridad y que conviene repetir porque son de las que no perdonan:

Habilitar la rotación es un cambio de una sola dirección: no hay vuelta atrás. La documentación es explícita en pedir un respaldo completo de la base de datos antes de habilitarla.

Después de habilitarla, no desactives la variable ni bajes de versión de n8n. Una vez que hay datos escritos en el formato nuevo, quitar la función o retroceder de versión deja esos datos permanentemente inaccesibles.

Léelo dos veces. "Permanentemente inaccesible" en criptografía no es una exageración de manual: significa que las credenciales se vuelven basura ilegible y hay que recrearlas una por una entrando a cada sistema externo. Para Terra Market, con seis credenciales, sería una tarde perdida y varias conversaciones incómodas. Para una instancia con cincuenta, es una semana.

La conclusión práctica para un operador: la rotación es una buena función y vale la pena tenerla, pero se habilita con respaldo verificado y en una ventana planeada, no un martes por la tarde porque se te ocurrió. Y como todo lo que cambia entre versiones, confirma el nombre de la variable y el flujo en la documentación de tu versión antes de ejecutarlo; aquí te damos el modelo, no la receta de tu instalación.

Ejemplo trabajado: qué se ve en un export de credenciales

Vamos al punto donde el modelo se toca con las manos, y donde más gente se lleva una sorpresa.

n8n tiene comandos de línea para exportar e importar credenciales. Se usan para respaldos y para mover credenciales entre instancias. Vamos a exportarlas de dos formas distintas y a mirar el resultado, porque la diferencia entre las dos es la diferencia entre un archivo inocuo y uno peligroso.

Recuerda: estos comandos los corres tú en tu servidor; la guía no ejecuta nada.

Paso 1 — El export normal. Este es el que exporta las credenciales tal como están guardadas, es decir, cifradas:

# Exporta TODAS las credenciales de la instancia a un archivo.
n8n export:credentials --all --output=backups/credentials.json

Desmenucemos el comando, porque cada pieza importa. n8n export:credentials es el subcomando de exportación de credenciales. --all significa "todas las de la instancia", en contraste con --id=<ID>, que exporta una sola. --output= (o su forma corta -o) indica el archivo o directorio de destino. Hay dos banderas más que vale la pena conocer: --separate, que escribe un archivo por credencial en vez de uno solo, y --backup, que es un atajo cómodo —según la documentación equivale a --all --pretty --separate— pensado justamente para respaldos.

Qué esperar. Un archivo JSON donde cada credencial aparece con sus metadatos legibles y sus datos como un revoltijo cifrado. Algo con esta forma:

[
  {
    "id": "17",
    "name": "erp_api",
    "type": "httpHeaderAuth",
    "data": "U2FsdGVkX1+9pQ3nK7bT2mX4yR8vC1dW0aH6sL5eZ3fN8gJ2..."
  },
  {
    "id": "18",
    "name": "carrier_api",
    "type": "httpHeaderAuth",
    "data": "U2FsdGVkX1/kM2xP0wR6nB9tY4uV7cD3fG8hJ1aS5eL0qZ..."
  }
]

Fíjate en la fila de erp_api. El nombre está en claro. El tipo está en claro. Y el campo data —donde vive la llave del ERP— es una cadena que no significa nada para quien la lea. Ese archivo, si se te cae en el lugar equivocado, es incómodo pero no catastrófico: sin la llave de cifrado de la instancia, nadie lo abre.

Paso 2 — El export descifrado, y su advertencia. Existe una bandera más:

# ⚠️ PELIGRO: exporta las credenciales en TEXTO PLANO.
n8n export:credentials --all --decrypted --output=backups/decrypted.json

--decrypted, según la documentación, "exporta las credenciales en formato de texto plano", y la propia documentación acompaña esa bandera con una advertencia directa: toda la información sensible queda visible en los archivos.

Qué esperar. El mismo archivo, con la misma estructura, pero con el campo data legible:

[
  {
    "id": "17",
    "name": "erp_api",
    "type": "httpHeaderAuth",
    "data": {
      "name": "X-ERP-Token",
      "value": "erp_live_7f4a2c9b0e1d8a5c3b6f9e2d4a7c1b8e"
    }
  }
]

Ahí está la llave del ERP de Terra Market, en texto, en un archivo del servidor. Ese archivo no es un respaldo: es la copia física de tu llavero completo. Vale exactamente lo mismo que todas tus credenciales juntas, y tiene una propiedad que ellas no tienen: no está cifrado, así que no necesita ninguna llave para abrirse.

Vale la pena ser preciso sobre cuándo --decrypted es legítimo, porque la bandera existe por razones reales y decir "nunca la uses" sería impreciso. Se justifica en dos situaciones: migrar credenciales a una instancia con una llave de cifrado distinta —el export cifrado no sirve ahí, porque la instancia destino no puede descifrarlo— y recuperar un valor que se perdió en otro lado. Fuera de esos casos, el export cifrado es el que quieres.

Y si la usas, tres reglas que no son opcionales:

  1. El archivo se borra apenas cumplió su propósito. No se queda "por si acaso". Un --decrypted que sobrevive a la tarea para la que se creó es una fuga esperando a ocurrir.
  2. El archivo nunca toca un repositorio, ni un servicio de archivos compartido, ni un chat. Si tienes que moverlo a otra máquina, va por un canal cifrado y directo, no por el camino cómodo.
  3. El directorio de salida no está dentro de la carpeta del proyecto. Este descuido es más común de lo que parece: alguien exporta a ./backups/ dentro de la carpeta que está versionada, y el archivo entra en el siguiente git add .. Si esa carpeta está en el repositorio, el secreto también.

Paso 3 — La verificación que cierra el ejercicio. Abre los dos archivos y compáralos. Es un ejercicio de dos minutos y deja una impresión que no se olvida:

# Mira el primero (cifrado): los datos son ilegibles.
head -20 backups/credentials.json

# Mira el segundo (descifrado): los datos están a la vista.
head -20 backups/decrypted.json

Qué esperar: en el primero vas a ver cadenas sin sentido en el campo data; en el segundo vas a ver tus llaves reales. Ver la diferencia con tus propios ojos hace por tu criterio de operador más que cualquier advertencia escrita. Después de eso, borra el archivo descifrado. Ese es el paso final del ejercicio, y sí, es parte del ejercicio.

Lo que la llave de cifrado NO protege

Vamos con la parte del modelo que evita falsas tranquilidades, porque la pregunta "¿está todo cifrado?" tiene una respuesta que es no.

La llave de cifrado protege los datos de las credenciales. Punto. No protege:

Los workflows. La lógica de order-sync, inventory-update y shipment-notify se guarda legible en la base de datos. No es un descuido: la lógica de un workflow no es un secreto en sí misma, y n8n necesita leerla constantemente para ejecutarla. Lo que sí conviene notar es la consecuencia: si alguien obtiene tu base de datos, obtiene el mapa completo de tu operación —qué sistemas conectas, con qué endpoints, con qué reglas de negocio—, aunque no obtenga las llaves. Es información valiosa por sí sola.

Los datos de ejecución. Aquí está el agujero que más gente ignora, y merece un párrafo propio.

Cada vez que un workflow corre, n8n guarda el registro de esa ejecución: qué entró y qué salió de cada nodo. Con 4.000 ejecuciones diarias, Terra Market acumula una montaña de esos registros. Y esos registros no están cifrados con la llave de credenciales.

¿Por qué importa? Porque si tu workflow escribe un secreto dentro de un item —por ejemplo, un nodo Code que construye un campo auth_header: "Bearer erp_live_7f4a..."—, ese secreto queda guardado en texto en el historial de ejecuciones, visible para cualquiera que pueda abrir esa ejecución en el editor. Lo protegiste en la credencial y lo volviste a filtrar por la puerta de atrás.

Esta es la razón profunda de una regla que vas a ver repetida en la lección 5 y en la 7, y que ahora puedes justificar tú mismo:

El secreto no debe pasar por los datos. Se queda en la credencial, lo usa el nodo que hace la llamada, y nunca aparece en un item. Un secreto que toca un item deja de estar cifrado en reposo, aunque su credencial sí lo esté.

Vale la pena señalar que n8n tiene hoy una función para reducir este riesgo —la redacción de datos de ejecución, que oculta la entrada y la salida de los nodos y conserva solo los metadatos como el estado y los tiempos—, pero según la documentación está disponible en los planes Enterprise (self-hosted y Cloud) y a partir de versiones recientes. La vamos a ver con más detalle en la lección 6, junto con el resto de la honestidad de planes. Para una instancia Community como la de Terra Market, la defensa no es una función: es la disciplina de que el secreto nunca entre a un item.

Tu cuenta de dueño y la lista de usuarios. Los datos de las cuentas se guardan con las protecciones habituales de una aplicación (las contraseñas no se guardan legibles), pero eso es un mecanismo distinto del cifrado de credenciales. No lo mezcles en tu modelo mental.

Para tenerlo de un vistazo:

Qué¿Lo cifra la llave de credenciales?Consecuencia práctica
Datos de una credencial (llave, contraseña, token)Un respaldo de la base sin la llave no revela secretos
Nombre, tipo e identificador de la credencialNoUn export muestra qué credenciales existen, no qué valen
Lógica de los workflowsNoQuien tenga la base tiene el mapa de tu operación
Historial de ejecuciones (entradas y salidas)NoUn secreto escrito en un item queda en texto
Cuentas de usuarioOtro mecanismoNo lo mezcles con el cifrado de credenciales

Errores comunes

Creer que el JSON de un workflow contiene los secretos (conceptual, y frena trabajo real). Qué pasa: alguien se niega a subir workflows a un repositorio o a compartirlos con un compañero "porque tienen las llaves adentro". El equipo pierde el versionado y la colaboración por un miedo mal dirigido. Por qué pasa: es una precaución razonable en abstracto, y nadie abrió el JSON para comprobarlo. Cómo detectarlo: abre el JSON de un workflow y busca el bloque credentials de un nodo. Si ves solo id y name, es esto. Cómo corregirlo: el JSON lleva la referencia, no el valor; se puede versionar. Lo que sí hay que revisar antes de compartir es que ningún nodo tenga un secreto escrito a mano en un campo de parámetros —una cabecera puesta manualmente, una URL con un token adentro—, porque eso sí viaja en el JSON. La regla es "confía en el bloque credentials, desconfía de los campos que alguien llenó a mano".

Guardar el export descifrado "por si acaso" (práctico, y grave). Qué pasa: alguien hace un --decrypted para una migración, la migración sale bien, y el archivo se queda en backups/ del servidor durante dos años. Por qué pasa: borrar un respaldo se siente contraintuitivo; el instinto dice que los respaldos se conservan. Cómo detectarlo: busca en tu servidor archivos JSON de credenciales y ábrelos; si el campo data es legible, encontraste uno. Cómo corregirlo: el export descifrado no es un respaldo, es una copia sin cifrar de tu llavero. Para respaldar, usa el export cifrado junto con la llave de cifrado guardada aparte —esa pareja sí es un respaldo—. El descifrado se crea para una tarea concreta y se borra cuando la tarea termina. Y si encuentras uno viejo, además de borrarlo, considera esas credenciales como potencialmente expuestas y rótalas.

Respaldar la base de datos y creer que eso respalda las credenciales (conceptual, y clásico). Qué pasa: el equipo tiene respaldos diarios de Postgres y se siente cubierto. Un día restauran en un servidor nuevo y las credenciales no se descifran. Por qué pasa: el respaldo de la base contiene las credenciales cifradas; sin la llave de cifrado, no valen nada. Es la caja fuerte sin combinación. Cómo detectarlo: pregúntate dónde está la llave de cifrado de tu instancia y si sobreviviría a la pérdida del servidor. Si la única copia está en el .env de la máquina que respaldas, no tienes respaldo de la llave. Cómo corregirlo: la llave se respalda aparte de la base de datos, en un gestor de contraseñas o el lugar seguro que use tu equipo. El procedimiento completo está en la guía de Self-Hosting y Operaciones, módulo 3, lección 5; aquí basta con que entiendas por qué: base y llave son dos mitades, y un respaldo que solo tiene una no restaura nada.

Escribir el secreto en un item "solo para depurar" (práctico, y el que anula el cifrado). Qué pasa: alguien pone temporalmente el token en un campo de salida de un nodo Code para verificar que llegó bien, ve que funciona, y se olvida de quitarlo. El token queda en el historial de ejecuciones de cada corrida. Por qué pasa: es la forma más rápida de depurar, y en el momento se siente inocuo porque "es solo para ver". Cómo detectarlo: abre una ejecución reciente y recorre las salidas de los nodos buscando cadenas que parezcan llaves; si tu workflow toca un secreto, revisa especialmente los nodos Code y los Edit Fields. Cómo corregirlo: nunca escribas un secreto en un item, ni temporalmente. Si necesitas verificar que una credencial funciona, mira si la llamada tuvo éxito, no si el token llegó. Y si ya pasó, no basta con quitar el campo: hay que borrar las ejecuciones afectadas y, si el secreto pudo ser visto, rotarlo.

Habilitar la rotación de llaves sin respaldo y sin ventana (práctico, y sin vuelta atrás). Qué pasa: alguien lee sobre la rotación de llaves de cifrado, le parece buena idea, pone la variable un martes por la tarde y reinicia. Todo funciona… hasta que alguien baja de versión por otro motivo y los datos quedan inaccesibles. Por qué pasa: la variable es fácil de poner y el efecto inmediato es invisible, así que no se siente como un cambio grande. Cómo detectarlo: si en tu instancia está N8N_ENV_FEAT_ENCRYPTION_KEY_ROTATION=true y nadie recuerda cuándo se puso ni si se respaldó antes, es esto. Cómo corregirlo: trátalo como lo que la documentación dice que es —un cambio de una sola dirección—: respaldo completo de la base verificado, ventana planeada, la variable en todas las instancias (principal y workers), y la regla escrita de que a partir de ahí no se desactiva ni se baja de versión.

Ejercicios

Ejercicio 1 — Decide qué se puede compartir. Para cada archivo, di si se puede compartir con un compañero por el canal habitual del equipo, y qué revisarías antes: (a) el JSON exportado de order-sync; (b) el resultado de n8n export:credentials --all; (c) el resultado de n8n export:credentials --all --decrypted; (d) un respaldo completo de la base de datos de Postgres; (e) una captura de pantalla del panel de salida de una ejecución de shipment-notify.

Ver solución

(a) Sí, con una revisión previa. El JSON del workflow lleva la referencia de las credenciales (id y name), no sus valores. Lo que hay que revisar antes es que ningún nodo tenga un secreto escrito a mano en sus parámetros: una cabecera puesta manualmente, una URL con un token, un valor pegado en un Edit Fields. El bloque credentials es confiable; los campos que llenó una persona, no.

(b) Sí, con precaución razonable. Los datos van cifrados: sin la llave de cifrado de la instancia, no se abren. Aun así revela qué credenciales existen y de qué tipo son, que es información de reconocimiento útil para alguien malintencionado. No es catastrófico compartirlo, pero tampoco hay razón para hacerlo alegremente.

(c) No. Es la copia en texto plano de todas tus llaves. La documentación misma advierte que toda la información sensible queda visible. Este archivo no se comparte por ningún canal; se crea para una tarea específica y se borra al terminarla.

(d) No por el canal habitual. Contiene las credenciales cifradas (protegidas), pero también los workflows completos y todo el historial de ejecuciones en claro —donde puede haber datos de clientes de Terra Market y, si alguien escribió un secreto en un item, secretos en texto—. Un respaldo de base de datos se mueve por canales controlados, no por el chat del equipo.

(e) Depende, y hay que mirarla. Una captura del panel de salida muestra los datos que fluyeron por el workflow. Si en shipment-notify viajan direcciones y teléfonos de clientes, es información personal; y si alguien alguna vez metió un token en un item, ahí estaría. La regla práctica: antes de compartir una captura de una ejecución, léela como la leería alguien de afuera.

Por qué funciona: el ejercicio entrena la pregunta correcta, que no es "¿es un archivo de n8n?" sino "¿qué contiene exactamente este archivo?". Fíjate en que las respuestas van de "sí" a "no" según lo que el modelo de almacenamiento predice: referencias y datos cifrados se pueden mover; valores en claro y datos de ejecución, no.

Ejercicio 2 — Explica el modelo de dos llaves. Un compañero te pregunta: "Si la N8N_ENCRYPTION_KEY nunca cambia, ¿cómo puede n8n ofrecer rotación de llaves de cifrado? ¿No es una contradicción?" Explícale el modelo con una analogía y di qué se rota exactamente.

Ver solución

Una respuesta posible:

"No es contradicción; son dos llaves distintas. La N8N_ENCRYPTION_KEY es la llave de instancia, y efectivamente se fija al desplegar y no cambia. Pero no es ella la que cifra tus credenciales directamente: su trabajo es proteger a otra llave, la llave de datos, que es la que sí cifra el contenido. Esa llave de datos vive en la base de datos, cifrada con la de instancia. Y esa es la que se rota.

La imagen que lo aclara: piensa en una caja fuerte grande en la oficina. Adentro no guardas los documentos: guardas el llavero de todos los archiveros. Los documentos están en los archiveros. Si quieres cambiar las cerraduras de los archiveros, cambias las llaves del llavero, y la caja fuerte grande no se toca. La caja fuerte —la llave de instancia— nunca cambia porque su trabajo es proteger el llavero, no los documentos.

Lo que se rota, entonces, es la llave de datos. Y hay que hacerlo con cuidado: habilitar la función es un cambio de una sola dirección, la documentación pide respaldo completo de la base antes, y una vez habilitada no se puede desactivar ni bajar de versión sin dejar los datos permanentemente inaccesibles."

Por qué funciona: la respuesta separa las dos llaves con claridad, explica para qué sirve la separación (poder rotar sin operación de corazón abierto), y no se olvida de las advertencias. Un operador que entiende el modelo pero omite el "una sola dirección" está a un paso de romper una instancia con la mejor intención.

Ejercicio 3 — Encuentra la fuga. Este nodo Code de Terra Market pasó una revisión sin que nadie dijera nada, y sin embargo filtra un secreto. Encuéntralo, explica exactamente dónde queda guardado el secreto, y escribe la versión corregida.

// Nodo: Code — "Prepare ERP payload"
// Modo: Run Once for All Items

const items = $input.all();
const config = $('Config').first().json;

return items.map((item, i) => ({
  json: {
    external_id: item.json.order_id,
    customer: item.json.customer_name,
    total: item.json.order_total,
    // Para que el siguiente nodo sepa a dónde mandar y con qué
    endpoint: config.erp_base_url + '/orders',
    auth_header: 'Bearer ' + config.erp_token,
  },
  pairedItem: i,
}));
Ver solución

Dónde está la fuga. En la línea auth_header: 'Bearer ' + config.erp_token. El script toma un token —que alguien puso en un nodo Config previo— y lo escribe dentro de cada item de salida.

Dónde queda guardado. En tres lugares, y ninguno está cifrado:

  1. En el panel de salida del nodo, visible para cualquiera que abra el workflow en el editor.
  2. En el historial de ejecuciones, que n8n guarda en la base de datos sin el cifrado de credenciales. Con 4.000 ejecuciones diarias, ese token queda replicado miles de veces en texto.
  3. En el JSON del workflow, porque el nodo Config que contiene el token se guarda como parte del workflow —así que además viaja al repositorio si el equipo versiona sus workflows—.

Fíjate en la ironía: el token podría estar perfectamente guardado en una credencial cifrada de n8n, y este script lo saca de ahí y lo publica en tres sitios distintos.

La versión corregida. La corrección no es "ocultar el token mejor": es sacarlo del código por completo. Quien hace la llamada al ERP es el nodo HTTP Request que viene después, y ese nodo tiene su propio mecanismo de credenciales. El nodo Code no necesita el token —nunca lo necesitó—, y en n8n 2.0 tampoco puede hacer la llamada él mismo, así que no hay ningún argumento para que lo toque.

// ============================================================
// Nodo: Code — "Prepare ERP payload"
// Modo: Run Once for All Items
//
// ENTRADA:  pedidos de la tienda con order_id, customer_name, order_total
// SALIDA:   un item por pedido con el cuerpo listo para el ERP
// NOTA:     este nodo NO conoce ningún token ni ninguna URL.
//           La autenticación y el destino los resuelve el nodo
//           HTTP Request siguiente, con la credencial erp_api.
// ============================================================

const items = $input.all();

return items.map((item, i) => ({
  json: {
    // Solo los datos del pedido. Ningún secreto, ningún destino.
    external_id: item.json.order_id,
    customer: item.json.customer_name,
    total: item.json.order_total,
  },
  pairedItem: i,
}));

Y el nodo HTTP Request siguiente se configura con la URL del ERP en su campo de URL y con la credencial erp_api en su selector de credenciales. El token no aparece en ningún item, ningún panel y ningún historial.

Qué esperar al ejecutar la versión corregida: el panel de salida muestra un item por pedido con external_id, customer y total, y ninguna cadena que se parezca a una llave. Esa ausencia es la señal de éxito.

Por qué funciona: el ejercicio conecta el modelo de almacenamiento con una consecuencia práctica inmediata. El cifrado en reposo protege el campo data de la credencial; en el momento en que tu código copia ese valor a un item, el dato sale de la zona protegida y entra a la zona que no lo está. Por eso la regla no es "cuida cómo manejas el token en el código", sino "el token no entra al código".

Resumen y siguiente paso

En esta lección abriste la credencial y miraste su modelo. Con la imagen de la caja de seguridad y el recibo separaste las dos cosas que se confunden: la referencia —identificador y nombre, que viven en el JSON del workflow y viajan a todas partes sin abrir nada— y el valor —los datos secretos, que viven solo en la instancia, cifrados—. Viste la anatomía de una credencial en cuatro partes (identificador, nombre, tipo y datos) y por qué solo la última se cifra, lo que explica que la interfaz no te muestre de vuelta los campos secretos. Definiste cifrado en reposo contra en tránsito, y viste los dos escenarios que lo justifican: una copia de la base en manos equivocadas, y la separación de responsabilidades con gente legítima de adentro. Conociste el modelo de dos llaves —la llave de instancia (N8N_ENCRYPTION_KEY) que no cambia y protege a la llave de datos, que es la que cifra tus credenciales y la que se rota— con la analogía de la caja fuerte que guarda el llavero, y sus advertencias serias: la rotación se habilita con N8N_ENV_FEAT_ENCRYPTION_KEY_ROTATION=true en todas las instancias, es un cambio de una sola dirección, exige respaldo completo previo, y desactivarla o bajar de versión después deja los datos permanentemente inaccesibles. Comparaste el export normal con el --decrypted, cuya propia documentación advierte que toda la información sensible queda visible, y fijaste las tres reglas para cuando sea legítimo usarlo. Y cerraste con lo que la llave no protege: los workflows, las cuentas, y —el agujero que más se ignora— el historial de ejecuciones, del que sale la regla que gobierna el resto del módulo: el secreto no debe pasar por los datos.

Antes de avanzar deberías poder: explicar qué viaja en el JSON de un workflow y qué no; definir cifrado en reposo con un ejemplo; explicar el modelo de dos llaves y qué se rota exactamente; decir qué hace --decrypted y cuándo se justifica; y nombrar tres cosas que la llave de cifrado no protege.

La lección 3 es el corazón del módulo, y cambia de plano: deja de preguntar dónde vive el secreto para preguntar qué puede hacer. Vas a enfrentar el hallazgo más incómodo de Terra Market —una erp_api con permisos de administrador cuando inventory-update solo necesita leer— y vas a entender por qué esa distancia entre lo que una credencial tiene y lo que usa es la que convierte un incidente pequeño en uno grande. Vas a aprender a auditar una credencial de verdad: quién la usa, qué permisos tiene, cuándo se rotó y quién responde por ella. Y vas a salir sabiendo reducir el alcance de una credencial en producción sin romper el workflow que depende de ella, que es la parte que da miedo y tiene método.

Recursos

  • Credentials — n8n Docs — qué son las credenciales, cómo se crean y cómo las usan los nodos. La referencia base del modelo de esta lección.
  • Use the command line — n8n Docs — los comandos export:credentials e import:credentials con todas sus banderas, incluida --decrypted y su advertencia sobre información sensible visible en los archivos.
  • Rotate encryption keys — n8n Docs — el modelo de dos llaves, la variable N8N_ENV_FEAT_ENCRYPTION_KEY_ROTATION, el flujo de rotación y las advertencias sobre el cambio de una sola dirección. Confirma el detalle en la documentación de tu versión.
  • Set a custom encryption key — n8n Docs — la llave de instancia: que n8n genera una automáticamente en ~/.n8n si no la fijas, y que en modo cola hay que darla a todos los workers. Su montaje completo está en la guía de Self-Hosting y Operaciones, módulo 3, lección 5.
  • Redact execution data — n8n Docs — la función que oculta entradas y salidas del historial de ejecuciones, disponible en planes Enterprise. Contexto para el agujero de los datos de ejecución; se retoma en la lección 6.