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

    Fields

    No fields configured for Feature.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions