Entry 013Demos7 min readBy Mandare Labs

How to detect deleted or truncated audit log entries

A hash chain with signatures exposes an entry edited or removed in the middle of a log. It does not expose the newest entries being cut off, or a rewrite re-signed with a stolen key. Those need a reference the log's holder cannot change: a tree head (size and root) kept by someone else, checked against a key you hold out-of-band.

A hash chain proves order, not completeness

A log that hashes each entry into the next and signs the result makes some edits visible. Which ones depends on where the attacker cuts and what they hold. Four cases are worth telling apart:

What was done to the log fileWhat the file shows on its ownWhat catches it
One entry in the middle editedThe chain breaks at that entryRe-hashing the chain
Entries in the middle deleted or reorderedA gap or a broken linkRe-hashing the chain
Newest entries removedA shorter chain that is internally validA head recorded earlier, with a larger size
One entry edited, every later hash and signature recomputed with the real keyA chain where every hash and signature checks outA head recorded earlier, which the new tree does not extend

The last two rows are the reason a log needs a reference outside itself. A verifier that reads only the file cannot see them, because the file is the thing under suspicion. A signature proves that someone holding the key wrote the history. It does not prove that the history is complete or that it is the one originally written.

A recorded head turns "valid" into "consistent with what was seen"

A head is a pair: the number of entries and one root hash over all of them. RFC 6962 defines the tree and names the way violations get caught. Section 7.3 reads: "Violation of the append-only property is detected by global gossiping, i.e., everyone auditing logs comparing their versions of the latest Signed Tree Heads." It adds: "As soon as two conflicting Signed Tree Heads for the same log are detected, this is cryptographic proof of that log's misbehavior."

Applied to one log and one recorded head, the comparison has three outcomes:

Recorded head against the log as it is nowMeaning
The log has fewer entries than the recorded sizeEntries were removed
The log has as many entries or more, and its tree does not contain the recorded root as its earlier partHistory was rewritten
The log's tree extends the recorded oneConsistent: entries were appended, none changed

The mechanics of the tree and of the consistency proof, with worked hashes, are in RFC 6962 Merkle trees and consistency proofs, explained. The operating rule that follows is simple: record the head after every verification run, and keep it where the log's operator cannot edit it. A head written next to the log file protects against accidents. A head recorded after the damage says nothing about the damage.

CloudTrail ships this for AWS API activity

AWS CloudTrail is the managed version of the idea, and a good place to start if the activity you need to prove is what CloudTrail records. Its documentation, read on 13 October 2026, describes the mechanism: "Every hour, CloudTrail also creates and delivers a file that references the log files for the last hour and contains a hash of each. This file is called a digest file." CloudTrail signs each digest file. "Each digest file also contains the digital signature of the previous digest file if one exists." That is a chain of signed hourly heads.

The validation command takes a trail and a time range:

aws cloudtrail validate-logs --trail-arn <trailARN> --start-time <start-time> [--end-time <end-time>] [--verbose]

The CLI page lists what it can detect: "Modification or deletion of CloudTrail log files", "Modification or deletion of CloudTrail digest files", and "Modification or deletion of both of the above". A failing run looks like this in the documentation's sample output:

aws cloudtrail validate-logs, sample output from the AWS documentationabbreviated
Digest file    s3://…/CloudTrail-Digest/…20150901T101728Z.json.gz    INVALID: signature verification failed
…
22/23 digest files valid, 1/23 digest files INVALID
63/63 log files valid

Three limits are stated in the same documentation, and they are the useful part for a reader choosing a design:

For hardening the digests, the overview page suggests Amazon S3 MFA Delete and keeps digests in a separate folder "to enforce granular security policies". CloudTrail is the better choice when the records you care about are the ones CloudTrail delivers: it is managed and signed by CloudTrail. It does not cover logs that your own gateway or application writes.

A witness does the recording for logs you run yourself

For a log you operate, the recorded head has to come from somewhere. The C2SP witness protocol specifies a service that does exactly this. Its introduction says the protocol "allows clients, usually logs, to obtain cosignatures from witnesses". The rule that matters: "Witnesses verify that the checkpoint is consistent with their previously recorded state of the log (if any), and return a timestamped cosignature."

The failure cases are explicit. "The witness MUST check that the old size matches the size of the latest checkpoint it cosigned for the checkpoint's origin (or zero if it never cosigned a checkpoint for that origin). If it doesn't match, the witness MUST respond with a "409 Conflict" HTTP status code." And: "If a Merkle Consistency Proof doesn't verify, the witness MUST respond with a "422 Unprocessable Entity" HTTP status code." A rewrite is therefore refused when it is submitted, not only noticed later.

The spec names the property being protected: "it must not be possible to partition clients from monitors, either by splitting the tree or by serving a stale view to monitors."

A witness that the log's operator also runs adds little, because whoever can rewrite the log can rewrite the witness's records. Witnesses run by other parties, and more than one, are the stronger arrangement for a log that outsiders have to trust. A single self-run witness on a separate machine is weaker, and still closes the gap that a file-only check leaves.

The verifier must choose the reference, not the file

The C2SP protocol protects the witness by configuration: "Each witness is configured with a list of supported log public keys", it "MUST ignore signatures from unknown keys", and an unknown origin gets "404 Not Found". That leaves the verifier's side. A witness holds history per log identity. If the verifier takes the identity from the file under test, an attacker can point the file at an identity whose recorded history is short and clean. On any witness that accepts new log identities, the truncated tree can itself be submitted as a new log. The truncated file then agrees with its own copy.

Two rules close that:

  1. The verifier looks the history up under a key or origin it obtained out-of-band, never the one the file declares.
  2. An unreachable witness is a failure. "Cannot rule out truncation" must not print as success.

The rest of this entry shows what that looks like in one implementation.

Where Mandare fits, and what it does not cover

Mandare's open-source door streams each RFC 6962 tree head of its ledger to an external witness as the ledger grows. Per the witnessing document, a submission is a tree size and a 32-byte root, and the witness "REFUSES anything that is not a provable append-only extension". The check is one command, with the door key passed out-of-band so the lookup is bound to your door:

mandare verify --db ledger.db --witness https://witness.example --witness-key <hex> \
  --door-key <door-public-key-hex>

The CLI reference lists the outcomes: CONSISTENT, TRUNCATION DETECTED, FORK DETECTED, SOURCE MISMATCH, TIMELINE VIOLATION and UNAVAILABLE. The demo transcript stages the worst local case, an attacker who has the ledger file and the door key. This is the rewrite (copy B) and the truncation re-witnessed under a fresh source (copy C):

docs/demos/S6-witness-demo.txtrepo transcript
[attacker] copy B: entry 4 cost rewritten €1.00 → €0.000001, chain re-signed
…
[verify]  rewritten copy, self-anchored:      chain VALID — the lie is locally perfect
[verify]  rewritten copy, --witness:      FORK DETECTED (exit 1)
[attacker] copy C: newest 2 DROPPED (no key needed), the truncated tree re-witnessed
           under a FRESH source, and the file's door_key_id repointed at it
[verify]  copy C, --door-key + --witness:  SOURCE MISMATCH + TRUNCATION DETECTED (exit 1)

The transcript adds that in copy C "the witnessed history consulted is the out-of-band door key's", not the file's. The same run checks an honest copy with the same flags and gets CONSISTENT with exit code 0, so the check does not cry wolf.

This applies to CLI v0.1.0, which npm view @mandarelabs/cli version reported on 13 October 2026, and to the public repository at commit 327442e (3 October 2026). The limits, next to the claims they limit:

For why the witness never sees log content, see proofs, not data.

Questions

How do you detect deleted entries in an append-only log?

Compare the log against a head recorded earlier by a party who cannot rewrite the log. A smaller size than the recorded one means entries were removed. A tree that does not contain the recorded root as its earlier part means history was rewritten. The log file alone cannot show either.

Does a hash chain detect that the newest entries were removed?

No. Removing the newest entries leaves a shorter chain in which every hash and signature still checks out. Nothing inside the file records that more entries existed, so detection needs a size or root recorded somewhere else.

Can AWS CloudTrail detect deleted log files?

Yes, for files it delivered and referenced in a digest file. Its documentation lists modification or deletion of log files, of digest files, or of both. The AWS CLI validates only log files that are referenced by digest files, and a missing digest leaves its time range unvalidated.

What is a transparency log witness?

A witness is a service that keeps the latest checkpoint it cosigned for each log and cosigns a new checkpoint only if it is consistent with that record. The C2SP witness protocol specifies the HTTP interface. The witness has to be run by someone other than the log operator to add anything.

Should a verifier pass when the witness cannot be reached?

No. If the reference is unreachable, truncation cannot be ruled out, so the result is a failure, not a pass. CloudTrail's validator does the same for a missing digest file: the time range it covered is reported as not validated.

Sources

  1. Validating CloudTrail log file integrity · Amazon Web Services
  2. Validating CloudTrail log file integrity with the AWS CLI · Amazon Web Services
  3. Transparency Log Witness Protocol · C2SP
  4. RFC 6962: Certificate Transparency · IETF / RFC Editor · 2013-06-01

Run it yourself: 3 commands, no API keys.

github.com/mandarelabs/mandare →
← All journal entries