Skip to content

Commit 0a509a9

Browse files
docs(theory): provenance threat model (foundational doc for Step 2) (#90)
Closes #25. Until now the provenance chain's value proposition lived only in the README ("tamper-evident SHA-256 hash chain so you can prove who did what and when") with no written model of what that actually proves under which adversary. The V-L2-C* implementation issues just made multiple binding decisions (which fields to hash, how to encode the timestamp, whether self-loops are allowed) that all need a single reference; this is it. `docs/theory/provenance-threat-model.adoc` covers the five sections required by V-L1-B1: 1. Four adversary models — read-only, sidecar-append, sidecar-rewrite, clock-skew (the last is orthogonal and can compose with any of the first three). 2. What the chain proves under each, field-by-field and with the specific "defeated by" pathway for each gap. Explicit about where the chain provides zero protection (sidecar-rewrite locally) and where it conditionally protects (sidecar-append against forged actor only if signatures are added). 3. Five out-of-scope items: DoS, side channels, target-DB tampering bypassing interception, retroactive provenance, identity binding. 4. Five assumptions: sidecar locality, optional append-only storage layer, SHA-256 strength, clock source, single writer. 5. Four open questions, each marked as a future ADR (per-entry signatures, external anchoring, trusted clock policy, snapshot redaction). Cross-references the V-L2-C1..C4 implementation and points at the specific source files where the relevant invariants live. No code changes; doc-only. Future ADR-0004 (the "binding choices" ADR called for in this issue's acceptance criteria) is a follow-up once this doc is reviewed. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 4ee90c8 commit 0a509a9

1 file changed

Lines changed: 232 additions & 0 deletions

File tree

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
= Provenance Threat Model
4+
Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
5+
:toc: left
6+
:toclevels: 3
7+
:icons: font
8+
:source-highlighter: rouge
9+
10+
This document specifies what verisimiser's provenance hash chain
11+
actually proves, in what threat model, with what assumptions. It is
12+
the binding reference for the Step 2 implementation issues
13+
(V-L2-C1 … V-L2-L2) and is cited from each of them.
14+
15+
The chain is a SHA-256 hash chain over `ProvenanceEntry` rows
16+
(`src/abi/mod.rs`). It is *not* a blockchain — there is no consensus,
17+
no proof-of-work, no distributed timestamp authority — but it does
18+
provide *append-only, tamper-evident* semantics under the conditions
19+
documented below.
20+
21+
== 1. Adversary models
22+
23+
Each subsequent section is parameterised by which adversary we are
24+
defending against. The four models are mutually distinct in
25+
capability; in practice a real attacker may sit between two of them.
26+
27+
[cols="1,3,2"]
28+
|===
29+
| Name | Capability | Typical real-world correspondent
30+
31+
| **Read-only**
32+
| Can read both target database and sidecar (provenance log,
33+
temporal versions, lineage). Cannot write either.
34+
| Compromised read replica; off-host backup reader; auditor with
35+
least-privilege account; lawful disclosure recipient.
36+
37+
| **Sidecar-append**
38+
| Can append new rows to `verisimdb_provenance_log` but cannot
39+
delete or rewrite existing rows. (Realistic in append-only
40+
configurations: filesystem-level WORM, immutable cron-driven
41+
ingest, or a `BEFORE UPDATE/DELETE` trigger that refuses.)
42+
| Misbehaving instrumentation library; faulty sidecar-writer cron;
43+
malicious extension to the application that can call the writer
44+
API but cannot bypass the WORM layer.
45+
46+
| **Sidecar-rewrite**
47+
| Can rewrite arbitrary rows in the provenance log, in any order,
48+
with any contents. The integrity store (sidecar) is fully under
49+
the adversary's control.
50+
| Root on the sidecar host; compromised application running with
51+
sidecar write privilege; disk swap / device theft.
52+
53+
| **Clock-skew**
54+
| Orthogonal to the above. Can submit writes (via the legitimate
55+
writer path) with timestamps drawn from a clock that does not
56+
agree with monotonic UTC. May be combined with any of the three
57+
above.
58+
| Untrusted NTP source; container with overridden clock; mobile
59+
client; multi-region writer with clock drift.
60+
|===
61+
62+
== 2. What the chain proves
63+
64+
In all cases below, the chain's claims are conditional on the
65+
assumptions in section 4. "Protected" means tampering produces a
66+
hash that fails `ProvenanceEntry::verify()` (per V-L2-C1, the
67+
preimage is domain-separated and length-prefixed over all seven
68+
data fields).
69+
70+
=== 2.1 Read-only adversary
71+
72+
Trivially: the adversary makes no writes, so nothing they observe
73+
can cause the chain to fail to verify. The chain is intact by
74+
construction; the adversary can read it.
75+
76+
* **Detectable**: nothing (they did nothing detectable).
77+
* **Defeated**: by `BEGIN TRANSACTION ... COMMIT` reads against
78+
the sidecar; no replay attack is even meaningful here.
79+
80+
This model exists primarily to constrain *what we publish*: the
81+
sidecar is readable, so any field stored in it is exposed.
82+
`before_snapshot` may contain redacted data; access-control policy
83+
(`verisimdb_access_policies`) is the boundary against unauthorised
84+
read.
85+
86+
=== 2.2 Sidecar-append adversary
87+
88+
The adversary can write new rows. They cannot delete or rewrite
89+
existing rows, so the existing chain is intact.
90+
91+
* **Protected**: every historical entry. Existing
92+
`previous_hash` pointers cannot be altered, so the chain spine
93+
from genesis to the most recent legitimate entry is verifiable.
94+
* **Not protected**: the adversary can append entirely new entries
95+
with whatever `actor`, `operation`, `before_snapshot`,
96+
`transformation` they choose, *including chained to the existing
97+
tip*. Their writes will have the right `previous_hash` and will
98+
`verify()` true individually.
99+
* **Defeated**: by out-of-band actor authentication. The chain
100+
records *what was claimed*; the adversary can claim
101+
`actor="alice"`. Pairing the chain with a signature on each
102+
entry (e.g. SSH commit signing equivalent over `entry.hash`) is
103+
the standard fix. **Not currently implemented.** See open
104+
question OQ-1.
105+
106+
=== 2.3 Sidecar-rewrite adversary
107+
108+
The adversary can rewrite arbitrary rows.
109+
110+
* **Protected**: nothing locally. Every row can be replaced; the
111+
hash chain can be entirely reconstructed from genesis with new
112+
content and will verify just as well. The chain provides zero
113+
protection against an adversary with write access to the
114+
sidecar.
115+
* **Defeated**: only by an *external* anchor. The standard
116+
anchors are (a) periodic publication of the chain's tip hash
117+
to a remote append-only log (e.g. transparency log,
118+
certificate transparency, signed-timestamp service); or
119+
(b) per-entry signatures backed by a key whose authority is
120+
not under the adversary's control. **Neither is currently
121+
implemented.** See OQ-2.
122+
123+
=== 2.4 Clock-skew adversary
124+
125+
The adversary submits writes through the legitimate path but with
126+
manipulated timestamps.
127+
128+
* **Protected**: ordering is determined by the `previous_hash`
129+
chain spine, not by `timestamp`. A backdated or future-dated
130+
entry will still chain correctly *to whatever predecessor it
131+
claims*; reordering is therefore prevented even under clock
132+
skew, *given* the predecessor was honestly chosen.
133+
* **Not protected**: the absolute time recorded in `timestamp`.
134+
V-L2-C2 hashes the timestamp canonically so the recorded value
135+
is tamper-evident, but the recorded value can still be a lie
136+
the writer chose at write time.
137+
* **Defeated**: trust a single monotonic clock source (NTP from a
138+
trusted root, hardware clock with attestation) and refuse
139+
writes whose timestamp is more than ±N seconds from the
140+
receiving host's clock. **Not currently implemented.** See OQ-3.
141+
142+
== 3. Out of scope
143+
144+
These threats are *not* defended by the provenance chain and any
145+
claim to the contrary is incorrect:
146+
147+
* **Denial of service.** An adversary who fills the sidecar
148+
disk, or who deletes the entire sidecar file, defeats the
149+
chain. The chain produces no signal in that case.
150+
* **Side channels.** Timing, memory, network metadata, query
151+
cardinality. The chain records what was written; it does not
152+
obscure access patterns.
153+
* **Target-DB tampering not routed through the writer.**
154+
VeriSimiser intercepts modifications through the configured
155+
interception path (`pg_notify`, `sqlite3_update_hook`,
156+
change streams, application middleware — see README).
157+
Modifications that bypass interception — direct file-system
158+
writes to the target's storage, restore from backup, schema
159+
migration outside the controlled path — are invisible to
160+
the chain.
161+
* **Retroactive provenance.** Data that existed before
162+
verisimiser was attached has no provenance row. The chain
163+
starts at the genesis entry; rows that predate the genesis
164+
cannot be vouched for.
165+
* **Identity binding.** The chain records `actor` as a string;
166+
it does not authenticate that the string corresponds to the
167+
real-world entity it names. See sidecar-append, above.
168+
* **Confidentiality.** Every field in the entry is stored in
169+
plaintext in the sidecar.
170+
171+
== 4. Assumptions
172+
173+
* **Sidecar locality.** The sidecar runs on the same host as
174+
the target database. Cross-host or remote sidecar deployments
175+
introduce a network-write surface that the threat model does
176+
not cover.
177+
* **Append-only storage (optional).** Sidecar-append's
178+
protection assumes the sidecar storage refuses
179+
`UPDATE`/`DELETE` on `verisimdb_provenance_log` at a layer
180+
the application cannot bypass. Without this, sidecar-append
181+
collapses into sidecar-rewrite.
182+
* **Hash algorithm.** SHA-256 is preimage-resistant and
183+
collision-resistant for the foreseeable future. A migration
184+
to a different algorithm requires bumping the domain tag
185+
(currently `b"verisim-prov-v1\0"`); see V-L2-C1.
186+
* **Clock source.** `chrono::Utc::now()` reads the system
187+
monotonic clock. The clock-skew adversary section assumes
188+
no defence against a lying clock; "trusted clock" is an
189+
out-of-band assumption a deployment may add.
190+
* **Single writer.** No multi-writer reconciliation. If two
191+
concurrent writers both chain to the current tip, only the
192+
first commit survives; the second errors at insert time
193+
(FK / chain-position contention).
194+
195+
== 5. Open questions
196+
197+
Each is a deliberate non-decision; an ADR resolves the choice.
198+
199+
* **OQ-1: Per-entry signatures?** Do we want a detached
200+
signature column (e.g. `signature TEXT`) over `entry.hash`
201+
using a per-actor key, defending sidecar-append against
202+
forged `actor`? Trade-off: PKI overhead vs. forensic
203+
integrity. Suggested follow-up: ADR-0004.
204+
* **OQ-2: External anchoring?** Periodic publication of the
205+
chain tip to a remote append-only log (TUF, Sigsum,
206+
certificate transparency). Trade-off: anchor latency and
207+
external dependency vs. defence against sidecar-rewrite.
208+
Suggested follow-up: ADR-0005.
209+
* **OQ-3: Trusted clock policy?** Refuse writes whose
210+
timestamp drifts more than ±N seconds from the local
211+
monotonic clock. Trade-off: replicated / mobile writers
212+
break vs. clock-skew defence. Suggested follow-up:
213+
ADR-0006.
214+
* **OQ-4: Snapshot redaction.** `before_snapshot` may carry
215+
PII. Should the writer hash a *digest* of the snapshot
216+
rather than the snapshot itself, with the plaintext kept
217+
in a separately access-controlled store? Trade-off: storage
218+
vs. lawful-disclosure granularity. Suggested follow-up:
219+
ADR-0007.
220+
221+
== Cross-references
222+
223+
This document is cited by V-L2-C1 through V-L2-C4 (which
224+
implement the field-coverage and canonical-timestamp choices),
225+
V-L2-L1 / V-L2-L2 (which constrain chain forks at the storage
226+
layer), and V-L1-C1 / V-L1-C2 (which build the actual write
227+
pipeline that produces the entries this model talks about).
228+
229+
The implementation lives in `src/abi/mod.rs::ProvenanceEntry`;
230+
the storage schema in `src/codegen/overlay.rs::generate_provenance_table`;
231+
and the integration tests in
232+
`tests/integration_test.rs::test_provenance_chain_integrity_multi_step`.

0 commit comments

Comments
 (0)