0.2.0 · · Release en GitHub
Kernl 0.2.0: el stack Docker deja de correr sin autenticación

Esta versión de Kernl es casi toda seguridad, y el problema principal era el camino que recorría todo el mundo: el quick start con Docker.
Loopback no es un límite de confianza
El docker-compose.yml traía KERNEL_ALLOW_UNAUTH=1, justificado en que el dashboard se publicaba en 127.0.0.1. El razonamiento no se sostiene contra un navegador.
Combinado con un Access-Control-Allow-Origin comodín en todas las respuestas, cualquier página que el usuario tuviera abierta podía leer —y escribir— el kernel entero cross-origin, incluida la superficie de herramientas MCP. Que el puerto sea local no impide que el navegador del usuario sea el que hace el pedido.
El quick start ahora corre con el token que el kernel genera en el primer arranque.
CORS por denegación
Access-Control-Allow-Origin se manda únicamente para un origen que esté explícitamente listado en CORS_ALLOWED_ORIGINS. Una lista vacía significa same-origin, que es lo que necesita el dashboard incluido.
Antes una lista vacía significaba *, y a un origen que no coincidía se le respondía con el primer origen configurado en lugar de una negativa.
La recuperación de licencia entregaba la licencia
GET /api/license/lookup devolvía el JWT completo de la licencia —la credencial de descarga de todas las extensiones que ese cliente compró— a cualquiera que supiera su dirección de email. De yapa, su 404 confirmaba qué direcciones eran de clientes y cuáles no.
Ahora manda la licencia por mail a la dirección registrada y responde 202 igual en los dos casos, sepa o no de quién es el mail.
Además, Torrents pasa a estar gateado en runtime. La extensión paga se distribuía sin verificación de licencia ni en sus herramientas MCP ni en sus rutas /api/torrents/*; lo único protegido era la descarga del bundle.
El token de auth del primer arranque, por último, se le entrega al navegador por el fragmento de la URL, que nunca viaja al servidor, en lugar de por query string.
Los backups copiaban la base equivocada, y lo festejaban
backup.sh copiaba el ./data/kernel.db del repo —casi siempre un sobrante viejo— en lugar de la base que está adentro del volumen de Docker. Usaba cp sobre una base en modo WAL abierta por el kernel. Y salía con código 0 cuando no encontraba nada.
Ahora toma el snapshot a través de SQLite (VACUUM INTO), verifica el resultado antes de llamarlo backup, y falla fuerte. También guarda .kernel-encryption-key, sin la cual los secretos cifrados de una base restaurada son irrecuperables.
Los restores tenían el problema simétrico: restore.sh copiaba la base al ./data del repo incluso en deployments con Docker, se salteaba la clave de cifrado, e informaba "starting fresh" cuando el archivo no tenía base ninguna. Ahora exige un destino explícito con --docker o --native y rechaza un archivo vacío.
Dos que reportaban éxito sin hacer nada
- Las licencias pagas se rechazaban en toda instalación que no fuera Docker. El kernel esperaba un issuer
issuer.mtwkernel.commientras las licencias se emiten desdeissuer.lifekernl.com, y el override sólo estaba en eldocker-compose.yml. Las instalaciones por rpm, deb, dmg y msi no podían activar una clave válida. - La firma de código nunca corría, con o sin certificados. Los pasos de firmado del workflow estaban condicionados con
if: env.X != '', conXdefinida en el bloqueenv:de ese mismo paso, que no está en alcance para la condición del propio paso. Todos los releases publicaban artefactos sin firmar e informaban éxito.
El kernel también quedaba atado a loopback aun con autenticación habilitada: loadConfig() resolvía el bind antes de que el bootstrap generara el token, reescribiendo de forma permanente un 0.0.0.0 explícito. La decisión ahora se toma donde se conoce el token final.
Y el primer arranque de una instalación nativa terminaba en una pantalla de login pidiendo un token que al usuario nunca se le había mostrado. Los launchers de macOS y Windows ahora se lo entregan; el de Linux imprime dónde encontrarlo.
Cómo actualizar
curl -fsSL https://lifekernl.com/install.sh | sh
Si venís de 0.1.x con el stack de Docker, revisá tu CORS_ALLOWED_ORIGINS antes de actualizar: una lista vacía ya no significa *. El detalle completo está en el CHANGELOG.
Registro de cambios
Seguridad
- El stack de Docker por defecto ya no corre sin autenticación.
docker-compose.ymlseteabaKERNEL_ALLOW_UNAUTH=1, con la justificación de que el dashboard se publicaba en 127.0.0.1. Loopback no es una frontera de confianza frente a un navegador: sumado a unAccess-Control-Allow-Origincomodín en cada respuesta, cualquier página que el usuario tuviera abierta podía leer —y escribir— todo el kernel cross-origin, incluida la superficie de herramientas MCP. El inicio rápido ahora corre con el token que el kernel genera en el primer arranque. - CORS deniega por defecto.
Access-Control-Allow-Originse manda solo para un origen listado explícitamente enCORS_ALLOWED_ORIGINS; una lista vacía significa solo mismo origen, que es lo que necesita el dashboard incluido. Antes, una lista vacía significaba*, y a un origen que no coincidía se le respondía con el primer origen configurado en vez de un rechazo. - La recuperación de licencias ya no reparte claves de licencia.
GET /api/license/lookupdevolvía el JWT completo de la licencia —la credencial de descarga de cada extensión que tiene un cliente— a cualquiera que supiera su dirección de email, y su 404 confirmaba qué direcciones eran de clientes. Ahora manda la licencia por mail a la dirección registrada y responde 202 igual en los dos casos. - Torrents se valida en tiempo de ejecución. La extensión paga salía sin
ningún chequeo de licencia ni en sus herramientas MCP ni en sus rutas
/api/torrents/*; lo único protegido era la descarga del bundle. - El token de autenticación que se genera en el primer arranque se le pasa al navegador por el fragmento de la URL, que nunca se manda al servidor, en vez de por un query string.
Corregido
- Los backups copiaban la base equivocada, y lo decían muy contentos.
backup.shcopiaba el./data/kernel.dbdel repo —casi siempre un resto viejo— en vez de la base que está dentro del volumen de Docker, usabacpsobre una base en modo WAL abierta por el kernel, y salía con 0 cuando no encontraba nada. Ahora hace el snapshot a través de SQLite (VACUUM INTO), verifica el resultado antes de llamarlo backup y falla con ruido. También copia.kernel-encryption-key, sin la cual los secretos cifrados de una base restaurada no se pueden recuperar. - Las restauraciones escribían donde nadie lee.
restore.shcopiaba la base al./datadel repo incluso en despliegues con Docker, se salteaba la clave de cifrado e informaba "starting fresh" cuando el archivo no tenía base. Ahora exige un destino explícito--docker/--nativey rechaza un archivo vacío. - Las licencias pagas se rechazaban en toda instalación que no fuera
Docker. El kernel esperaba un emisor
issuer.mtwkernel.commientras que las licencias las emiteissuer.lifekernl.com, y solodocker-compose.ymlseteaba el override, así que las instalaciones rpm, deb, dmg y msi no podían activar una clave válida. - La firma de código nunca corría, con o sin certificados. Los pasos de
firma del workflow de release estaban condicionados con
if: env.X != '', conXdefinida en el bloqueenv:del mismo paso, que no está al alcance de la condición de ese paso. Cada release publicaba artefactos sin firmar e informaba éxito. - El kernel escuchaba en loopback aun con la autenticación activada:
loadConfig()resolvía el bind antes de que el bootstrap generara el token de autenticación, y pisaba para siempre un0.0.0.0explícito. Ahora la decisión se toma donde ya se conoce el token final. - El primer arranque de una instalación nativa terminaba en un callejón sin salida: una pantalla de login que pedía un token que al usuario nunca se le había mostrado. Ahora los lanzadores de macOS y Windows lo pasan; el de Linux imprime dónde encontrarlo.
docs/DEPLOYMENT.mddocumentaba el backup de Neo4j contra un nombre de volumen que no existe, lo que hace quedocker run -vcree uno vacío y no archive nada, con éxito.