Skip to content

transcript-integrity chain has no downgrade resistance against a full-strip attack (vault anchor missing) #6449

Description

@bug-ops

Description

Issue #6360 shipped hash-chain tamper-evidence for sub-agent transcripts (zeph-subagent::transcript), session event logs (zeph-session::log), and the durable journal (zeph-durable's authenticated high-water-mark). All three correctly detect and fail closed on in-place content edits, entry reordering, a partial strip of chain/integrity metadata, and key-epoch tampering.

None of the three defend against a fully-consistent whole-file/whole-execution downgrade: an attacker who deletes every chain field from a transcript/session JSONL file (or deletes the durable durable_execution_integrity row entirely) makes the file/execution indistinguishable from genuinely pre-feature legacy content, which is auto-trusted by design (no backfill, no permanent UNVERIFIED flag — see spec-069 FR-006's migration posture).

This is exactly the spec's motivating threat model (spec-069 Problem Statement): an adaptive prompt-injection attacker fabricating a fake "approved" turn. Such an attacker, aware of the chain scheme, strips all chain fields rather than editing in place — precisely the uncovered case.

The original 3-round architecture design (.local/handoff/2026-07-18T04-*.md, rev3 S-new-4) resolved this via a vault-stored per-file anchor (integrity.anchor = "vault", default-on for the JSONL paths): a small attestation {genesis, head, count, epoch} mirrored to the age vault on finalize, checked on read — since the vault sits outside filesystem-write reach, this closes the downgrade lever the base chain mechanism cannot. This anchor was never implemented. Confirmed via the PR's own security audit (.local/handoff/2026-07-18T06-43-34-security.md) and implementation critic review (.local/handoff/2026-07-18T06-46-47-critic.md), both of which adjudicated shipping without it as acceptable for an initial PR conditional on this follow-up issue being filed.

The durable side has the same conceptual gap via a different mechanism: check_high_water_mark treats a missing durable_execution_integrity row as legacy-accepted (migration-safety decision), so an attacker with DB write access can delete that row to force legacy trust on an execution that should otherwise fail closed.

Expected Behavior

  • JSONL (subagent/session): implement integrity.anchor = "vault" per the rev3 design — a per-file vault attestation mirrored on finalize, checked on read; a chained file whose vault anchor is absent/mismatched fails closed (resolvable via the existing --allow-unverified override on attended paths only).
  • Durable: either implement an equivalent anchor, or make the "missing durable_execution_integrity row on an otherwise-keyed execution" case fail closed once no pre-feature (unmigrated) executions remain in a deployment (a bounded, documented cutover rather than a permanent legacy-accepted stance).

Actual Behavior

A fully-consistent whole-file/whole-row strip (delete all chain fields, or delete the durable integrity row) is silently accepted as legacy content on all three surfaces, with no anchor, no WARN, and no audit event distinguishing it from genuinely pre-feature content.

Environment

Notes

Not a blocker for #6360's PR — the shipped mechanism provides substantive, well-tested defense-in-depth (in-place edit, reorder, partial-strip, and key-epoch tampering are all detected and fail closed) and the highest-stakes unattended path (durable resume, US-003/SC-004) is already the least-affected since a keyed execution missing its integrity row is anomalous. CHANGELOG/PR text has been scoped to claim only what is actually implemented (no downgrade-resistance claim). Cross-referenced from both the JSONL and durable CHANGELOG entries for #6360.

Metadata

Metadata

Assignees

Labels

P1High ROI, low complexity — do next sprintenhancementNew feature or requestsecuritySecurity-related issue

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions