checkId: check.evidencelocker.merkle plugin: stellaops.doctor.evidencelocker severity: fail tags: [evidence, merkle, anchoring, integrity]

Merkle Anchor Verification

This stella doctor check validates the Evidence Locker’s Merkle anchoring chain so operators can prove that stored evidence has not been tampered with and that anchoring is still running on schedule.

What It Checks

Verifies Merkle root anchoring integrity when anchoring is enabled. The check operates on anchor records stored in the anchors/ subdirectory of the evidence locker path. It validates:

  1. Anchor record presence: checks for .json anchor files in the anchors directory.
  2. Anchor structural validity: each anchor must contain merkleRoot, timestamp, signature, and a verifiable leaf set (leafHashes or leaves) with non-empty values.
  3. Anchor integrity: validates the most recent 5 anchor records by recomputing the Merkle root from the leaf hashes and comparing it to the declared merkleRoot.
  4. Anchor freshness: compares the latest anchor timestamp against the configured interval (EvidenceLocker:Anchoring:IntervalHours, default 24). Warns if the latest anchor is more than 2x the interval age.
ConditionResult
Anchoring not enabledSkip
Evidence locker path not configuredSkip
No anchor records foundWarn
Any anchor records invalid (missing fields, missing leaf set, corrupt, root mismatch)Fail
Latest anchor older than 2x configured intervalWarn
All checked anchors valid and freshPass

Evidence collected: CheckedCount, ValidCount, InvalidCount, InvalidAnchors, LatestAnchorTime, AnchorAgeHours, ExpectedIntervalHours, LatestRoot.

The check only runs when EvidenceLocker:Anchoring:Enabled is set to “true”.

Why It Matters

Merkle anchoring provides cryptographic proof that evidence has not been tampered with since the anchor was created. Each anchor captures the Merkle root hash of all evidence at a point in time, creating an immutable checkpoint. Invalid anchors mean the integrity chain is broken and evidence cannot be independently verified against its anchor. A legacy anchor that only carries a merkleRoot and signature but no leaf set is not treated as verified. Stale anchors indicate the anchoring job has stopped running, creating a window where evidence changes are not captured by any checkpoint.

Common Causes

How to Fix

Docker Compose

# Create an initial anchor
docker compose exec evidence-locker stella evidence anchor create

# Check anchor job status
docker compose exec evidence-locker stella evidence anchor status

# Audit anchor integrity
docker compose exec evidence-locker stella evidence anchor audit --full

# Verify a specific anchor
docker compose exec evidence-locker stella evidence anchor verify <anchor-filename>

# Enable anchoring in configuration
# EvidenceLocker__Anchoring__Enabled=true
# EvidenceLocker__Anchoring__IntervalHours=24

docker compose restart evidence-locker

Bare Metal / systemd

# Create an anchor
stella evidence anchor create

# Check anchor status
stella evidence anchor status

# Full anchor audit
stella evidence anchor audit --full

# Configure anchoring in appsettings.json
# "EvidenceLocker": {
#   "Anchoring": {
#     "Enabled": true,
#     "IntervalHours": 24
#   }
# }

# Verify the anchor job is scheduled
stella jobs list --filter anchor

sudo systemctl restart stellaops-evidence-locker

Kubernetes / Helm

# values.yaml
evidenceLocker:
  anchoring:
    enabled: true
    intervalHours: 24
    schedule: "0 0 * * *"  # Daily at midnight (24h interval)
# Create initial anchor
kubectl exec deploy/stellaops-evidence-locker -- stella evidence anchor create

# Check anchor status
kubectl exec deploy/stellaops-evidence-locker -- stella evidence anchor status

# Audit anchors
kubectl exec deploy/stellaops-evidence-locker -- stella evidence anchor audit --full

helm upgrade stellaops ./charts/stellaops -f values.yaml

Verification

stella doctor run --check check.evidencelocker.merkle