# Revisión de seguridad de Fable (30-09-2026) *Revisión externa del paquete `formato3_para_revision.md`, copiada tal como la pasó el autor. Ese paquete ya no está en el repo: sigue en git, en el commit 56851a9, y en el artefacto privado publicado. El diseño vigente, con estos hallazgos resueltos, es [formato3_diseno.md](formato3_diseno.md).* He leído [formato3_para_revision.md](../docs/spec_v0.10/formato3_para_revision.md) entero y contrastado algunas afirmaciones con el código. Mi opinión: ## Veredicto **El formato está bien diseñado; el servicio de sellado aún no es revisable.** La parte criptográfica (qué se firma, qué se sella, los compromisos) no le encuentro ataque. Los problemas están en los bordes: el origen del servicio, los caracteres invisibles y la vida del sello a décadas. Comprobado por mí: - Toda la aritmética de tamaños cuadra (22/128/175/281, 53, 45, 146, L = 573 → P = 768, L = 1637 → P = 1792). - noble 2.4.0 usa la ecuación con cofactor siempre (`edwards.js:659`), así que el verificador propio es necesario, no un capricho. - Go trae Unicode 15.0.0 y Node 24.9 el 16.0, como dice el documento. ## Hallazgos, por gravedad | # | Gravedad | Problema | Arreglo | |---|---|---|---| | 1 | Mayor | **Mismo origen (decisión 5).** Hoy el sitio es estático y el CSP va en ``. Un documento servido bajo `/seal/*` no lleva CSP y corre en el origen que maneja plaintexts, `.dkk` y claves de autor | Origen aparte (`seal.…`), o cabeceras forzadas en el proxy: `nosniff`, `octet-stream`, `CSP: sandbox` | | 2 | Mayor | **Caracteres invisibles en rutas.** R4 deja pasar U+206A–206F, y ZWJ/ZWNJ están permitidos: HFS+ los ignora al comparar, así que «a‌b» y «ab» colisionan. Los tags U+E0020–E007F permiten nombres visualmente idénticos | R4 por propiedad (`Default_Ignorable_Code_Point`) con lista blanca; la clave de R7 descarta lo permitido | | 3 | Mayor | **El sello no sobrevive a DateKeys.** La auditoría depende de que el registro siga publicado dentro de décadas, y el perfil anual obliga a actualizar cada lector cada año | Fichero aparte con prueba de inclusión, checkpoint y `.ots`; raíz offline ahora, no «más adelante» | | 4 | Mayor | **Abuso frente a «sin registros».** El registro solo crece y el POST es anónimo; limitar por IP exige guardar IPs | Definirlo: estado solo en memoria, o prueba de trabajo con SHA-256 | | 5 | Menor | **El área depende de `SECURITY_LEN`.** Un sello futuro de más de 512 bytes cambia L y delata su presencia por P | Área fija mayor, o longitud propia en la trama | | 6 | Menor | **Orden de rutas en TypeScript.** Comparar strings ordena por UTF-16, no por bytes: U+FF5E y un emoji salen al revés que en Go | Vector en `paths.json` | | 7 | Menor | **Suplantación en la CLI.** Una línea de comentario rellena de espacios salta de renglón y el falso veredicto aparece sin el prefijo `│ ` | Repetir los veredictos al final, o partir líneas en la CLI | | 8 | Menor | **A1 y el tráfico.** La página no hace ninguna petición hoy; un POST delata el sello | Declararlo como fuga aceptada | | 9 | Menor | **Ambigüedades:** si una firma ilegible invalida también el sello; R6b con «~1» a secas y qué cuenta como carácter; mtime negativa | Tabla de verdad, expresión regular, regla de omisión | | 10 | Menor | `control_commit` y `head_digest` sin prefijo de dominio; el directorio `.datekeys-*` puede coincidir con una entrada | Prefijo; reservar ese nombre | ## Sobre el documento - **Las 28 objeciones son 24:** la 12, 16, 18 y 24 repiten otras. Y varias no están «corregidas» sino aceptadas (2, 9, 23). Un revisor agradece la distinción. - **Faltan definiciones para alguien de fuera:** «capa 2/3/4», las referencias a § del spec, el valor de L_MAX (2⁵³ − 2⁴⁶) y para qué sirve la sal del head. - **Hay tres ficheros con el mismo texto.** `para_revision` es la suma exacta de los otros dos; cualquier corrección hay que hacerla dos veces. - **El sello sin perfil conocido no muestra nada.** Mejor un texto que diga que hay un sello y que el lector es antiguo. ## Recomendación Separar en tres entregas, con el formato congelado desde la primera y `security` vacío: 1. Contenedor y rutas. 2. Firma y claves de autor. 3. Sello, cuando exista su documento propio («Servicio de sellado v1»), con custodia de clave, monitores y hosting. Los hallazgos 1, 3 y 4 son del servicio, no del formato, y no deberían frenar lo demás. El 2 y el 6 sí conviene cerrarlos antes de escribir el texto del spec. No he revisado aún a fondo el ZIP byte a byte ni he probado el comportamiento de HFS+ en una máquina; lo de HFS+ lo afirmo por la lista que usa git en `core.protectHFS`.