Skip to content

Harden Trust Pipeline v0.2.1: crash-consistent promotion, verifier independence, watcher/materializer integration - #52

Open
kam6l wants to merge 15 commits into
mainfrom
hardening/trust-pipeline-021
Open

Harden Trust Pipeline v0.2.1: crash-consistent promotion, verifier independence, watcher/materializer integration#52
kam6l wants to merge 15 commits into
mainfrom
hardening/trust-pipeline-021

Conversation

@kam6l

@kam6l kam6l commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Summary

Hardening pass on the AgentDiff Trust Pipeline targeting 0.2.1 — no new AI features. The primary story is unchanged: isolate → observe → control → analyze → prove → promote → tamper-evident evidence.

Baseline: main @ 145dc8c had CI red at every uv sync --locked step (stale uv.lock pinning agentdiff = 0.1.0), plus failing ruff/mypy/pytest. This branch fixes the causes (nothing disabled) and finishes the trust pipeline.

Highlights

Promotion (P0)

  • Lock inode race closed: promotion.lock is persistent and never unlinked; only the OS advisory lock is released (flock/msvcrt), with cross-process exclusion tests.
  • Journal crash window closed: per-entry write-ahead state machine PREPARED → APPLY_INTENT → APPLIED (+ RECOVER_INTENT/RECOVERED/AMBIGUOUS); applied: bool removed; legacy schema-2 journals remain recoverable under old semantics.
  • Corrupt journal ≠ no journal: explicit NO_JOURNAL / VALID_JOURNAL / CORRUPT_JOURNAL outcomes; corrupt journals block promotion.
  • Recovery hardened: every journal path validated (normalized, below root and approved backup dir, no symlink parents, no internal targets); backups re-verified by SHA-256/size/opened identity; restore through fsynced temp with mode restoration; recovery converges on crashes between replace and chmod and fails closed on ambiguity.
  • 37 adversarial fault-injection tests (crash matrix, traversal, symlink/hardlink backups, mode, legacy, cross-process lease).

Proof (P0)

  • Baseline verifier independence: proof runs patched tests, then restores the sealed pre-run verifier files over the patched product code and re-runs the trusted tests, so agent-modified tests cannot silently weaken the verifier; NOT_PROVEN when verifier files were modified and baseline cannot confirm.
  • Proof-strength metadata: deterministic L0–L4 + WEAK/REVIEW/STRONG + verifier-independence; verdict stays deterministic.
  • Clean-room base materialization now preserves file modes; verifier-file classifier (tests, fixtures, runner configs, manifests, lockfiles, CI workflows).

Runtime / Safety (P1)

  • Explicit RuntimeCapabilities replace hasattr/getattr string sniffing; configure_source/configure_safety/close are protocol methods.
  • HybridSafetyWatcher integrated: watchdog event hints → dirty-path targeted checks + periodic authoritative full reconciliation + degradation to polling with recorded status; final state always authoritative capture.
  • WorkspaceMaterializer wired into DockerRuntime; strategies renamed accurately (CLONE=FICLONE, FAST_COPY=copy_file_range not a guaranteed reflink, STREAM_COPY), modes preserved on every strategy, symlinks/hardlinks/special files rejected, O_NOFOLLOW identity checks, ~100× faster by dropping pointless fsync on the ephemeral copy (measured in benchmarks/materialize_bench.py).
  • LocalRuntime reparented-descendant attribution via POSIX session id (fixes flaky test).

Evidence

  • Capsule v1/v2 verification separation: v1 capsules verify under their original guarantees; incomplete v2 seals fail closed; compute_merkle_root renamed compute_root_digest (flat aggregate, not a Merkle tree).
  • Real CAS object store (.agentdiff/objects): immutable, digest-validated, fail-closed reads; capsule spec-v2 layout unchanged (incremental spec-v3/export foundation).

Product / Docs / CI

  • agentdiff prove and agentdiff promote CLI commands added (previously only the engines existed; the GitHub Action already called them) with the AgentDiff Trust Report output.
  • README/PROJECT_PLAN/CHANGELOG/SECURITY/docs synchronized to 0.2.x; tamper-evident-not-authenticated terminology corrected; Merkle/reflink wording fixed.
  • Version 0.2.1 (pyproject, __version__, uv.lock).
  • CI gates verified locally: ruff format/check, mypy, 277 tests on Python 3.12/3.13/3.14, strict docs build + link check, twine + wheel-contents, bandit, pip-audit (no known vulnerabilities), safetybench.

Known limitations (unchanged scope)

  • CAS is architecture + tests; capsule artifact storage migration to object references is spec-v3 work; agentdiff gc and capsule export/import CLI remain Planned.
  • Proof L4 (external signed verifier) is reserved, not produced.
  • Capsule checksums are tamper-evident, not authenticated; signing remains future work.

muskw added 11 commits August 17, 2026 15:27
…-closed recovery

- WorkspaceLease: persistent promotion.lock that is never unlinked; the
  OS-level advisory lock (flock / msvcrt) is the only thing released, closing
  the unlink-then-relock inode race.
- Journal: per-entry write-ahead state machine (PREPARED -> APPLY_INTENT ->
  APPLIED, plus RECOVER_INTENT/RECOVERED/AMBIGUOUS) replacing the single
  applied: bool; transaction states gain RECOVERY_FAILED.
- Journal load: explicit NO_JOURNAL / VALID_JOURNAL / CORRUPT_JOURNAL
  outcomes; corrupt journals now fail closed instead of reading as absent.
  Legacy schema-2 journals remain recoverable under their old semantics.
- Recovery: validates every journal path (normalized, under root and the
  approved backup dir, no symlink parents, no AgentDiff-internal targets),
  re-verifies backup digest/size and opened identity before restoring,
  restores content and mode through an fsynced temp, converges on crashes
  between replace and chmod, and fails closed on ambiguity.
- Engine: per-entry APPLY_INTENT persists before mutation and APPLIED after
  post-state verification; recovery failures propagate instead of being
  ignored; staging and payload copies open with O_NOFOLLOW and verify opened
  device/inode identity.
- Tests: 37 adversarial fault-injection cases (corrupt/malformed journals,
  traversal, symlink/hardlink backups, crash matrix, mode restoration,
  cross-process lease exclusion, legacy journal recovery).
…trength

- New deterministic verifier-file classifier (tests, fixtures, runner
  configs, manifests, lockfiles, CI workflows) with a mutation report.
- Proof now runs two distinguishable verifications: patched tests on the
  complete patched workspace, and a baseline run against the sealed pre-run
  verifier files overlaid onto patched product code. Patch-added verifier
  files are removed from the baseline workspace.
- PROVEN fails closed when verifier-related files were modified but the
  baseline verifier cannot confirm the patched run.
- ProofStrength L0-L4 + WEAK/REVIEW/STRONG label and verifier-independence
  metadata, computed deterministically; verdict stays PROVEN/NOT_PROVEN.
- Clean-room base materialization now preserves file modes and verifies
  opened identity; proof plan discovery uses the shared classifier.
- Tests: classifier matrix, mutation reports, strength matrix, baseline
  overlay end-to-end, tamper-blocking, added-verifier-file fail-closed.
…on, safe materializer

- RuntimeBackend now exposes static RuntimeCapabilities (backend, control
  levels per boundary, private_workspace, live-safety and source-snapshot
  support) before execution; runner no longer uses hasattr/getattr string
  hacks, and configure_source/configure_safety/close are protocol methods.
- HybridSafetyWatcher is wired into transaction execution: watchdog event
  source (optional) feeds dirty-path/directory queues, targeted policy
  checks accelerate protected-path detection, full reconciliation runs on a
  configurable cadence/overflow/force, and backend failures degrade to
  polling with recorded watcher status. Final after-state stays an
  authoritative full capture.
- WorkspaceMaterializer: strategies renamed accurately (CLONE=reflink,
  FAST_COPY=copy_file_range, STREAM_COPY=fallback; copy_file_range is not a
  guaranteed reflink), modes preserved on every strategy, symlink/hardlink/
  special-file targets rejected instead of silently dropped, O_NOFOLLOW
  identity checks, target-symlink check before resolve, actual strategy
  reported; DockerRuntime now materializes through it.
- LocalRuntime: reparented descendants are attributed via POSIX session id
  captured at process start (fixes flaky execution-domain test).
- SafetyController split into budget checks and authoritative full
  reconciliation for targeted dirty-path scanning.
…bject store

- verify_integrity routes by capsule version: spec-v2 verified with the
  structured manifest, legacy v1 verified under its original guarantees; a
  schema-2 mirror without integrity/manifest.json is an incomplete seal and
  fails closed. IntegrityReport carries the verified version.
- CapsuleReader.compute_merkle_root renamed to compute_root_digest (flat
  aggregate, not a Merkle tree); deprecated alias retained.
- Real content-addressed immutable ObjectStore under .agentdiff/objects:
  stream + digest + fsync + atomic rename, write-once immutability with
  re-verification, digest-validated paths, O_NOFOLLOW identity checks,
  fail-closed reads on mismatch. Capsule layout (spec v2) is unchanged;
  CAS is the foundation for incremental spec-v3 artifact references and
  export/import hydration.
- Tests: v1/v2 verification, tamper detection, incomplete-seal fail-closed,
  root-digest determinism, CAS dedupe/immutability/corruption/symlink.
Fixes the CI-quality failures that shipped on main: E501/I001/TC001/TC003/
SIM1xx lint fixes, blind-exception narrowing, dead-code removal, accurate
type annotations, msvcrt/yaml/watchdog typing, and updated watcher tests
for the accelerated targeted-check behavior.
…to 0.2.1

README now leads with the Docker/prove/promote flow, the Trust Report
example, accurate feature status, and an explicit tamper-evident-not-
authenticated note. PROJECT_PLAN, SECURITY.md, quickstart, and version
references in docs_src are synchronized; CHANGELOG adds the 0.2.1 entry and
corrects the inaccurate Merkle/reflink terminology in the 0.2.0 notes.
pyproject/__init__/uv.lock bumped to 0.2.1.
The engines existed but were unreachable from the CLI. Adds:

- agentdiff prove RUN_ID: clean-room reproduction with the AgentDiff Trust
  Report summary (runtime, policy, immediate/future blast, trusted plan,
  baseline/patched test counts, verifier changes, hidden state, proof
  strength, promotion, verdict) plus machine-readable JSON.
- agentdiff promote RUN_ID [--dry-run] [--safe-only] [--path ...]:
  crash-consistent promotion gated on PROVEN proof; fail-closed exit 9 on
  recovery ambiguity/corruption and on missing proof.
- CLI tests for help surface, missing-run handling, and proof-gated
  fail-closed behavior.
The materialized workspace is an ephemeral copy derived from durable sealed
capsule evidence, so per-file fsync added no durability and cost ~100x
throughput (8s -> 0.07s for 1k files on this host). Destination writes are
flushed; fsync remains in promotion staging and the sealed capsule where
durability is a trust property. Adds benchmarks/materialize_bench.py with
measured results (stream/fast/auto across 1k and 10k files) and sets
explicit 0.4-or-later removal versions for the deprecated legacy modules.
Moves watchdog typing to a pyproject mypy override so quality jobs pass
with and without the optional docs group installed.
…ours-preferred promotion/proof/runtime resolution
Comment thread src/agentdiff/cli.py Fixed
muskw added 2 commits August 17, 2026 16:10
…across files

Walk order is filesystem-dependent, so earlier regular files may already
exist when a symlink is rejected; the caller discards the failed private
workspace. The guarantee tested is that the symlink itself is never
materialized or followed.
The prove/promote JSON output now passes through redact_data before
serialization so evidence emitted for sharing/upload is defensively
redacted; also addresses the CodeQL clear-text-logging finding on the
report serialization path.
Comment thread src/agentdiff/cli.py Fixed
muskw added 2 commits August 17, 2026 16:24
…heuristic

CodeQL's py/clear-text-logging-sensitive-data flags the attribute access
as a sensitive-data source via its name heuristic, tainting the proof JSON
report print path (false positive: the value is a boolean). Renamed the
ProofResult field and JSON key to verification_confirmed; the human-facing
report label remains 'Trusted Plan'. JSON output additionally passes
through redact_data before serialization.
Windows st_mode is not a meaningful security property and restore cannot
preserve it, so content is the ground truth there; this fixes the Windows
fault-injection recovery failures.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants