Audit verify reports legacy writer serialization mismatches as tamper-like chain breaks
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 71
- Forks
- 64
- Avg merge
- 15h 38m
- Merged PRs (30d)
- 66
Description
Problem
GET /v1/audit/verify can report ok: false for rows created before 3a23cadf33ef (fix(audit): hash persisted JSON representation), even when no stored audit row was modified.
The old writer hashed the in-memory object and then persisted it as JSONB. Values such as an optional undefined object property were present in the old hash input but removed by JSON serialization. The current verifier only has the persisted JSON and therefore computes a different content hash.
This is a real content-hash mismatch, but it is writer-generated legacy drift rather than evidence of tampering. List pagination and display order are unrelated: verification scans the full organization chain in ascending seq order.
Minimal reproduction
- With the pre-
3a23cadf33efalgorithm, build a successfulrun.finishedaudit event whose payload is:{ status: "succeeded", error: undefined, receipt_sha256: "..." } - Hash that in-memory object and persist the row as JSONB. PostgreSQL retains no
errorproperty. - Run the current
verifyAuditChainagainst the stored row. - Observe
ok: falseat that sequence, even thoughprev_hashstill equals the previous row's storedhashand the following row points to this row's storedhash.
3a23cadf33ef prevents new occurrences by canonicalizing through JSON before hashing, but it does not provide a compatibility or migration path for rows already written with v1.
Expected
Upgrades should not make historical, writer-generated serialization drift indistinguishable from post-write mutation. Verification should state whether the failure is:
- a
prev_hashlink mismatch; - a current-algorithm content-hash mismatch;
- or a known/unverifiable legacy serialization mismatch.
Suggested direction
- Version the hash algorithm or introduce chain epochs/checkpoints.
- Add an explicit, auditable legacy-repair path. Do not silently rewrite audit rows: lock writes per organization, preserve an immutable before-image and checksum, and either rehash canonical persisted JSON with recorded provenance or begin a v2 epoch anchored to that snapshot.
- Return structured diagnostics such as
failureKind,hashVersionorepoch, andcheckedThroughfrom/v1/audit/verify. - Label the UI action
verify full org chainbecause filters and the current 100-row page do not scope verification, and linkfirstBreakSeqto the containing page/event.
Acceptance criteria
- An upgrade fixture containing a v1
run.finishedevent witherror: undefinedis classified deterministically and is not presented as unexplained tampering. - New-version events verify from their persisted canonical JSON.
- Link mismatches and current-version content mismatches remain hard failures.
- Any repair or epoch transition preserves a checksummed before-image and durable provenance.
- UI copy makes full-organization scope explicit and can navigate to the reported sequence.
Relevant code:
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with packages/db/src/audit.ts and packages/core/src/audit.ts, then trace the run.finished writer in services/api/src/sandbox/orchestrator.ts and the UI in apps/web/components/audit/audit-explorer.tsx. Reproduce the described v1 fixture and inspect the existing verification flow. Done means legacy drift is classified deterministically, hard failures remain distinct, and any repair or epoch transition records the required before-image and provenance.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- postgresql, typescript
- Domain
- backend-api-design, databases, frontend, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100