Los invariantes que impiden que un bug del daemon Zig escale, la capa zkit.safety que los impone y las guardas de construcción que impiden saltársela.
Qué obliga, no qué está verificado. Esta página fija el contrato de diseño de
dec-0117. Qué invariantes tienen su prueba verde en el árbol actual lo dice el gate detrack/byte-runtime/sec, y un invariante del diseño que el código todavía no cumple sigue siendo la obligación.
Cada uno lleva su test de rotura: un test que falla si alguien lo incumple.
| Invariante | Obliga a |
|---|---|
| I1 | Ninguna operación del daemon sin capacidad verificada (SCT). El identificador de sesión no es un secreto. |
| I2 | El daemon sólo acepta sesiones creadas por playback-svc por el socket de control, y una SCT sólo vale si su sesión existe con el mismo actor y recurso. |
| I3 | Escribir (publicar en MoQT, ingestar) exige alcance de escritura y un recurso concreto; el publicador de laboratorio no va en el binario de producción. |
| I4 | Presupuestos jerárquicos (daemon, actor, sesión, conexión) de memoria, flujos, suscripciones, bytes en vuelo y sesiones. Agotarlo falla la sesión, no el daemon. |
| I5 | Los parsers de entrada no confiable son seguros por construcción: ReleaseSafe, lectores acotados, aritmética comprobada, sin unreachable alcanzable y con fuzz. |
| I6 | libav es aislable: el contrato con el motor libav es de mensajes y descriptores, para poder correrlo en un proceso trabajador sin red, con seccomp y límites. |
| I7 | Raíces de fichero fd-relativas: por el socket sólo viaja {rootId, relPath}, la apertura no sigue symlinks ni sale de la raíz y se verifica (dev, ino). |
| I8 | Contenedores con el mínimo privilegio: no root, rootfs de sólo lectura, sin capabilities, raíces de medios ro, salida de red denegada por defecto. |
| I9 | Las referencias que sobreviven a un salto asíncrono son handles con generación, no punteros. |
| I10 | Un fallo de memoria es un pánico acotado, no una primitiva de explotación; el daemon corre supervisado. |
| I11 | Sólo los niveles builtin, official y trusted-native ejecutan código en el proceso del daemon. |
| I12 | Observabilidad sin fugas: las SCT y las rutas no se registran, y cada rechazo relevante genera un evento de auditoría. |
zkit.safety ofrece las primitivas con las que se escriben los invariantes (ver
zkit, spire y conduit): handles tipados con generación, un asignador
con presupuesto anidable, raíces fd-relativas con apertura que no sigue enlaces, lectores acotados
y aritmética comprobada.
Un paso de construcción, zig build audit:safety, recorre el AST de todo el código de
producción de native/zig (no es un grep) y se pone en rojo si encuentra:
| Regla | Prohíbe |
|---|---|
| S1 | Funciones de la librería estándar que abren o resuelven rutas en bruto, en lugar de pasar por la raíz fd-relativa |
| S2 | Primitivas de sincronización en bruto: se usa zkit.safety.Mutex y zkit.sync |
| S3 | Errores tragados: un catch que no hace nada con el error |
| S4 | Abortar sobre entrada en módulos parser: unreachable, @panic, assert |
| S5 | Conversiones de puntero o entero sin un comentario SAFETY: que las justifique |
| S6 | El ejecutable del daemon compilado en ReleaseFast o ReleaseSmall |
| S7 | El asignador global de la librería estándar dentro de sesiones, MoQT o transportes |
| S8 | Apagar los chequeos de seguridad por bloque sin justificación (y nunca en módulos parser) |
| Z1 | Reimplementar un símbolo de zkit (check:zkit-no-reimpl) |
Las reglas resuelven cada nombre por ámbito léxico hasta su declaración, así que un alias no
esconde una primitiva. El guard tiene un trinquete: guarda contadores por regla y por fichero
en safety_baseline.zon, que pueden bajar pero nunca subir, y un fichero nuevo parte de cero. Cada
regla se demuestra con un mutante que pone el paso en rojo. Su especificación está cerrada: una
regla nueva o un alcance mayor va a un ticket de seguimiento, con su test y su enmienda de
dec-0117 §6. Entra en zig build test y en CI.
Una dirección de escritura nueva se trata con el mismo rigor: capacidad de alcance ingest
obligatoria en cada petición, cuotas por fichero y por actor (con tope total por raíz), preparación
y renombrado relativos a la raíz de ingesta (I3, I7), conexiones acotadas por identidad antes y
después de autenticar, y borde de red declarado, no deducido (dec-0121).
dec-0117 deja explícitamente fuera:
dec-0119/dec-0120) y el de la ingesta (dec-0121),
de los que sólo fija los requisitos de seguridad;dec-0118);También declara los límites de su propia guarda (por ejemplo, un contenedor que guarda un alias en bruto y viaja como valor en tiempo de compilación), que cubren los tests de la capa 1 y el perfil seccomp del contenedor. Lo que es reversible sin ADR: nombres de módulos, duraciones de las SCT, cifras de los presupuestos y la lista exacta de llamadas del perfil seccomp.