Skip to content

Audit Trail and Tamper Detection

A signed contract is only as defensible as the records that surround it. FoxCLM stores three independent integrity proofs, each guarding against a different threat. Understanding what they cover (and don't) helps you answer "could this have been tampered with?" with precision.

Three things, three threats

MechanismWhat it protectsFrozen at
Document hash (contract_signatures.signed_document_hash)The contract body itself - the words a signer agreed toSign time, one per signature
Workflow snapshot hash (contract_history.workflow_snapshot_hash)The workflow rules in force when an event firedEvent time, one per history row
History hash chain (contract_history.prev_hash + row_hash + row_hmac)The log of events itself - sequence, actors, timestampsEach row links to the prior

Three different "movies" of the same contract: what was signed, what rules applied, what actually happened. Tampering with any one is detected independently.

Why one hash isn't enough

It's tempting to think "hash the contract body and you're done". But that only proves the document didn't change. It says nothing about what surrounded the signature.

Consider an attacker with database write access. With only a body hash:

  • They could delete a rejected event and insert a signed event. The body hash still matches - it was never about events.
  • They could backdate a signature to before a critical amendment.
  • They could swap the actor_users_id on an existing row to frame a different user.
  • They could reorder events to make a recall-and-revise look like a clean first sign.
  • They could edit the workflow so that a status that used to require admin approval no longer does, then claim the contract followed the rules.

Each of those attacks leaves the contract body untouched. Each is caught by one of the other two mechanisms.

Document hash - what was signed

When a signer clicks Sign and submit, FoxCLM:

  1. Renders the contract HTML the signer just agreed to
  2. Sanitizes it (strips scripts, normalizes whitespace)
  3. Computes SHA-256 over the sanitized bytes
  4. Stores the digest in contract_signatures.signed_document_hash

If the contract body is later edited, the hash on file no longer matches the current document. The Verify Integrity button on the contract page recomputes the hash and reports per-signature match status.

This is a static proof. One contract, one body, one hash per signer.

Workflow snapshot - what rules applied

The status flow you build under Workflow is editable. That's a feature: rules evolve. But it creates a tension with audit defensibility - if you edit the workflow today, can someone still trust what the workflow looked like when last year's contract was signed?

FoxCLM resolves this with per-event snapshots. Every time something happens to a contract (status change, signature, recall), FoxCLM:

  1. Captures the current workflow definition (statuses + transitions + conditions + actions for contracts)
  2. Computes SHA-256 over a canonical JSON representation
  3. Stores the snapshot once in contract_workflow_snapshots (deduplicated across contracts and tenants by content hash)
  4. References it from the new history row via workflow_snapshot_hash

So each row in contract_history carries a fingerprint of the workflow that authorized that specific event. Edit the workflow tomorrow - last year's events still point to last year's snapshot, byte-identical to what existed then.

The verify endpoint also recomputes the hash of the stored snapshot JSON, so direct DB tampering of snapshot rows is also caught.

History hash chain - what actually happened

This is the most subtle layer, and the one most analogous to a courtroom evidence log.

Every row in contract_history carries:

  • prev_hash - the row_hash of the previous row for the same contract
  • row_hash - SHA-256 over the row's canonical content (action, timestamps, actor, IP, user-agent, the snapshot hash, etc.) plus prev_hash
  • row_hmac - HMAC-SHA256 over row_hash, signed with a key the database doesn't have

Each row is cryptographically tied to its predecessor. To rewrite history, an attacker would need to:

  1. Edit the target row
  2. Recompute its row_hash
  3. Update the next row's prev_hash
  4. Recompute that row's row_hash
  5. Cascade forward to the end of the chain
  6. Re-sign every recomputed row_hmac

Steps 1-5 require only DB write access. Step 6 requires the HMAC signing key, which lives outside the database (env var or AWS KMS - see Audit chain signing key for deployment options).

Without that key, the chain breaks loudly: verification reports the exact row where row_hash no longer matches the recomputed value, or where row_hmac no longer verifies.

What you see in the app

  • Verify Integrity button on each contract - rehashes the current body and compares to every signature's stored hash
  • History tab - every event with actor, IP, user-agent, and a tamper badge if the chain check fails
  • Contracts list "Audit tamper" badge - surfaces any contract whose chain or workflow snapshot has been tampered with
  • GET /api/v1/contracts/:id/audit/verify - JSON report: per-row chain status, HMAC status, snapshot status

What this doesn't cover

Honesty about the threat model matters more than marketing copy:

  • Full server compromise plus signing key exfiltration. An attacker who reads both the database and the HMAC signing key (env mode) or compromises the IAM credentials for KMS can rewrite the entire chain end-to-end. The countermeasure is off-DB anchoring - periodic export of chain head hashes to immutable storage (S3 Object Lock, blockchain timestamping, etc.) - which is on the roadmap but not yet shipped.
  • Pre-signing tampering. None of these mechanisms protect what was on screen before the signer clicked sign. The document hash is computed at sign time, not at draft time. Internal review and approval workflows are how that risk is managed.
  • Identity assurance of the signer. Email OTP and (when configured) SMS OTP raise this bar, but they're orthogonal to integrity. See E-Signatures for signer-identity controls.

For most SMB use cases - including HK Electronic Transactions Ordinance defensibility - the three-layer scheme plus KMS-mode HMAC is more than the threshold a court would expect. Higher-stakes deployments should add off-DB anchoring; that decision usually waits for the first contract worth litigating over.

FoxCLM Documentation