AI News HubLIVE
In-site rewrite5 min read

Show HN: Git log for your infrastructure (including your AI agent's config)

statedrift is an open-source tool that records Linux host state (network, packages, services, etc.) into a tamper-evident hash chain, enabling version control and auditability. It supports snapshots, diffs, security analysis, and cryptographic integrity verification without any external dependencies or cloud services.

SourceHacker News AIAuthor: ibukunolu

Uh oh!

There was an error while loading. Please reload this page.

Notifications You must be signed in to change notification settings

Fork 0

Star 0

BranchesTags

Open more actions menu

Folders and files

NameName

Last commit message

Last commit date

Latest commit

History

57 Commits

57 Commits

.github/workflows

.github/workflows

cmd/statedrift

cmd/statedrift

configs

configs

demo

demo

docs

docs

internal

internal

tests

tests

.dockerignore

.dockerignore

.gitignore

.gitignore

CHANGELOG.md

CHANGELOG.md

Dockerfile

Dockerfile

LICENSE

LICENSE

Makefile

Makefile

README.md

README.md

ROADMAP.md

ROADMAP.md

go.mod

go.mod

install.sh

install.sh

Repository files navigation

Git log for your infrastructure. A single Go binary that snapshots what your Linux hosts actually are — network, packages, services, users, sudoers, kernel modules, mounts, firewall, containers, GPUs, and more — into a tamper-evident hash chain. Diff any two points in time, and hand auditors a cryptographically verifiable evidence bundle.

Linux · single static binary · no daemon required · no cloud · zero third-party dependencies.

See it work

$ sudo statedrift init ✓ Store initialized at /var/lib/statedrift/chain ✓ Genesis snapshot recorded ✓ Run 'statedrift snap' to take more snapshots.

...an hour later, something changed on the box. Snapshot again:

$ sudo statedrift snap ✓ Snapshot recorded

What actually changed?

$ statedrift diff HEAD~1 HEAD Comparing 2026-06-30 14:00 → 2026-06-30 15:00

kernel_params: ~ net.ipv4.ip_forward: "0" → "1"

users: + backdoor: uid=1001 gid=1001 shell=/bin/bash

2 material changes, 0 counter increments

Someone turned the host into a router and gave themselves an account — surfaced in plain sight, with a timestamped record of exactly when it happened.

A diff lists everything that changed. analyze runs the built-in rule engine over that diff and tells you which of those changes are dangerous, and how severe:

$ statedrift analyze statedrift analyze — 2026-06-30T14:00:00Z → 2026-06-30T15:00:00Z 2 material changes, 54 rules evaluated

[HIGH] Kernel parameter changed (1 match) A sysctl value was changed. Security-relevant params (ip_forward, rp_filter) are high severity. [HIGH] New user account (1 match) A new entry was added to /etc/passwd. New accounts created outside a change window may be backdoors.

2 finding(s). Run 'statedrift diff HEAD~1 HEAD' for full details. Pro rules skipped (no license). See 'statedrift help analyze'.

And because every snapshot is SHA-256 hash-chained to the one before it, nobody can quietly rewrite history to cover their tracks — verify catches an edited snapshot:

$ statedrift verify Verifying chain integrity... Snapshots: 3 Chain: ✓ all 3 hashes valid Head: ✓ matches last snapshot Result: INTEGRITY VERIFIED

No tampering detected. All snapshots are consistent with their recorded hashes.

...but if an attacker edits an old snapshot to erase the evidence:

$ statedrift verify Verifying chain integrity... Snapshots: 3 Chain: ✗ BREAK at snapshot #1 (2026-06-30T15:00:00Z) Expected prev_hash: a3f81c2d9e4b5f6a... Found prev_hash: 7c0091a2bd34ef58... Result: INTEGRITY VIOLATION

The problem

During an incident, nobody can agree on what the config looked like before things broke. During an audit, your team burns weeks on screenshots and spreadsheets that auditors don't trust — because files can be edited without a trace. Drift happens silently, and by the time you find it, there's no record of when it started.

statedrift records what your infrastructure actually is — not what it's supposed to be — in a tamper-evident hash chain that makes retroactive edits detectable.

Install

curl -fsSL https://raw.githubusercontent.com/statedrift/statedrift/main/install.sh | bash

Linux only (amd64 and arm64). Needs curl (or wget), tar, and sha256sum. The installer pulls the latest release, verifies its SHA-256 checksum, and installs to /usr/local/bin.

Pin a version, or install without sudo to a user-writable prefix:

curl -fsSL https://raw.githubusercontent.com/statedrift/statedrift/main/install.sh | bash -s -- --version 0.8.1 curl -fsSL https://raw.githubusercontent.com/statedrift/statedrift/main/install.sh | bash -s -- --prefix "$HOME/.local/bin"

Or build from source (Go, no external dependencies):

make build && sudo cp bin/statedrift /usr/local/bin/

Quick start — 60 seconds to your first drift catch

sudo statedrift init # record the genesis snapshot (chain root) sudo statedrift snap # ...later, snapshot again after any change sudo statedrift diff HEAD~1 HEAD # see exactly what changed sudo statedrift verify # prove the whole chain is untampered

Prefer to try it without root? Use a home-directory store — then no command needs sudo:

export STATEDRIFT_STORE=$HOME/.statedrift statedrift init && statedrift snap

That's the loop. Run snap on a schedule (cron, or the built-in statedrift watch) and you've got a continuous, verifiable record of everything your host is. Want more than the core sections? Optional collectors (containers, GPUs, your AI agent's config, ...) are one command away:

sudo statedrift config enable containers # or: gpu, dataplane, harness, ... or all

See what gets captured below, or statedrift --help for every command.

Optional: shell alias

statedrift is deliberately unambiguous in scripts and logs. For interactive use, add an alias to your shell profile:

~/.bashrc or ~/.zshrc

alias sd='statedrift'

If you're working with a non-default store path, bake it in:

alias sd='STATEDRIFT_STORE=/var/lib/statedrift statedrift'

Note: sd is also the name of an unrelated find-and-replace tool. If you have both installed, choose a different alias name (e.g. sdt) to avoid the conflict.

What gets captured

Every snapshot records:

Section What Source

host Hostname, OS, kernel version, uptime /proc/version, /etc/os-release

network.interfaces IPs, link state, MTU, packet counters /sys/class/net/

network.routes Routing table (destination, gateway, metric) /proc/net/route

network.dns Nameservers, search domains /etc/resolv.conf

kernel_params Selected sysctl values /proc/sys/

packages Installed package names and versions dpkg-query or rpm -qa

services Systemd unit names and states systemctl list-units

listening_ports TCP sockets in LISTEN state /proc/net/tcp

mac SELinux/AppArmor enforcement mode and policy /sys/fs/selinux, /sys/kernel/security/apparmor

firewall Packet-filter ruleset identity (SHA-256 + rule count) plus the parsed per-rule list for added/removed/reordered diff (rules embed IPs/ports — redacted by --redact-network) nft list ruleset, iptables-save

Opt-in collectors extend the snapshot further. Enable one with statedrift config enable (or enable all for everything); all are free and daemon-free, like everything else:

Collector What Drift it catches

cpu, kernel_counters CPU mode ticks; IP/TCP/UDP protocol counters Load and traffic anomalies (tracked as counters, never material drift)

processes Top-N processes by memory A resident process that wasn't there before

sockets Socket inventory per process A process quietly opening new connections

nic_drivers NIC driver and firmware versions (ethtool -i) A swapped driver or firmware image

filesystem Hash tree over configured roots (default /etc): per-file mode, ownership, size, SHA-256, plus a Merkle root_hash Per-file content, permission, or ownership changes

containers Running-container inventory from /proc cgroup membership — runtime-agnostic (Docker, containerd, CRI-O, podman) A container appearing (R37), disappearing (R38), or turning privileged (R39)

gpu NVIDIA GPU inventory from the driver's /proc interface GPU added/removed, driver or VBIOS drift (R40–R43)

dataplane SR-IOV VF counts and DPDK-bound NICs from /sys — devices handed to a userspace vfio-pci/uio driver, invisible to the kernel stack and its firewall A NIC silently rebound to userspace, VF counts changing (R44–R47)

harness Your AI agent's own config: Claude Code's settings.json, .mcp.json, and the user-scope ~/.claude.json that claude mcp add writes to A broadened tool permission, a new MCP server or hook, a model change (R49–R54)

filesystem growth is bounded by size and file-count caps; file paths and hashes are stored as-is (system config paths, nothing sensitive). The harness collector never stores secrets — MCP env values and embedded credentials are dropped at collect time, keeping only key names and a redacted fingerprint (details in the box below).

Your AI coding agent's own config is attack surface — statedrift version-controls it.

An agent's permissions, MCP servers, and hooks decide what it's allowed to touch. A silently broadened tool permission or a newly wired-in MCP server is a real privilege change, and nothing else is watching that file. The harness collector snapshots it, so the change shows up in the diff and trips a rule:

$ statedrift config enable harness # one-time opt-in (reads your ~/.claude — run as your own user) $ statedrift diff HEAD~1 HEAD --section harness harness.mcp: + ~/.claude.json filesystem: stdio # a new MCP server was wired in → R50

Secrets never enter the chain: MCP env values and credentials embedded in commands or URLs are dropped at collect time, leaving only env key names and a redacted SHA-256 fingerprint — so rotating a secret doesn't churn the snapshot, but changing the wiring does.

Each snapshot is SHA-256 hash-chained to the previous one. Modifying any snapshot breaks the chain — and statedrift verify catches it.

What statedrift does NOT do

Collect packet payloads or user content

Modify any system state

Require a cloud service or external dependency

Replace your monitoring/observability stack

Statedrift is an evidence tool, not a monitoring tool. It answers: "What was the state at time T, and can you prove it?"

Architecture

/proc, /sys, dpkg, systemctl, ip │ ▼ ┌──────────────────┐ │ Snapshot Collector│ Reads, normalizes, canonicalizes └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ Hash Chain Engine│ canonical JSON → SHA-256 → prev_hash link └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ Append-Only Store│ /var/lib/statedrift/chain/YYYY-MM-DD/HHMMSS.json └────────┬─────────┘ │ ┌────┬───┴───┬────┬──────┐ ▼ ▼ ▼ ▼ ▼ log show diff verify export │ ▼ Audit Bundle (.tar.gz + verify.sh)

Store layout

/var/lib/statedrift/ ├── head # SHA-256 of latest snapshot ├── baseline.json # Optional pinned compliance reference ├── chain/ │ ├── 2026-03-22/ │ │ ├── 140000.json # Snapshot at 14:00:00 UTC │ │ ├── 150000.json │ │ └── 160000.json │ └── 2026-03-23/ │ └── 090000.json └── exports/

Command reference

statedrift init

Initialize the snapshot store and take a genesis snapshot.

sudo statedrift init

Must be run once before any other command. Creates /var/lib/statedrift/ (or $STATEDRIFT_STORE).

statedrift snap

Take an on-demand snapshot.

sudo statedrift snap

Collects current host state, links it to the previous snapshot via hash chain, and writes it to the store. Prints a brief diff from the previous snapshot.

statedrift log

Show snapshot history.

statedrift log statedrift log --since 2026-03-01 statedrift log --since 2026-03-01 --until 2026-03-22

Flags:

Flag Description

--since YYYY-MM-DD Show snapshots on or after this date

--until YYYY-MM-DD Show snapshots on or before this date

--json Output as JSON array

statedrift show

Display the full contents of a specific snapshot.

statedrift show a3f8c1d2 # by hash prefix statedrift show HEAD # latest snapshot statedrift show HEAD~1 # one before latest statedrift show HEAD~3 # three before latest

Prints all sections: network, routes, kernel params, listening ports, packages (top 20), services.

Flags:

Flag Description

--json Output raw snapshot JSON

statedrift diff

Compare two snapshots.

statedrift diff HEAD~1 HEAD # last c

[truncated for AI cost control]