Ogentic-AuditVerifying chain
0/ 100
Walking the HMAC chain000%
ogentic·audit
Open source · Apache-2.0 · v0.3.0

A tamper-evident
record of what
your AI did

An HMAC-SHA256-chained, append-only audit log built for evidence. Change one byte and the chain breaks at that exact record — and a checkpoint you hand off catches even a keyholder rewrite.

Apache-2.0crates.io + PyPI · v0.3.0HMAC-SHA256Rust + Pythonformat v0.1 · frozen
Scroll to verify
Live

Verify it. Then try to fool it.

A four-event log from a legal matter. Verify and the whole chain checks out. Tamper a byte and it breaks at the exact record. Rewrite the history with the key and plain verification is fooled — only the checkpoint catches it.

A single-byte edit is easy to catch. The hard case is an attacker who holds the key — that's the one the checkpoint is for.

$ ogentic-audit verify ./matter-2024-CV-3047
r0vault.unlocked
🔗
r1file.opened
🔗
r2llm.cloud-approved {model: gpt-4o}
🔗
r3vault.locked
✓ Verified · 4 events · chain head f27a7231…
// faithful illustration of the published verifierTry the full demo ↗
Why it exists

Five things a log-to-a-file can't promise.

A plain log tells you what someone chose to write down. An HMAC-chained, checkpointed log tells you what actually happened — and proves it, byte for byte.

01HMAC chain

Cannot be silently edited

Every record is HMAC-chained to the one before it. Any edit by someone without the key breaks the chain — and the verifier reports the exact (segment, record_id) of the first violation, with a structured evidence payload an auditor can act on.

02checkpoint

Can prove it wasn't rewritten

Chained HMACs verify the log against itself — which someone holding the key also satisfies after re-chaining fabricated history. checkpoint pins the current head; verify --checkpoint proves the log still extends the head you saw before.

Only works if the checkpoint is held by someone who can't rewrite the log. A copy stored beside the log proves nothing.

03crash-safe

Survives crashes

Append-only, with an atomic flush + F_FULLFSYNC on macOS. A partial write never yields a half-record; a torn tail is detected and truncated with a structured RecoveryReport.

04portable format

Travels across languages

A byte-for-byte documented on-disk format, with committed golden vectors that any conforming implementation must round-trip. Rust + Python ship today; the format is implementable in any language with HMAC-SHA256.

05evidence

Is court-defensible

A paired threat model and court-defensibility brief; the CLI ships a bit-reproducible export --pdf command for self-contained evidence packages.

The brief is engineering's read; legal-team sign-off is pending (pre-GA).

What it is

One frozen format, documented byte by byte.

Not a database, not a blockchain — a single-writer, append-only file format. Each record is canonical CBOR and carries the HMAC of the record before it, so the whole log is one continuous chain.

record · segment 0
{
record_idmonotonic index within the segment
prev_hashHMAC of the previous record — the chain link
actor"user:counsel"
event"llm.cloud-approved"
payloadcanonical CBOR (RFC 8949 §4.2)
hmacHMAC-SHA256 over header ‖ prev_hash ‖ payload
}
prev_hash → hmac is the chain link — break one and the verifier stops at that exact record.
The guarantees
Chain
HMAC-SHA256 (FIPS 198-1), each record bound to prev_hash
Encoding
Canonical CBOR (RFC 8949 §4.2)
Integrity
CRC32 header · BLAKE3-256 key fingerprint
Compare
constant-time (subtle)
Genesis
first record's HMAC bound to the segment header
Layout
append-only, per-segment files

The on-disk format is frozen at v0.1 ↗ (spec + committed golden vectors). The library is v0.3.0 — a separate version line.

Scenarios

Anywhere an action has to be provable later.

Any system that takes consequential actions — a legal workflow, an autonomous agent, a compliance pipeline — needs a record that a motivated insider can't quietly rewrite.

Legal matter logs

A defensible record of a matter

Every file open, model call, and disclosure on a legal matter, chained and checkpointed — an evidence trail that stands up when the matter is contested.

matter-2024-CV-3047
AI-agent action trails

What the agent actually did

Tool calls, approvals, and side effects recorded append-only. When an agent misbehaves, the log shows exactly what happened — and can't be quietly cleaned up after.

agent.tool-called
Compliance event streams

Continuous, tamper-evident events

Access grants, approvals, and policy decisions streamed to an append-only log. Auditors verify the whole chain, or spot-check a single segment.

policy.approved
Vault access logs

Who opened what, and when

Unlock, read, and lock events for a secrets vault — the sibling record to the vault itself, provable independent of whoever administered it.

vault.unlocked
Lifecycle

Append, anchor, prove.

Five steps from a live event to a self-contained evidence package. The one that makes it defensible is the checkpoint — the moment you hand the current head to someone who can't rewrite the log.

1 · writeappend

Append a record

Each event is written append-only and HMAC-chained to the previous record's hash — single-writer, microsecond-latency.

2 · durableflush

Flush to disk

Atomic flush + F_FULLFSYNC (macOS). A crash can never leave a half-record; a torn tail is detected and truncated.

3 · anchorcheckpoint

Hand off the head

Pin the current (segment, record_id, hmac) and give it to a party who can't rewrite the log — a customer, a regulator, a counterpart.

4 · proveverify --checkpoint

Verify

Walk the chain to confirm nothing was edited — and confirm the log still extends the head you checkpointed.

5 · exportexport --pdf

Package the evidence

Emit a bit-reproducible, self-contained evidence package for handoff to counsel or a regulator.

append flush checkpoint verify --checkpoint export --pdf

// A checkpoint stored beside the log proves nothing — whoever rewrites one rewrites the other. Security comes from where you hand it.

Key sources

One KeyHandle trait, three places to keep the key.

The HMAC key is the root of trust. Where it lives is a deployment choice — swap the key source without touching the log format. Same trait, from a unit test to a KMS.

InMemoryKey

In-memory

A raw key held in process. Zero setup — for tests, CI, and reproducible golden vectors.

  • tests / CI
  • golden vectors
  • no OS dependency
ogentic-audit-core
KeychainKey

OS keychain

The signing key lives in the OS keychain — macOS Keychain, Linux Secret Service, Windows Credential Manager. For desktop.

  • macOS / Linux / Windows
  • key never in your code
  • desktop apps
ogentic-audit-keychain
KmsKey

KMS

The key stays in a managed KMS and never leaves it; signing happens there. For servers and multi-tenant deployments.

  • AWS KMS (v0.1)
  • GCP / Azure later
  • server-side / multi-tenant
ogentic-audit-kms
Case study

One matter, as written and tampered.

A four-event log from a legal matter. On the left, verification passes and prints the chain head. On the right, one byte is flipped inside the gpt-4o approval — and the verifier stops at that exact record.

matter-2024-CV-3047as written
r0vault.unlocked
🔗
r1file.opened
🔗
r2llm.cloud-approved {model: gpt-4o}
🔗
r3vault.locked
✓ Verified · 4 events · chain head f27a7231…
matter-2024-CV-3047tampered
r0vault.unlocked
🔗
r1file.opened
🔗
r2llm.cloud-approved {model: gpt-4o-mini}← byte flipped
⛓️‍💥
r3vault.locked
✗ HmacMismatch@s0r2 · first violation named

The matter-2024-CV-3047 sample ships in the repo, with a clean log and a byte-identical tampered companion — run ogentic-audit verify against either.

Code

Rust core, Python bindings, one CLI.

The core is Rust (edition 2024, MSRV 1.85); abi3 wheels wrap it for Python 3.9+; the CLI drives it. Same frozen format, same golden vectors — byte for byte.

quickstart.pyPython
from ogentic_audit import Writer, Reader, KeyHandle, verify

key = KeyHandle.from_env("OGENTIC_AUDIT_KEY_HEX")  # 64 hex chars

with Writer.open("./audit-logs", key=key) as w:
    w.append({"actor": "user:alice", "event": "vault.unlocked",
              "payload": {"vault_id": "v-001"}})

for record in Reader.open("./audit-logs"):
    print(record["record_id"], record["actor"], record["event"])

report = verify("./audit-logs", key=key)
assert report.ok
terminalCLI
export OGENTIC_AUDIT_KEY_HEX=$(openssl rand -hex 32)

# exit 0 verified, 1 on the first violation
ogentic-audit verify ./audit-logs

# pin the head, then prove the log still extends it
ogentic-audit checkpoint ./audit-logs --out head.json
ogentic-audit verify ./audit-logs --checkpoint head.json --summary
main.rsRust
use ogentic_audit_core::{InMemoryKey, RecordInput, Writer, Verifier};

let key = InMemoryKey::from_hex(env!("OGENTIC_AUDIT_KEY_HEX"))?;
let mut writer = Writer::open("./audit-logs", Box::new(key), session_id)?;

writer.append(RecordInput::new("user:alice", "vault.unlocked"))?;

let verifier = Verifier::new(Box::new(key));
let report = verifier.verify("./audit-logs")?;
assert!(report.ok());
installGet it
pip install ogentic-audit          # Python
cargo add ogentic-audit-core       # Rust library
cargo install ogentic-audit        # CLI binary
The ogentic stack

Shield decides what's sensitive. Audit proves what happened.

Composable Apache-2.0 primitives for regulated AI. Shield is the input-side classifier; Audit is the evidence layer. Two are live today; the rest are on the way.

shield
Detects input sensitivity
PyPI · v0.6
audit
Tamper-evident evidence
crates + PyPI · v0.3
router
Privacy-aware routing
soon
redact
Structure-aware redaction
soon
convert
Synthesis primitives
planned
vault
Local encrypted store
planned
adapter-sdk
Contracts
planned

shield → route → audit · classify on the way in, prove on the way out. Audit is the evidence sibling to ogentic-shield ↗ — part of the OgenticAI ↗ OSS suite.

Get started

Install it. Verify in three lines.

Rust core, Python bindings, or the CLI — one Apache-2.0 library, one frozen format. No account, no telemetry.

cargo add ogentic-audit-core  ·  cargo install ogentic-audit