cd /news/ai-agents/an-append-only-hash-chained-audit-lo… · home › topics › ai-agents › article
[ARTICLE · art-155407] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

An append-only, hash-chained audit log in SQLite, and a script to check it

Nicholas Flemmer built Styx, an Apache-2.0 guardrail for coding agents that records every access request, grant, use, denial, revocation and expiry in an append-only, hash-chained audit log stored in local SQLite. Each row carries a sequence number, the previous row's hash and a SHA-256 hash of the prior hash plus the row's contents, with triggers blocking updates and deletes and secrets scrubbed before writing. Flemmer also published a short Node script that exports the table read-only via sqlite3 and verifies the chain, while noting the log cannot stop someone with file write access from rewriting and rehashing the entire history.

by read3 min views3 publishedOct 22, 2026

When a coding agent can ask for production access, the question afterwards is simple: who touched production, with what, and why? Styx answers it with an audit log on your machine. This post is about how that log is built, what it does and doesn't protect against, and a short script you can run to verify it.

Every time an agent asks for access, and every time access is granted, used, denied, revoked or expires, one row is written. Connecting or removing a target, changing a policy and opening a pull request are rows too. Each row carries:

$ vercel deploy --prod Before a row is written, anything shaped like a secret is scrubbed out of commands, reasons and labels, so a token an agent pasted into a command line doesn't end up in the log.

The log is a table in the local SQLite database. Triggers on the table abort any update or delete. Nothing in the app edits a row, and rows are only written through one function, AuditService.append. When a grant is revoked, Styx doesn't change the 'granted' row; it adds a 'revoked' row.

Each row has a sequence number, the hash of the row before it (prev_hash) and its own hash: a SHA-256 of the previous hash and the row's contents. Change any field of any row, or remove one from the middle, and the chain breaks from that point on.

The app has no verify button yet, but the data is just a table. Export it read-only with sqlite3 (3.33 or later, for -json) and pipe it into Node:

sqlite3 -readonly -json ~/Library/Application\ Support/Styx/styx.db \
  "SELECT * FROM audit_entries ORDER BY seq" | node verify-audit.mjs

With this saved as verify-audit.mjs:

import { createHash } from 'node:crypto';

const chunks = [];
for await (const chunk of process.stdin) chunks.push(chunk);
const rows = JSON.parse(Buffer.concat(chunks).toString('utf8') || '[]');
const camel = (k) => k.replace(/_([a-z])/g, (_, c) => c.toUpperCase());

let prev = '';
for (const r of rows) {
  const row = {};
  for (const [k, v] of Object.entries(r)) if (k !== 'hash') row[camel(k)] = v;
  const canonical = JSON.stringify(row, Object.keys(row).sort());
  const hash = createHash('sha256').update(prev).update('\n').update(canonical).digest('hex');
  if (row.prevHash !== prev || hash !== r.hash) {
    console.log(`Chain broken at entry ${row.seq}`);
    process.exit(1);
  }
  prev = r.hash;
}
console.log(`Chain intact: ${rows.length} entries`);

It prints Chain intact with the number of entries, or the first row where the chain breaks. The database is at ~/Library/Application Support/Styx/styx.db on a Mac, %APPDATA%\Styx\styx.db on Windows and ~/.config/Styx/styx.db on Linux.

The chain shows that rows weren't edited one at a time after the fact. It doesn't stop someone who can write to your files from rewriting the whole log and recomputing every hash. If that matters to you, copy the latest hash somewhere else now and then; a later rewrite of the history before it won't match.

It's also a record, not a barrier. Styx is a guardrail, not a sandbox: agents run as you, and one that deliberately goes around Styx's tools isn't stopped by the log or by anything else in it. What the log gives you is a trustworthy account of everything that did go through Styx.

The code is Apache-2.0 at github.com/NicholasFlemmer/styx-app.

── more in #ai-agents 4 stories · sorted by recency
── more on @styx 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/an-append-only-hash-…] indexed:0 read:3min 2026-10-22 · —