7 min de lectura

¿Puede la verificación cifrada de logs de auditoría detectar eliminaciones?

La verificación cifrada de logs de auditoría detecta registros intermedios modificados o ausentes, pero el truncamiento del final necesita un checkpoint externo que demuestre que el historial está completo.

¿Puede la verificación cifrada de logs de auditoría detectar eliminaciones?

Una cadena de hashes puede detectar que falta un registro en medio de un archivo de auditoría, pero no puede decirte con fiabilidad que alguien recortó registros del final. Esta diferencia hace que los equipos exageren lo que demuestra su verificador y descubran durante un incidente que conservaron pruebas de integridad, pero no el historial completo.

El cifrado no cambia esa respuesta. Protege el contenido de los registros. Una cadena construida sobre bytes cifrados puede demostrar que esos bytes siguen conectados como se esperaba, aunque nadie que ejecute el verificador pueda descifrarlos. No puede demostrar que el archivo contiene todos los registros que existieron alguna vez, salvo que una prueba externa al archivo conserve un punto posterior de su historia.

NIST describe una cadena de hashes como una estructura de solo adición en la que cada bloque incluye un hash de los datos anteriores. Por eso, modificar un bloque cambia el digest que registra su sucesor. Es evidencia de manipulación, no una máquina del tiempo.

La verificación de la cadena comprueba una relación, no la integridad del historial

Un verificador comprueba si cada registro apunta al predecesor correcto y si el digest de cada registro coincide con sus bytes almacenados. Dado un primer registro de confianza, puede establecer la continuidad a través de todos los registros que siguen presentes.

Supón que un archivo contiene los registros del 1 al 100. El registro 58 incluye el digest del 57 y el 59 incluye el digest del 58. Si alguien elimina el 58 y deja todo lo demás intacto, el 59 sigue apuntando a un digest que el verificador no puede encontrar. La verificación falla en el hueco.

Ahora elimina los registros del 91 al 100. El 90 todavía apunta correctamente al 89. Nada dentro del archivo acortado dice que el 90 estuvo seguido alguna vez por diez registros más. Un verificador que empieza en el 1 y se detiene al terminar el archivo puede devolver éxito. Verificó la historia que se le presentó. No verificó que esa historia estuviera completa.

A menudo se agrupan tres afirmaciones distintas bajo «el log es resistente a manipulaciones»:

  • Los registros que sobreviven no han cambiado.
  • No se eliminó ningún registro del medio.
  • El archivo termina donde terminaba la historia original.

Cada afirmación necesita pruebas distintas. Una cadena lineal gestiona bien la primera y detecta una versión sencilla de la segunda. La tercera necesita un compromiso independiente posterior, llamado a menudo checkpoint, cabecera, sello, recibo o testigo.

Certificate Transparency hace la misma separación con otra estructura de datos. RFC 9162 indica que una prueba de consistencia de Merkle puede demostrar que un árbol nuevo solo añade elementos respecto a un árbol anterior anunciado. La cabecera del árbol anterior hace aquí el trabajo real. Sin ella, una raíz actual no dice nada sobre los registros que el operador decidió no mostrarte.

Crea un log de prueba que trate los payloads como ciphertext opaco

No practiques con un archivo de auditoría de producción. Crea un fixture desechable cuyos payloads parezcan ciphertext al verificador y prueba las eliminaciones sobre registros de bytes exactos.

El siguiente script escribe un archivo JSON Lines. Cada registro contiene un número de secuencia, un payload opaco en base64, el digest del predecesor y su propio digest. El payload son datos aleatorios de prueba, no cifrado real. Basta para esta prueba porque el verificador de la cadena solo necesita bytes opacos estables. En un log cifrado real, sustituye el registro de ciphertext serializado y sus metadatos autenticados.

# make_log.py
import base64
import hashlib
import json
import os

OUT = "audit.jsonl"
ZERO = "0" * 64


def canonical(record):
    return json.dumps(record, sort_keys=True, separators=(",", ":")).encode()


def digest(record):
    return hashlib.sha256(canonical(record)).hexdigest()

prev = ZERO
with open(OUT, "w", encoding="utf-8") as f:
    for seq in range(1, 13):
        body = {
            "seq": seq,
            "ciphertext": base64.b64encode(os.urandom(24)).decode(),
            "prev": prev,
        }
        body["hash"] = digest(body)
        f.write(json.dumps(body, sort_keys=True) + "\n")
        prev = body["hash"]

print(f"wrote 12 records to {OUT}")
print(f"head seq=12 hash={prev}")

Usa un verificador separado en lugar de confiar en que el escritor valide su propia salida. Debe rechazar números de secuencia duplicados, saltos inesperados, registros mal formados, referencias incorrectas al predecesor y hashes incorrectos. Un verificador que solo comprueba hashes tiene un punto ciego: puede aceptar un archivo reordenado si el atacante también ha reorganizado suficientes campos asociados.

# verify_log.py
import hashlib
import json
import sys

ZERO = "0" * 64


def canonical(record):
    return json.dumps(record, sort_keys=True, separators=(",", ":")).encode()


def digest_without_hash(record):
    copy = dict(record)
    supplied = copy.pop("hash", None)
    return supplied, hashlib.sha256(canonical(copy)).hexdigest()


def fail(message):
    print(f"FAIL {message}")
    raise SystemExit(1)

path = sys.argv[1]
prev = ZERO
expected_seq = 1
last_hash = ZERO

with open(path, encoding="utf-8") as f:
    for line_no, line in enumerate(f, start=1):
        try:
            record = json.loads(line)
        except json.JSONDecodeError:
            fail(f"line={line_no} invalid JSON")

        if record.get("seq") != expected_seq:
            fail(f"line={line_no} expected_seq={expected_seq} got={record.get('seq')}")

        if record.get("prev") != prev:
            fail(f"line={line_no} seq={expected_seq} predecessor mismatch")

        supplied, calculated = digest_without_hash(record)
        if supplied != calculated:
            fail(f"line={line_no} seq={expected_seq} digest mismatch")

        prev = supplied
        last_hash = supplied
        expected_seq += 1

print(f"OK records={expected_seq - 1} head={last_hash}")

Ejecuta el fixture y conserva la cabecera indicada fuera del archivo de log:

python3 make_log.py
python3 verify_log.py audit.jsonl

El resultado debería tener este aspecto, con un digest distinto en cada ejecución:

wrote 12 records to audit.jsonl
head seq=12 hash=8f...c2
OK records=12 head=8f...c2

El par final records=12 y head=... es tu checkpoint. Guárdalo en un archivo separado de notas de prueba antes de modificar copias. Si dejas el checkpoint solo en el archivo que quieres atacar, has creado un registro cómodo de lo que el atacante puede editar.

Eliminar el final muestra enseguida el límite

La eliminación del final es la prueba que debería hacer que todos sean prudentes con la palabra «completo». Copia el archivo, elimina las tres últimas líneas y ejecuta el mismo verificador.

cp audit.jsonl tail-cut.jsonl
head -n 9 audit.jsonl > tail-cut.jsonl
python3 verify_log.py tail-cut.jsonl

El verificador informará de que nueve registros son válidos. Debe hacerlo. Los registros del 1 al 9 todavía forman una cadena válida. Llamar a esto un fallo del verificador fomentaría un diseño peligroso en el que los historiales parciales válidos parecen corruptos.

Compara el resultado con el checkpoint:

OK records=9 head=4a...91
expected records=12 head=8f...c2

Ahora tienes pruebas de truncamiento. La prueba procede del desacuerdo con una evidencia capturada cuando el log era más largo, no de los enlaces internos del archivo acortado.

Un número de secuencia ayuda a diagnosticar el problema, pero no crea seguridad por sí solo. Un atacante que pueda reescribir el final puede cambiar el número final de 12 a 9. El número resulta útil cuando el valor esperado proviene de una fuente que el atacante no puede reescribir, como un checkpoint firmado almacenado por otro servicio, un artefacto de release protegido o un export que llegó a otro dominio administrativo.

Esta es la prueba que muchos diseños de auditoría omiten porque parece demasiado obvia. También es el fallo que importa después de una acción destructiva. Un proceso malicioso no necesita volver internamente incoherente la historia. Solo tiene que hacer que termine lo bastante pronto para ocultar la acción.

Una eliminación intermedia debería fallar, pero solo bajo condiciones concretas

Haz otra copia y elimina un registro que tenga tanto predecesor como sucesor. El registro 6 es un buen objetivo.

cp audit.jsonl middle-cut.jsonl
sed '6d' audit.jsonl > middle-cut.jsonl
python3 verify_log.py middle-cut.jsonl

El resultado debería tener este formato:

FAIL line=6 expected_seq=6 got=7

Si eliminas la comprobación de secuencia, el verificador fallará una prueba después, porque el registro 7 contiene el digest del 6 mientras el verificador tiene el digest del 5. Conserva ambas comprobaciones. El desajuste de secuencia hace que el problema resulte evidente para el operador y el desajuste del predecesor muestra qué relación criptográfica se rompió.

No afirmes que una cadena de hashes siempre detecta la eliminación intermedia. Detecta una eliminación sencilla cuando el atacante no puede reescribir la cadena restante. Cualquiera puede calcular nuevos hashes con un algoritmo público. Si alguien puede eliminar el registro 6, cambiar el campo de predecesor del 7, recalcular su hash y repetir el proceso hasta el 12, el archivo reconstruido puede verificarse contra su propia cabecera nueva.

Esto no es un ataque de colisión ni una ruptura de SHA-256. Es un recálculo normal. El sistema aceptó una historia nueva porque no tenía pruebas protegidas de que la historia anterior hubiera existido.

La orientación de NIST sobre pruebas digitales expresa claramente el punto operativo: un hash almacenado debe vivir donde las personas con acceso a las pruebas no puedan alterarlo ni sobrescribirlo. El mismo informe considera útiles las cadenas de hashes para proteger hashes, pero sigue siendo necesario guardar externamente el valor de referencia.

Por tanto, una prueba de eliminación intermedia tiene dos versiones:

  1. Elimina una línea y deja intactos los bytes posteriores. La cadena debe fallar.
  2. Elimina una línea y regenera todos los digests posteriores. La cadena reconstruida debe fallar frente a un checkpoint conservado de forma independiente.

Si tu plan solo ejecuta la primera versión, prueba daños en el archivo y manipulaciones descuidadas. No prueba a un atacante que puede escribir en el almacenamiento del log.

El cifrado y la integridad de la cadena responden preguntas distintas

Detén las acciones en la puerta del vault
La puerta del vault rechaza toda acción mientras está bloqueada, con Secure Enclave y Touch ID en macOS.

Los registros cifrados crean una división útil de responsabilidades. Los operadores pueden verificar la cadena sobre ciphertext sin leer secretos, cuerpos de solicitudes, salidas de comandos ni otros contenidos sensibles de auditoría. Los investigadores con el acceso adecuado pueden descifrar después los registros relevantes. Es un diseño razonable cuando los propios datos de auditoría son sensibles.

Pero los bytes cifrados no certifican su propio origen. Un registro de ciphertext puede eliminarse. Alguien que controle la ruta de cifrado y registro puede añadir otro. Si el modo de cifrado no autentica los datos, a veces puede alterarse un ciphertext hasta convertirlo en basura sin que el descifrador sepa por qué. Usa cifrado autenticado para la confidencialidad e integridad de cada registro y usa después la cadena para unirlos en una historia ordenada.

Separa estas comprobaciones al redactar el diseño y el informe del incidente:

  • La autenticación del registro pregunta si un registro cifrado fue alterado.
  • La verificación de la cadena pregunta si cada registro que sobrevive sigue al predecesor declarado.
  • La comparación con un checkpoint pregunta si la historia observada alcanza una cabecera observada anteriormente.
  • La captura de eventos pregunta si el sistema llegó a escribir la acción en el log.

La última pregunta es incómoda. Ningún log criptográfico puede demostrar que un evento se registró si un escritor comprometido decidió no emitirlo. Puedes reducir esa exposición recopilando pruebas en fronteras independientes, como un servicio de red, una instalación de auditoría del host o una aprobación humana. No puedes eliminar la brecha llamando append-only al log local.

El modelo de auditoría de Sallyport resulta útil aquí porque sus journals de ejecuciones de agentes y llamadas individuales proceden de un único log de auditoría cifrado y encadenado con hashes, y sp audit verify puede verificar sin conexión la cadena de ciphertext sin una clave del vault. Así, un revisor puede comprobar la continuidad sin exponer las credenciales ni los datos de acción que protege el historial.

Un checkpoint debe ser difícil de reescribir, no solo una copia

Un checkpoint es una declaración sobre un log en un momento concreto. Como mínimo, incluye un identificador del log, el número de secuencia final, el digest final, la hora de creación y la versión del formato o algoritmo. Trátalo como un artefacto separado con su propia custodia.

Este formato pequeño basta para un arnés de pruebas:

{
  "log_id": "agent-actions-test-a",
  "sequence": 12,
  "head": "8f...c2",
  "created_at": "2026-07-22T14:30:00Z",
  "format": "jsonl-chain-v1"
}

No confíes por sí sola en la propiedad created_at. Un reloj local sirve para ordenar e investigar, pero un atacante que controle la máquina puede controlar el reloj y el archivo que contiene el valor. El checkpoint obtiene su fuerza del lugar al que se envió y de quién puede cambiarlo.

Una disposición práctica hace que una parte escriba los registros de auditoría y otra conserve cabeceras periódicas. La segunda parte no necesita acceso para descifrar. Necesita información suficiente para rechazar un archivo posterior cuyo digest final y número de secuencia no coincidan con lo que vio antes.

En una herramienta para desarrolladores, la parte independiente puede ser modesta: un artefacto protegido de CI, un sistema de attestations de release, una cuenta de recopilación separada o un export diario aprobado por otro administrador. Para acciones de mayor riesgo, emite el checkpoint inmediatamente después de la acción y consérvalo fuera de la estación de trabajo. La frecuencia debe seguir la ventana de daño que puedas aceptar. Un checkpoint diario no puede demostrar que el historial está completo durante las horas entre la cabecera de ayer y el final perdido de hoy.

Firmar un checkpoint ayuda cuando los verificadores necesitan saber quién lo emitió. La firma vincula los datos del checkpoint con la autoridad firmante, pero no hace honesto al firmante. Si el mismo proceso comprometido escribe el log y firma checkpoints de reemplazo, sigues teniendo una sola frontera de confianza. Coloca el testigo donde el escritor original no pueda controlarlo en silencio.

El almacenamiento write-blind cambia el atacante que estás probando

Ejecuta acciones de agentes sin exponer claves
Sallyport devuelve los resultados al agente sin entregarle claves de API ni claves SSH en texto plano.

Una cadena almacenada en un lugar donde el escritor puede editar libremente registros antiguos tiene un modelo de amenazas distinto del de una cadena almacenada mediante una ruta de adición write-blind. El primer diseño necesita testigos externos para detectar reescrituras. El segundo intenta impedir que el escritor las haga.

Sé preciso con la expresión «write-blind». Debe significar que el componente que envía un registro nuevo no puede leer ni modificar a voluntad los registros cifrados anteriores. No significa que el disco no pueda fallar, que un administrador no pueda eliminar un archivo o que el sistema operativo no pueda verse comprometido. Reduce una vía de ataque: reescribir una parte concreta de la historia después de haberla visto.

Esa diferencia afecta a los casos de prueba. Para el acceso normal a archivos, prueba la eliminación seguida del recálculo de hashes, porque un atacante puede leer los datos y calcular hashes. Para un almacén write-blind, prueba si un usuario puede solicitar una eliminación, sustitución, reversión o un segundo log con la misma identidad. Prueba también cómo identifica el verificador el log correcto. Una cadena perfecta del archivo equivocado sigue siendo una prueba equivocada.

El error más habitual es tratar los permisos de almacenamiento como un ancla criptográfica. Un atacante con privilegios suficientes puede cambiar los permisos. Siguen siendo importantes porque reducen los daños casuales y limitan qué procesos pueden actuar, pero no sustituyen un checkpoint que haya cruzado una frontera que el atacante no controle.

Prueba la reversión por separado del truncamiento

Una reversión parece un truncamiento del final con un archivo antiguo plausible. Una máquina puede restaurar el archivo de auditoría válido de ayer después de un fallo, una restauración de backup o un reemplazo deliberado. El archivo restaurado puede pasar su verificador interno porque ayer era realmente válido.

Tu checkpoint detecta esto si registra una cabecera más nueva. Compara el archivo candidato con el checkpoint más reciente conservado, no con el más antiguo que encuentres. Un verificador de cadenas no puede inferir qué versión válida debería ser la actual.

Pruébalo directamente:

  1. Guarda audit.jsonl y su checkpoint de 12 registros.
  2. Genera otro fixture con más registros y conserva su checkpoint más reciente.
  3. Sustituye el archivo más nuevo por la copia de 12 registros.
  4. Verifica internamente el archivo restaurado y compáralo después con el checkpoint más reciente.

Espera dos resultados distintos. La verificación interna debe tener éxito. La comparación con el checkpoint debe fallar porque el archivo termina en una secuencia y un digest antiguos. Si fallan ambas comprobaciones, quizá tu fixture haya cambiado bytes en vez de reproducir una reversión. Si ambas tienen éxito, comparas con el checkpoint equivocado o no conservaste ninguno.

No «corrijas» una reversión añadiendo registros nuevos al archivo antiguo y continuando. Conserva primero el estado. Al añadir algo, mezclas una historia potencialmente incompleta con actividad posterior al incidente. Empieza un segmento de log nuevo con una frontera de incidente registrada o sigue el procedimiento de conservación y recuperación aprobado por tu organización.

Haz que los resultados del verificador sirvan durante una investigación

Consulta cada acción del historial
Su journal Activity registra cada acción HTTP y SSH desde el mismo log de auditoría cifrado.

Un verificador que solo devuelve invalid crea trabajo innecesario. Debe identificar la primera condición fallida sin imprimir los payloads sensibles de los registros. El número de secuencia, el desplazamiento de bytes o número de línea, el digest esperado del predecesor, el observado y el digest calculado del registro suelen aportar suficiente detalle.

Evita registrar contenido descifrado en un mensaje de error. Una herramienta de verificación suele ejecutarse en CI, en un paquete de soporte o en una captura del terminal. Si un diseño de auditoría cifrada filtra texto claro cada vez que falla la verificación, convierte un incidente de integridad en una exposición de datos.

Una respuesta disciplinada ante un fallo de verificación sigue un orden breve:

  • Conserva el archivo original y registra el digest de esa copia.
  • Recopila el checkpoint más reciente, los anteriores y cualquier export externo.
  • Ejecuta la verificación sobre copias y guarda la salida exacta del comando.
  • Compara la cabecera indicada con todos los checkpoints conservados.
  • Determina si el fallo es corrupción, un hueco intermedio, pérdida del final, reversión o una cadena reescrita que entra en conflicto con un testigo.

La clasificación importa. Un fallo por hueco intermedio indica una contradicción interna. Una cadena válida pero más corta indica que el archivo quizá solo esté completo hasta su último registro. Una cadena válida que no coincide con un checkpoint posterior aporta pruebas de reversión, truncamiento o sustitución. Una cadena reescrita puede parecer limpia por dentro, pero no coincidirá con una cabecera independiente anterior.

No prometas más de lo que permiten las pruebas. «La cadena de auditoría se verificó hasta la secuencia 90 y no coincide con el checkpoint conservado de la secuencia 100» es una afirmación sólida y específica. «Nadie cambió el log» no lo es.

La prueba que cuenta es la que se enfrenta a tu frontera real

Ejecuta primero las pruebas desechables de eliminación de líneas, porque enseñan la mecánica. Después repite los mismos casos contra el export de auditoría real y su verificador real, usando copias y los límites de manipulación documentados por el formato. No edites un log cifrado de producción en el mismo sitio solo para ver qué ocurre.

En Sallyport, conserva el log cifrado original y usa sp audit verify sobre una copia antes y después de cada alteración controlada. Registra si el verificador detecta un hueco interno, si una copia acortada sigue verificándose y si tu cabecera conservada revela la historia más corta. La respuesta a las tres preguntas es más útil que una afirmación vaga de que el log ofrece evidencia de manipulación.

Una cadena válida significa que los registros comprobados coinciden entre sí. Una cadena válida más un checkpoint posterior de confianza significa que los registros alcanzan un punto conocido de la historia. Diseña y prueba para la segunda afirmación cuando importe detectar acciones ausentes.

FAQ

¿Qué demuestra realmente la verificación de auditoría con una cadena de hashes?

Demuestra que los registros que sobreviven forman la misma secuencia de bytes que el verificador espera desde su punto de inicio o checkpoint de confianza. No demuestra que nadie haya eliminado un sufijo válido, salvo que hayas conservado en otro lugar pruebas de una cabecera posterior.

¿Puede una cadena de hashes detectar la eliminación de un registro intermedio?

Detectará la eliminación de un registro intermedio si alguien simplemente lo quita y deja intacto el registro siguiente. Ese registro todavía apunta al hash que falta y la cadena se rompe. Un atacante que pueda reescribir los registros posteriores y recalcular una cadena no autenticada debe quedar limitado por un checkpoint externo, una firma, un MAC o un almacenamiento que impida la reescritura.

¿Puede una cadena de hashes detectar registros eliminados del final?

Una cadena sin más normalmente no puede hacerlo. Si el verificador solo lee el archivo acortado y no tiene una cabecera posterior guardada, el último registro que queda parece un final normal del archivo. Un checkpoint capturado después del registro eliminado cambia la situación, porque el archivo acortado ya no puede alcanzar la cabecera registrada.

¿Cifrar un log de auditoría impide que se eliminen registros?

El cifrado oculta el contenido de los registros a quienes pueden leer el archivo pero no tienen el material de descifrado. No hace que la cadena esté completa ni impide que un atacante elimine registros de ciphertext opaco. Integridad y confidencialidad son propiedades distintas.

¿Dónde deben guardarse los checkpoints de un log de auditoría?

Guárdalo en un lugar donde la persona o el proceso que puede modificar el archivo de auditoría tampoco pueda modificarlo en silencio. Un export firmado, un recopilador independiente, un artefacto de release protegido o un segundo dominio administrativo pueden servir. El checkpoint debe incluir suficiente contexto, como la identidad del log, el número de secuencia, la marca de tiempo y el digest de la cabecera.

¿Un log de auditoría verificado demuestra que se registró cada acción?

No. Una cadena válida indica que los registros que quedan no han sido alterados con respecto a las pruebas comprobadas. No demuestra que el sistema registrara todas las acciones, que un escritor comprometido no omitiera un evento antes de registrarlo ni que la persona detrás de un proceso estuviera autorizada.

¿Cómo puedo probar la verificación de auditoría de Sallyport de forma segura?

Usa el verificador exacto del formato del log antes de manipular copias para la prueba. Sallyport ofrece sp audit verify, de modo que el equipo puede verificar sin conexión el log de ciphertext cifrado sin entregar material del vault al verificador. Conserva el original intacto y anota el comando, el digest del archivo, la fecha de la prueba y el resultado.

¿Cuándo conviene usar un árbol de Merkle en lugar de una cadena de hashes?

Una cadena de hashes enlaza los registros linealmente, por lo que es sencilla de añadir y revisar, pero necesita un ancla para revelar la pérdida del final. Un árbol de Merkle puede producir pruebas eficientes de inclusión y consistencia para logs grandes, como describe Certificate Transparency, pero también necesita conservar cabeceras del árbol y testigos para demostrar la historia a quien no observó su crecimiento.

¿Qué puede decirme un verificador de auditoría después de fallar?

Puede indicar que los bytes no coinciden con la secuencia de registros esperada y señalar el primer registro cuya referencia al predecesor falla. No puede decirte quién eliminó el registro, si la eliminación fue accidental ni si el sistema de origen omitió un evento antes de escribir el log.

¿Qué debo hacer si falla la verificación de una auditoría cifrada?

No repares el original. Conserva una copia byte por byte, calcula el digest del archivo, recopila el último checkpoint conocido y cualquier cabecera exportada, y después verifica copias documentando cada comando. Si el log cubre acciones sensibles para la seguridad, trata el fallo como una prueba que requiere investigación, no como un simple error de mantenimiento.

Sallyport

Sallyport ejecuta llamadas de API y comandos SSH por tu agente de IA. Las claves se quedan en una bóveda local de tu Mac; tú apruebas cada ejecución y cada acción queda en un registro sellado.

© 2026 Sallyport · Código abierto bajo Apache-2.0 · Oleg Sotnikov