Actualizaciones del formato de auditoría que conservan las evidencias antiguas
Planifica actualizaciones del formato de auditoría que conserven las evidencias antiguas mediante versiones explícitas, bytes de origen inmutables, fixtures de migración y verificadores compatibles.

Una actualización del formato de auditoría solo es segura cuando un investigador puede verificar las evidencias exportadas ayer con las reglas que se aplicaban ayer. Un analizador nuevo que muestra una pantalla plausible, una migración de base de datos completada correctamente y un despliegue en verde no lo demuestran. A menudo ocultan justo la ruptura importante: un registro cuyos bytes siguen intactos, pero cuyo significado, entrada del hash o comportamiento del verificador ha cambiado.
Trata el formato de auditoría como un protocolo con una cola larga. Tu aplicación puede cambiar cada semana. Las evidencias no. Cuando alguien depende de un registro para explicar quién aprobó una acción, qué credencial se utilizó o qué envió un agente a un servicio externo, ese registro necesita una interpretación estable mucho después de que haya desaparecido el código que lo escribió.
Conserva los bytes antes que la comodidad
El artefacto autoritativo es la secuencia original de bytes del registro junto con el contexto necesario para verificarla. Una fila analizada en la base de datos actual es una copia de trabajo. Un objeto JSON mostrado en un panel es una vista. Ninguno sustituye a los bytes de evidencia que participaron en una firma, una cadena de hashes o una envoltura autenticada.
Esta distinción parece excesiva hasta que una actualización reescribe un campo. Supón que la versión 1 almacenaba el destino SSH como una cadena proporcionada por el usuario:
{"schema_version":1,"event":"ssh.execute","target":"[email protected]:22","command":"uptime"}
La versión 2 quiere separar los campos para poder filtrar por host y puerto:
{"schema_version":2,"event":"ssh.execute","user":"build","host":"prod.example","port":22,"command":"uptime"}
Los dos registros pueden describir la misma acción, pero no son evidencias intercambiables. Un codificador v2 puede normalizar el nombre de host, insertar un puerto predeterminado o rechazar un destino que v1 aceptaba. Si sobrescribes el primer registro con el segundo, has creado una afirmación nueva sobre el evento antiguo. Puede que sea correcta, pero no puedes demostrarlo simplemente señalando los datos reescritos.
Mantén separadas estas tres cosas:
- Evidencia original: bytes inmutables exactamente como fueron aceptados en la secuencia de auditoría.
- Representación derivada: una forma indexada, decodificada o migrada que se usa para buscar y mostrar información.
- Notas de interpretación: reglas documentadas que explican los campos, los valores predeterminados y el comportamiento específico de cada versión.
Una representación derivada puede reconstruirse. La evidencia original no. Guarda los originales en un paquete de evidencias de solo adición o en un almacén de objetos, identifícalos mediante su hash y haz que cada elemento derivado apunte a ese hash. Si las reglas de conservación exigen eliminar algo, registra esa eliminación como un evento independiente. No compactes el historial en silencio y llames actualización al resultado.
Esto también se aplica cuando el registro utiliza entradas cifradas. El cifrado protege el contenido frente a lectores no autorizados; no hace que una migración con pérdida sea inocua. El verificador debe seguir identificando qué texto cifrado, cabecera y regla de cadena produjeron el resultado.
Asigna una versión explícita a cada registro
Incluye un schema_version explícito dentro de cada registro antes de someterlo a hash o firmarlo. No deduzcas la versión a partir de una extensión de archivo, un número de migración de base de datos, una versión de la aplicación o la presencia de un campo añadido recientemente.
La inferencia funciona hasta la primera exportación forense. Un investigador recibe una carpeta de registros copiada desde una copia de seguridad, un ticket de soporte o una máquina que ya no ejecuta la aplicación actual. El contexto que la rodea está incompleto, mientras que el registro sigue presente. Un registro que contiene su propia versión indica al verificador qué decodificador y qué reglas necesita.
Usa un entero pequeño para un formato cuyas reglas controlas. Reserva la versión 0 como no válida, de modo que un campo ausente no se convierta silenciosamente en un formato antiguo. La versión debe tener una función limitada: seleccionar la gramática del registro y la receta de verificación. No la conviertas en un identificador general de versión del producto.
Una envoltura duradera podría tener este aspecto:
{
"schema_version": 3,
"record_id": "01J8X7K5W3H0Q9M6P2R4A1C8ZD",
"recorded_at": "2026-07-22T14:08:31.482Z",
"kind": "http.request.completed",
"previous_digest": "sha256:4a4d...",
"payload": {
"method": "POST",
"authority": "api.example.test",
"status": 201
}
}
La versión forma parte de la entrada autenticada. Si un registro indica la versión 3, pero el hash se calculó sin ese campo, un atacante que pueda editar los bytes almacenados podría redirigir al verificador hacia una ruta de interpretación diferente. Incluye en los bytes protegidos todos los campos que seleccionen un analizador, un algoritmo de hash, una regla de canonicalización o un algoritmo de firma.
Separa también la versión del esquema del registro de la versión del significado del evento. La primera responde a «¿Cómo analizo y verifico estos bytes?». La segunda responde a «¿Qué significaba este evento cuando se emitió?».
Por ejemplo, cambiar actor de un nombre visible de texto libre a una identidad estable de proceso puede conservar la forma del JSON y cambiar la afirmación. No es simplemente la versión 4 del esquema. Es un cambio semántico, y el investigador necesita que la especificación de evidencias indique cuándo comienza el nuevo significado. La misma advertencia se aplica cuando un campo cambia de unidades, una marca de tiempo pasa de hora local a UTC o un estado cambia de una respuesta observada a una decisión de política.
Fija la receta de verificación, no solo los campos
Un esquema versionado está incompleto si el verificador no puede reconstruir la receta exacta de bytes utilizada para autenticar un registro. La disposición de los campos es solo una parte de esa receta.
Escribe, para cada versión:
- la gramática aceptada del registro y los campos obligatorios;
- la codificación de texto o binaria y las reglas de canonicalización;
- los algoritmos de hash y firma;
- el separador de dominio, si utilizas uno;
- la regla que enlaza la cadena y la regla de génesis;
- el comportamiento ante entradas malformadas o desconocidas.
RFC 8785 explica por qué el JSON necesita una representación determinista antes de las operaciones criptográficas: el JSON normal permite varias serializaciones del mismo valor lógico, mientras que el hash y la firma necesitan bytes invariantes. Su esquema de canonicalización de JSON restringe la entrada y ordena las propiedades de los objetos de forma determinista. También advierte, mediante las restricciones del formato, que los nombres de propiedades duplicados y los números fuera de la representación admitida no son detalles inofensivos.
Ese estándar solo sirve si nombras el perfil exacto. Decir «calculamos el hash del JSON» no es una receta. Decir «llamamos al serializador del entorno de ejecución actual» es peor, porque una actualización del entorno puede cambiar el escape, el formato numérico o el orden sin que haya ningún cambio deliberado en la auditoría.
El mismo problema aparece con los formatos binarios. RFC 8949 define los requisitos de codificación determinista de CBOR y señala que una convención de ordenación anterior necesita un modo de compatibilidad con nombre explícito. Un verificador no puede asumir de forma segura que todos los productores históricos entendían lo mismo por «canónico».
No construyas un verificador nuevo que analice cualquier JSON, lo vuelva a serializar con la biblioteca de hoy y después calcule el hash. Ese patrón rompe las evidencias antiguas de dos maneras. Puede rechazar registros válidos según la receta anterior y aceptar un registro según una receta nueva que nunca produjo el hash original.
En su lugar, mantén la selección de versiones cerca del límite de bytes:
leer los bytes de la envoltura
-> identificar el schema_version protegido
-> seleccionar el verificador V1, V2 o V3
-> validar la gramática de esa versión
-> reproducir los bytes autenticados de esa versión
-> verificar el hash, la firma y el enlace de la cadena
-> decodificar un modelo de visualización solo después de verificar
El modelo de visualización aparece al final por una razón. Un renderizador puede ser agradable. Un verificador no puede ser imaginativo.
Las versiones desconocidas deben fallar de forma segura
Cuando un verificador encuentra una versión que no admite, debe devolver un resultado explícito unsupported_version. No debe tratar los campos desconocidos como elementos ignorables, asumir el diseño más reciente ni ejecutar un decodificador genérico de respaldo.
Los ingenieros suelen resistirse porque quieren compatibilidad hacia delante. Esa compatibilidad es adecuada para que una aplicación lea campos opcionales de presentación. Resulta peligrosa al verificar evidencias, donde un campo que parece opcional puede controlar más adelante la interpretación autenticada.
Usa una estructura de resultado que separe el fallo de las evidencias de las limitaciones de la herramienta:
{
"record_id": "01J8X7K5W3H0Q9M6P2R4A1C8ZD",
"schema_version": 4,
"status": "unsupported_version",
"verified": false,
"supported_versions": [1, 2, 3],
"reason": "Verifier 2.7.0 has no verification recipe for schema version 4"
}
Ese resultado dice algo preciso: la herramienta no ha establecido la autenticidad. No acusa al registro de haber sido manipulado ni finge que sea válido. Mantén distintos invalid, incomplete, unsupported_version y verified, tanto en la salida de los comandos como en las API.
Una cadena de hashes añade otro requisito de compatibilidad. No se puede asumir el historial de la cadena a partir de un único hash final. El verificador necesita las reglas específicas de cada versión para el primer registro, el orden de los registros, la codificación del hash padre y cualquier formato de punto de control. Certificate Transparency ofrece aquí un modelo mental adecuado: RFC 9162 define pruebas de consistencia que demuestran que un árbol anterior es el mismo prefijo de uno posterior, en lugar de pedir a los auditores que confíen en un hash raíz comunicado recientemente.
Tu secuencia de auditoría quizá no utilice un árbol de Merkle, pero la lección se mantiene. Cuando cambien las reglas de la cadena, demuestra la continuidad en el límite. Crea un punto de control terminal v1 que contenga su hash final verificado, el número de registros y la versión. Haz que el primer registro v2 autentique ese punto de control en un campo definido. El verificador v2 debe comprobar ambos lados con sus propias reglas antes de informar de un historial continuo.
Nunca unas dos historias guardando un hash antiguo como comentario o campo de presentación. La unión debe formar parte de la entrada protegida.
Las migraciones deben crear derivados, nunca sustitutos
Una buena migración crea un derivado nuevo y reproducible junto a las evidencias de origen. Registra suficiente procedencia para que otra persona pueda recrear el mismo resultado y compararlo con la fuente.
Para cada registro o lote migrado, captura:
{
"source_digest": "sha256:4a4d...",
"source_schema_version": 1,
"migration_id": "audit-v1-to-v2",
"migration_build": "2.7.0+e31c9f4",
"output_digest": "sha256:77c8...",
"migrated_at": "2026-07-22T14:12:09Z"
}
La hora de migrated_at describe el derivado, no el evento original. No sobrescribas recorded_at ni presentes un registro v2 generado como si lo hubiera emitido el sistema antiguo. Ese error ha causado más confusión en investigaciones internas que cualquier fallo evidente del analizador.
Algunas migraciones no pueden conservar toda la información. Un registro v1 puede tener una única cadena target, mientras que v2 exige un URI estructurado. Si el análisis falla o contiene ambigüedades, conserva la cadena de origen y registra un estado de migración explícito. No inventes un valor estructurado solo porque tu nuevo índice necesita uno.
Por ejemplo:
{
"source_digest": "sha256:4a4d...",
"migration_status": "partial",
"derived": {
"target_raw": "[email protected]:22",
"host": "prod.example",
"port": 22
},
"unresolved": ["user"]
}
Puede parecer menos ordenado que una fila completamente rellenada. Es más honesto. Un investigador futuro podrá ver tanto lo que decía el registro antiguo como lo que dedujo la migración.
Evita la recomendación habitual de pasar cada entrada antigua por el escritor actual y llamar a eso una actualización. Es popular porque simplifica una ruta de código y uniformiza los informes. Es incorrecta para las evidencias porque los escritores suelen aplicar los valores predeterminados actuales, omitir campos obsoletos y normalizar valores. El resultado puede ser útil para buscar, pero es una traducción, no el testimonio original.
Un corpus permanente de fixtures detecta las rupturas silenciosas
La compatibilidad es un recurso de pruebas, no una promesa en las notas de una versión. Construye un corpus de evidencias para cada versión de esquema publicada y ejecútalo contra cada versión del verificador que declare compatibilidad con ella.
El corpus necesita más que unos cuantos registros del caso normal. Conserva fixtures exactas byte por byte para estos casos:
- un registro válido normal y una cadena válida de varios registros;
- marcas de tiempo límite, texto Unicode, valores opcionales vacíos y límites numéricos admitidos por esa versión;
- un registro con un byte del contenido cambiado;
- un registro con el hash padre cambiado o la secuencia reordenada;
- entradas malformadas, duplicadas, truncadas y de versión desconocida.
Guarda los resultados esperados, no solo los objetos decodificados esperados. La prueba debe comprobar el resultado de las evidencias y la categoría del diagnóstico. Un verificador que marca correctamente como inválido un registro válido también ha fallado. Un verificador que convierte un registro malformado en una excepción genérica del analizador ha fallado de forma menos llamativa, pero ha dificultado las investigaciones.
Usa un manifiesto que fije los hashes de las fixtures y el comportamiento esperado del verificador:
fixture: v1/0007-http-request.json
sha256: 4a4d5f0c...
expect:
status: verified
schema_version: 1
chain_position: 7
fixture: v1/0007-http-request-tampered.json
sha256: 91af2a7d...
expect:
status: invalid
error_code: payload_digest_mismatch
Después prueba más de una dirección.
- El verificador más antiguo que conserves debe seguir verificando su corpus original.
- El verificador actual debe verificar todos los corpus históricos conservados.
- Un escritor candidato debe crear registros que el verificador actual acepte bajo la nueva versión.
- Una migración candidata debe conservar el hash de origen declarado y producir el derivado esperado.
- Todos los verificadores deben rechazar las fixtures diseñadas para versiones futuras no compatibles.
No reescribas las fixtures esperadas cada vez que falle una prueba. Primero inspecciona los bytes, la versión del verificador seleccionada y el código del fallo. Las actualizaciones de fixtures deben ser poco frecuentes, revisarse como un cambio de protocolo y acompañarse de una razón que distinga una fixture nueva intencionada de unas evidencias modificadas.
Añade pruebas basadas en propiedades alrededor de los analizadores, pero no las confundas con el corpus permanente. La entrada aleatoria encuentra fallos y casos límite extraños. Las fixtures con nombre conservan los casos que ya aprendiste de la forma difícil, incluidos los registros de versiones reales después de eliminar el contenido sensible.
Prueba el límite de actualización como lo haría una investigación
El mayor riesgo suele estar en el límite entre versiones, no en ninguna de las versiones por separado. Escribe un escenario que empiece antes de la actualización y termine después, y pregunta si una persona independiente puede explicar toda la secuencia.
Imagina una cadena en la que v1 registra la aprobación de una sesión de agente, varias llamadas HTTP y la revocación de una sesión. La versión 2 introduce un campo de resultado de solicitud más detallado y una nueva codificación de la suma de comprobación. La prueba debe comenzar con un registro de génesis v1, añadir entradas v1 válidas, crear el punto de control de límite documentado, añadir entradas v2 y exportar el paquete completo.
El informe de verificación esperado debe mostrar claramente la transición:
$ audit verify evidence-bundle
verified v1 records: 18
verified v1 terminal digest: sha256:6c12...e98a
verified v1-to-v2 continuity checkpoint
verified v2 records: 6
chain status: verified
Ahora ejecuta los fallos que provocan las actualizaciones en producción:
- elimina el último registro v1, pero conserva los registros v2;
- modifica el hash v1 del punto de control;
- usa un registro v2 con una etiqueta de versión v1;
- exporta solo el segmento v2 y solicita un veredicto sobre todo el historial;
- ejecuta un verificador anterior a v2 contra el paquete mixto.
Un sistema correcto da respuestas distintas. Los tres primeros casos son inválidos. El cuarto puede verificarse como segmento parcial si el paquete declara su punto de control inicial, pero no puede afirmar que haya verificado todo el historial. El quinto devuelve unsupported_version después de informar sobre las evidencias v1 que haya podido verificar, si el diseño de su comando permite informes parciales. No debe informar de que todo el paquete está verificado.
Aquí es donde los equipos descubren que sus diarios y paneles ocultan los límites entre fuentes. Una interfaz que combina los registros en una sola cronología puede ser adecuada, siempre que etiquete la transición de esquema y permita al revisor inspeccionar la envoltura original. No hagas que un investigador tenga que deducir un cambio de formato por la aparición repentina de un campo.
Mantén el verificador lo bastante pequeño para sobrevivir a la aplicación
El verificador de auditoría debe tener menos dependencias y privilegios que la aplicación que produce los registros. Si para leer evidencias antiguas hay que iniciar una aplicación gráfica, conectarse a una cuenta, abrir un almacén de credenciales o descargar un paquete de compatibilidad, tu plan de evidencias depende de condiciones que pueden desaparecer en el peor momento.
Separa las responsabilidades:
- La aplicación escribe los registros y presenta la actividad en directo.
- Un verificador compacto lee un paquete exportado, selecciona recetas versionadas y emite un informe legible por máquinas.
- Un renderizador puede convertir los registros verificados en tablas y cronologías sin participar en la decisión de autenticidad.
Haz que el verificador sea determinista. Con el mismo paquete y las mismas opciones del comando, debe devolver los mismos códigos de estado y la misma estructura de informe. Incluye el identificador de versión del verificador en el informe, pero no permitas que ese identificador cambie el resultado de las evidencias.
Para Sallyport, sp audit verify es la comprobación adecuada que debes conservar en el procedimiento de actualización, porque verifica sin conexión la cadena de hashes cifrada sobre el texto cifrado y no necesita la clave de la bóveda. Guarda el informe del comando junto a una exportación intacta antes de cambiar la aplicación y vuelve a verificar esa misma exportación después.
La palabra «sin conexión» requiere precisión. Significa que el verificador puede establecer el resultado de la cadena a partir de las evidencias disponibles y de sus recetas de verificación incorporadas. No significa que pueda reconstruir registros ausentes, decidir quién operó una máquina o demostrar que un usuario entendió una tarjeta de aprobación. Un informe bien diseñado dice exactamente qué afirmación ha comprobado.
Publica la especificación del formato de evidencias junto con el código fuente y las fixtures del verificador. Una versión del código sin el corpus de fixtures deja a los futuros responsables intentando adivinar la compatibilidad. Un corpus sin una receta escrita les hace preguntarse si una prueba aprobada refleja una regla deliberada o un accidente de una implementación concreta.
Define una política de retirada antes de la primera emergencia
Solo puedes dejar de admitir un formato histórico después de decidir qué ocurrirá con las evidencias que lo utilizan. Esa decisión corresponde a seguridad, asuntos legales, operaciones y las personas que investigan incidentes. No debe llegar como consecuencia accidental de eliminar un paquete antiguo.
Escribe una tabla de soporte que indique las versiones conservadas, las versiones del verificador capaces de leerlas, el periodo esperado de conservación de las evidencias y el proceso para un archivo excepcional. Si tienes previsto retirar un lector, proporciona un verificador de archivo independiente y congela antes su corpus de fixtures. Guarda sus instrucciones de compilación y sus sumas de comprobación esperadas junto con la documentación de las evidencias.
No prometas compatibilidad perpetua a la ligera. Los algoritmos envejecen, los sistemas operativos cambian y los analizadores antiguos pueden contener fallos de seguridad. Pero sí debes conservar una vía para verificar las evidencias retenidas. A veces eso significa una herramienta de archivo aislada que solo acepte archivos locales. Otras veces significa conservar una imagen de contenedor o de máquina virtual con hashes registrados. La elección depende de tu entorno; la obligación es no pedir a un investigador futuro que reconstruya de memoria una cadena de herramientas desaparecida.
La primera acción es concreta: exporta un paquete pequeño de evidencias reales, ejecuta el verificador y anota todas las reglas versionadas de las que dependía el comando. Si no puedes describir esa receta y reproducir el resultado después de una actualización de prueba, todavía no tienes un plan de actualización. Tienes la esperanza de que las evidencias antiguas sigan siendo legibles.
FAQ
¿Deben migrarse los registros de auditoría en el mismo sitio cuando cambia el esquema?
Conserva los bytes originales y el verificador que los entiende. Una migración puede crear una vista actual cómoda, pero nunca debe sustituir las evidencias originales ni convertirse en la única forma de verificarlas.
¿Cuál es la diferencia entre la versión del esquema y la versión semántica de un evento?
No. Una revisión del formato cambia la representación; una revisión del evento cambia su significado. Si tratas un cambio semántico como algo meramente estético, los informes antiguos pueden parecer decir algo que nunca dijeron.
¿Dónde debe almacenarse la versión del esquema de auditoría?
Usa un campo explícito y obligatorio dentro de cada registro firmado o sometido a hash, como schema_version. El nombre del archivo, la ruta de almacenamiento o una columna de base de datos pueden servir como metadatos, pero no bastan para unas evidencias que pueden exportarse o copiarse.
¿Puede un verificador de auditoría nuevo leer registros creados antes de una actualización?
Un verificador antiguo debe rechazar las versiones desconocidas en lugar de adivinar. Un verificador nuevo debe conservar el decodificador y las reglas de verificación antiguas para cada formato de evidencias que aún prometas admitir.
¿Es seguro reescribir registros de auditoría antiguos en un formato JSON nuevo?
Solo si los bytes originales siguen disponibles y la migración se declara explícitamente no autoritativa. Guarda la representación migrada como un artefacto derivado, con el hash de origen, la versión de la herramienta de migración y una etiqueta clara.
¿Las reglas del JSON canónico resuelven los problemas de compatibilidad de los registros de auditoría?
El JSON canónico evita que diferencias aparentemente inocuas del serializador, como los espacios en blanco o el orden de las propiedades, cambien los bytes firmados. No resuelve significados de campos poco claros, campos duplicados, pérdida de precisión ni cambios no documentados en la entrada del hash.
¿Qué pruebas de migración debe conservar para siempre un sistema de auditoría?
Conserva un corpus permanente con registros válidos, casos límite, registros malformados y casos conocidos de manipulación para cada versión publicada. Ejecuta todos los verificadores compatibles contra él en la integración continua y exige el resultado esperado para cada fixture.
¿Una cadena de hashes hace seguras por sí sola las migraciones de esquema?
La cadena puede demostrar que la secuencia no se ha alterado silenciosamente según sus reglas. No puede demostrar que un analizador más reciente haya asignado el mismo significado humano a un campo antiguo. Por eso la interpretación específica de cada versión debe quedar fijada y probarse.
¿Durante cuánto tiempo deben los verificadores de formato de auditoría admitir registros antiguos?
Usa una política de soporte escrita, vinculada a las necesidades de los investigadores, las obligaciones contractuales y los periodos de conservación. Retirar un verificador antiguo es una decisión sobre el producto y las evidencias, no una tarea de mantenimiento del equipo de ingeniería.
¿Cómo verifico una exportación de auditoría de Sallyport después de una actualización?
Ejecuta sp audit verify contra una exportación de evidencias intacta antes y después de cualquier actualización de la aplicación, y conserva ambos resultados junto con la exportación. El comando verifica sin conexión la cadena de hashes cifrada de Sallyport, por lo que la comprobación no depende de abrir la bóveda.