Blog

Tamper-Evident Audit Logs: What SOC 2 and FedRAMP Auditors Actually Check

Every security platform claims to keep an audit log. What most of them actually keep is an application log with a timestamp column. The distinction matters when an auditor asks: "Prove that this record wasn't modified after it was written." For most systems, the honest answer is we can't. For any system that stores records in a mutable database without integrity protection, that answer is unavoidable.

SOC 2 Common Criteria 7.2 and 7.3, ISO 27001 A.12.4.2 and A.12.4.3, PCI DSS 10.5, HIPAA 164.312(b), and FedRAMP AU-9 all require audit trail integrity controls. They use different language and different checklists, but they converge on the same question: can you detect whether an audit record has been modified, deleted, or inserted after the fact? This post walks through the mechanism that gives you a "yes" answer, and what auditors actually verify when they review your controls.

The difference between an audit log and an application log

Application logs are operational telemetry: "request X took 40ms, returned 200, user Y." They're voluminous, noisy, and optimized for debugging. If one gets rewritten during a disk hiccup or a log rotation, nobody notices.

Audit logs are evidence. They record security-relevant events — who authenticated, who accessed which secret, who granted permission, who revoked access, who logged in to which host. They're queried during incident response, during compliance audits, during subpoena response. If one record is subtly altered — a timestamp changed, an actor swapped, a "denied" flipped to "allowed" — downstream decisions are wrong and nobody can tell.

The technical test for whether your audit log is actually an audit log: can you detect a one-character change in any historical record? If the answer is no, it's an application log.

Hash-chained audit logs

The standard mechanism for tamper-evidence is hash chaining, borrowed from blockchain without the blockchain. Each audit event carries a hash field computed over its own content plus the hash of the previous event:

event[N].hash = SHA256(event[N].fields || event[N-1].hash)

With this structure, flipping a single byte in event N invalidates not only N's hash but every subsequent event's hash. An attacker who modifies one record has to rewrite every record after it, recompute every hash, and then smuggle the rewritten tail back into the database. Verifying the chain — recomputing each hash and comparing — catches any change.

How tampering breaks a hash chain The top row shows three linked audit events, each hash computed over its own fields plus the previous event's hash. The bottom row shows the same chain after event two is altered: its recomputed hash no longer matches the value event three stored as its previous hash, so verification fails at that link and at every link after it. Intact chain event 1 prev: 0000… hash: a41f… event 2 prev: a41f… hash: 7c02… event 3 prev: 7c02… hash: e9b8… After event 2 is altered event 1 prev: 0000… hash: a41f… event 2 (modified) prev: a41f… hash now: 3d5a… mismatch event 3 prev: 7c02… expects old hash
Event 3 stored event 2's original hash. Once event 2 changes, its recomputed hash no longer matches what event 3 recorded, and verification fails at that link and everywhere downstream. Rewriting one row means rewriting the entire tail.

A few things have to be right for this to work:

What auditors actually check

Having a hash chain is necessary. It's not sufficient. Auditors ask for specific artifacts:

1. The hash algorithm and its properties

Is it SHA-256 or stronger? SHA-1 is a red flag — collisions are practical and auditors know it. MD5 is a fail. The answer should be SHA-256 minimum; SHA3-256 or BLAKE3 earns extra credit.

2. The input to the hash

A hash of "just the timestamp and action" is weaker than a hash of every field. Auditors want to see the full canonical serialization of the event as hash input, including actor, resource, IP, user agent, status, and details payload. Anything not in the hash input is a field an attacker can silently modify.

3. The chain verification schedule

"We could verify the chain" isn't the answer. The answer is a scheduled job, its last run time, its alert destination, and its runbook when it fails. For SOC 2, auditors will ask for evidence that the job ran during the audit period and that failures were investigated.

4. The write path's atomicity

How does the system ensure the previous-hash read and the new-event write happen atomically? If there's a race window, an attacker with DB access can insert an event between read and write and break the chain's ordering. Auditors will ask to see the transaction isolation level and the locking strategy.

5. The retention policy

Audit records have to be retained for the full audit period (SOC 2: 12 months minimum, often 24; FedRAMP: 3 years for High baseline; HIPAA: 6 years). Deletion before expiry is a finding. "Archived to cold storage" counts as retention only if the archive is also tamper-evident.

6. The access controls on the audit log

Who can read it? Who can write to it? The answer should be: the application writes via a tightly-scoped DB user, and only platform admins can read through the UI. Nobody should have direct DB write access to the audit table — if a DBA can silently insert rows, the hash chain just moves the trust problem from "the application is honest" to "the DBA is honest."

7. The tampering-response procedure

When chain verification fails, what happens? A mature control has: alerting (PagerDuty, Slack, email), an incident response runbook, a forensic capture mechanism (snapshot the DB, isolate affected tenant), and a documented communication path to affected customers if the failure is confirmed. "We'd figure it out" is not an answer.

What CoreLink does

CoreLink's audit subsystem (full compliance surface) writes every security event — 216+ event types covering authentication, secret access, permission grants, session starts, policy changes, and NHI attestations — through a hash-chained path:

The design is deliberately boring. No Merkle tree, no distributed ledger, no PQ crypto. Just SHA-256 chained events in a relational database, verified on a schedule. Auditors have seen this pattern before — it's the same mechanism used by regulated financial systems for decades — and they know how to review it.

What a failing chain actually tells you

When VerifyChain returns false, one of three things happened:

  1. Someone modified the database directly. An admin ran an UPDATE against audit_events. This is the attack case; escalate immediately.
  2. A row was deleted. Either malicious or a well-intentioned "cleanup" that wasn't actually safe. Same escalation path.
  3. There's a bug in the write path. The chain broke because a concurrent write used the wrong predecessor hash. This is less serious but still a production bug; the chain needs to be re-anchored (usually by inserting a new "chain recovery" event and carrying on).

Which one is which is answered by the forensic snapshot: look at the divergent event, compare against the expected hash, cross-reference against application logs for that time window. If the application never wrote the event that's in the database, it's case 1 or 2. If it did, it's case 3.

What about external attestation?

For the highest-assurance environments (FedRAMP High, DoD IL5, some financial regulators), in-system hash chains aren't enough. Auditors want external attestation: periodic submission of the chain's current head hash to a third-party timestamping service (RFC 3161) or a public blockchain so the platform can't alter history even with full root access.

This is a reasonable feature to add if you're chasing those compliance regimes. It's overkill for SOC 2 Type II and ISO 27001. The internal hash chain is the floor; external attestation is the ceiling, and you only need it when your regulator asks for it.

Where to start

If you're evaluating or building an audit log:

  1. Confirm the hash algorithm (SHA-256+) and the input fields.
  2. Verify the write path is atomic — serialize isolation or better.
  3. Run the verification job and check it actually detects a planted tamper (insert a test event, modify it, confirm the verify fails).
  4. Document the schedule and the escalation path.
  5. Restrict DB write access to the audit table to the application's service role only.

The payoff isn't just the audit. It's the operational peace of mind that comes from knowing that if something bad happened to your security state, you can prove exactly what, when, and by whom — and prove that the record of it hasn't been rewritten.

The compliance use-case page has the full scan / report / export surface; the platform overview covers the audit-adjacent features (session recording, SIEM forwarding) that compound the evidence story.