Skip to content

Releases

v0.8.0 — An uncorroborated chain is now a finding

Choose a tag to compare

@CharlotteN7 CharlotteN7 released this 04 Sep 21:11

Breaking: an anchor naming a chain that is not in the spool is now a finding

The claim this release retracts

docs/integrity.md said:

Any later rewrite of the spool must either leave those records exactly as they were or produce a
chain that disagrees with what the SIEM already has, and dsh-ocsf-verify --anchor makes that
comparison.

That was false. chain_uid is minted by the writer, the fingerprints are unkeyed SHA-256, and a
spool replaced wholesale under a fresh chain_uid does not disagree with the anchors — it fails
to overlap them, so the comparison never runs.

Reproduced against a real 105-record spool: three records deleted, one edited, the whole file
re-chained under a forged id, verified against the 25 anchors the SIEM held —

25 anchor(s) name a chain with no records here
INTACT: every record hashes to its own fingerprint and every link matches.   EXIT=0

The one trace it left was a count that this same page taught operators to read as a shipper that
had drained a generation.

What changed

That absence is now the finding uncorroborated-chain, exit 1. It is raised per chain (twenty
anchors on one missing chain is one thing wrong) and only when --anchor was supplied, so
dsh-ocsf-verify spool.jsonl with no anchors behaves exactly as before.

The trade, stated rather than discovered later: a host whose shipper legitimately drained and
unlinked every generation of a chain produces a byte-identical report, and nothing on that host
distinguishes the two. That is why the count existed. --no-strict-anchors restores it, and
reaching for it is a claim about that host's retention — not a way to quieten a report. It is the
default anyway, because a whole-spool replacement leaves no other trace at all, and a control that
stays silent on the one move that erases history is worse than one you have to reason about.

If this breaks a green pipeline: you are either looking at a real deletion, or at a chain your
shipper drained. Confirm against your retention, then pass --no-strict-anchors if it is the
latter.

What it does not close

Anchors bound a chain from below and never from above. A second fabricated chain beside an
intact one, and records appended past the last anchored entry, disturb no anchor and are reported by
neither setting. Both stay pinned as limitations in tests/unit/integrity-tamper.spec.ts. The query
that closes them belongs to the SIEM, which holds both the delivered stream and the roster of chain
ids an installation ever shipped under.

Also in this release

The three event types 0.1.2 added — model/selection, session-log-deepseek/delivery-accepted,
subagent/model-selection-policy — all had installed emitters and all fell to the generic fallback,
which reads no payload field. Now mapped, along with the four team/* types. Dev-dependencies moved
to 0.1.2-rc.1, which the peer range had already admitted for three releases.

v0.7.1 — detecting a truncated audit log

Choose a tag to compare

@CharlotteN7 CharlotteN7 released this 03 Sep 09:55

The chain did not detect a truncated log

The integrity chain links each record to its predecessor, so removing records from the end left
a shorter chain that verified perfectly:

clean spool               INTACT   exit 0   (control)
interior record deleted   BROKEN   exit 1   (caught)
last 3 records removed    INTACT   exit 0   (missed)

Deleting your own recent activity is the easiest tampering there is — no key, no code, one head
command — and it was invisible.

The fix uses what was already there. Every attested record carries its chain index and its
predecessor's fingerprint inside the hashed content, and the shipper posts spool lines verbatim,
so a shipped record already is the claim "this chain reached entry N". What was missing was the
comparison:

dsh-ocsf-verify --anchor <records exported from your SIEM> <spool path>

It reports truncated when the chain stops before an entry the anchor accounts for, and
anchor-mismatch when the record at an anchored entry is not the one that shipped — which catches a
tail rewritten to the right length, something a length check alone would pass.

Stated honestly: a purely local run still cannot detect suffix truncation, because nothing local
can. Every chain line now ends with anchored through entry N or no anchor — nothing here can show whether entries after N were removed. The residual window is one flushIntervalMs, or one
statsIntervalMs if you anchor only on heartbeats.

The plugin would have stopped recording, silently

Session.events became Session.snapshotEvents() in the 0.1.2 prerelease line. Against
0.1.2-alpha.5 the unported plugin mounted, the agent ran, and the spool held zero session
records
— no crash, no warning, an audit forwarder that had quietly stopped forwarding. The
plugin now reads whichever spelling the resolved harness has, and the same for
header.seedLength → Session.inheritedEventCount.

Append-only spools

chattr +a is the natural hardening for an audit spool: appends succeed, truncation and rewriting
fail, and even root must clear the attribute first. Our fchmodSync defeated it — the mode change
returned EPERM and took the whole write down with it. Worse, the descriptor leaked on every such
failure: one per record while a spool was failing.

Both fixed, and docs/hardening.md says what the attribute buys, what it costs, and what it breaks
— every row measured against a real ext4 file rather than argued. Append-only and rotation are
incompatible
: rename is EPERM, full stop. An append-only spool degrades into a permanently
un-rotating one that drops nothing and says so, and that is the supported combination.

Also

The harness peer range matches the rest of the suite. Three packages shipped three different ranges,
and npm resolves to the newest any of them admits — installing all four together was impossible
until this.

Upgrading

No configuration changed and no record shape changed. --anchor is new and optional; without it the
verifier behaves as before, but now says plainly what it cannot know.