Package Exports
- @runback/verify
Readme
@runback/verify
Verify the tamper-evidence and integrity of runback.cassette/v1 audit records. No account required. Pure Node.js — zero external dependencies.
Every AI agent decision captured by Runback is sealed in a hash-chained, HMAC-signed cassette. This package lets anyone verify the record independently — without Runback software, without a Runback account.
Install
npm install @runback/verify
# or
npx @runback/verify cassette.jsonCLI
# Verify a cassette file
runback-verify cassette.json
# Verify from stdin
cat cassette.json | runback-verify -
# Verify the signature (if the record is signed)
runback-verify cassette.json --key <signing-key>Output:
runback.cassette/v1 — VALID
run_id loan-approval-agent-2026-07-01
generated_at 2026-07-01T09:14:22.000Z
events 6
✓ chain — event hashes form an unbroken SHA-256 chain
✓ digest — manifest.content_digest matches terminal chain hash
✓ cassette — oracle-stream digest matches manifest.replay.cassette_digest
– signature — HMAC-SHA256 (record not signed)
spec https://runback.dev/specExit code 0 = valid. Exit code 1 = invalid or error.
Library
import { verify, verifyJson } from '@runback/verify';
// From a parsed object
const result = verify(cassette);
// From raw JSON string
const result = verifyJson(jsonString);
// With signing key
const result = verify(cassette, process.env.AUDIT_SIGNING_KEY);
console.log(result.valid); // true / false
console.log(result.checks.chain); // true / false
console.log(result.checks.digest); // true / false
console.log(result.checks.cassette); // true / false
console.log(result.checks.signature); // "valid" | "invalid" | "unsigned" | "no-key"What it verifies
Three required checks, one optional:
| Check | What it proves |
|---|---|
| chain | Every event._hash matches SHA-256(h_{i-1} + canonical(event)). Tampering with any event breaks the chain at that point and every hash after it. |
| digest | manifest.content_digest equals the terminal chain hash — a single value covering all events. |
| cassette | manifest.replay.cassette_digest equals the oracle-stream digest — proving the recording IS the deterministic input stream of the agent run, not a post-hoc reconstruction. |
| signature | HMAC-SHA256(key, content_digest:cassette_digest) — optional. Proves the record came from a holder of the signing key. |
Algorithm
# Event chain
h_0 = ""
h_i = SHA-256( h_{i-1} + canonical( event_i_without_hash ) )
canonical = JSON.stringify with all keys sorted recursively
# Oracle stream (cassette digest)
For each event sorted by seq:
if type === "llm": entry = { kind: "llm", key: SHA-256("llm:{model_id}:{canonical(request)}"), output: response }
if type === "tool": entry = { kind: "tool", key: SHA-256("tool:{tool_name}:{canonical(input)}"), output: output }
if type === "env": entry = { kind: e.kind, key: e.key, output: e.output }
else: skip
chain_hash = SHA-256( prev_hash + canonical(entry) )Full specification: runback.dev/spec
Online verifier
Paste a cassette at runback.dev/verify — runs entirely in your browser, the cassette never leaves your machine.
License
MIT