Skip to content

Filesystem-state attestation: prove what a run changed on disk (pre/post Merkle root) #397

Description

@dpup

Summary

Add filesystem-state attestation to the audit store: compute a cryptographic commitment over the run's writable paths before the agent starts and again after it exits, so a run can prove what changed on disk — or prove that nothing did.

Moat's audit today is an event log (hash-chained console/network/credential events). It records what happened over the wire and at the boundary, but not what the agent did to the filesystem. This adds the complementary axis: a verifiable, after-the-fact answer to "which files did this run actually modify?"

Motivation

  • Provable blast radius. For a tool whose pitch is full observability of untrusted agents, being able to attest "this run provably touched only these N files" (or "provably changed nothing in the tracked paths") is high-value for review, compliance, and incident response.
  • Tamper-evidence for disk changes. The audit store already gives tamper-evident events; this extends the same guarantee to filesystem state.
  • Cheap to add on existing machinery. Moat already has a hash-chained audit store and exportable proof bundles to anchor the roots into.

Proposed mechanism

Compute a Merkle root over the writable/tracked paths at two points:

  • Pre-root — before the agent starts, walk the tracked paths and hash (relative_path, file_content_hash) leaves into a Merkle tree; record the root.
  • Post-root — after the agent exits, recompute the root.

Properties:

  • If pre_root == post_root, it is cryptographically provable nothing in the tracked paths changed.
  • If they differ, the filesystem changed; the per-file leaf hashes let a verifier enumerate exactly which paths differ (added/removed/modified).
  • Use SHA-256 with domain separation (distinct prefixes for leaf vs. internal-node hashes) so a leaf hash can't be confused with an internal node.
  • Record both roots in the run's session metadata and fold the post-root into the existing audit chain / export bundle so it's covered by the same signature.

Scope of tracked paths

  • Default: the run's writable mount(s) / workspace.
  • Exclusion patterns for regenerable or huge trees (.git, node_modules, target, dist, .next, etc.) and a size guard (skip directories above a configurable file-count threshold) so large repos don't make pre/post walks pathologically slow. Surface skipped paths in the metadata so the attestation's scope is explicit and honest.

Configuration sketch

# moat.yaml
audit:
  filesystem_attestation:
    enabled: true
    paths: [./]              # defaults to writable mounts
    exclude: [.git, node_modules, target]
    max_files_per_dir: 10000 # skip + record dirs larger than this

Verification

Extend the audit verify/export flow so a reviewer can:

  • Recompute the post-root from the captured leaf set and confirm it matches the signed metadata.
  • Diff pre-root vs. post-root leaves to list exactly which tracked paths changed.
  • Confirm the roots are bound into the existing run attestation/signature.

Open questions

  • Walk cost on large monorepos — is a pre-run walk acceptable, or do we need an opt-in / incremental approach (e.g., watch-based) for big workspaces?
  • Should leaf content hashes be stored (enables exact per-file diff but larger metadata) or only the root (smaller, but diff requires re-walking the pre-state)?
  • Symlinks and out-of-tree writes: define behavior for symlinks pointing outside tracked paths and for writes to paths outside the writable set.
  • How does this compose with the per-run export proof bundle — separate artifact or additional fields in the existing bundle?

Out of scope (for this issue)

  • Snapshot/rollback of the changes (separate feature — this issue only attests to changes, it does not capture or restore content).
  • Executable-identity binding in the audit trail (related provenance improvement; tracked separately).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions