promptledger
v0.1.0
Published
Tamper-evident audit trail for LLM calls. Hash-chained logging with verification, compliance export, and SDK middleware.
Maintainers
Readme
PromptLedger
Token counters tell you how much you spent. ContextGuard tells you if it was worth it. PromptLedger proves what happened.
Tamper-evident audit trail for LLM calls. Hash-chained logging with verification, compliance export, and SDK middleware.
The Problem
AI teams log LLM calls to flat files, databases, or observability platforms. None of them can answer: "Has anyone modified a recorded response after the fact?"
When a security analyst flags a CRITICAL finding but someone changes the stored output to LOW, regular logs won't catch it. PromptLedger will — every entry is SHA-256 hashed and chained to the previous entry. Modify anything, and the chain breaks.
PromptLedger proved it: In the tamper detection demo, an attacker modifies a stored severity rating from CRITICAL to LOW. Verification instantly detects the broken hash chain and pinpoints the exact tampered entry.
The Solution
Treat LLM audit trails as a ledger, not a log. Three capabilities:
- Record — Append-only hash-chained entries for every LLM call
- Verify — Chain integrity check from genesis to head in milliseconds
- Export — Compliance-ready JSON with verification proof and statistics
Quick Start
# See the tamper detection demo (records → verifies → tampers → re-verifies)
npx promptledger scenario
# Verify your ledger chain
npx promptledger verify
# Chain statistics
npx promptledger stats
# Recent entries
npx promptledger recent 20
# Export full ledger for compliance
npx promptledger export audit-2026-Q1.json
# JSON output for piping
npx promptledger verify --jsonProgrammatic Usage
Record LLM Calls
import { LedgerEngine } from 'promptledger';
const ledger = new LedgerEngine({
agent: 'claims-processor',
sessionId: 'session-001',
});
// Record any LLM call
const entry = ledger.record({
provider: 'anthropic',
model: 'claude-sonnet-4-6',
operation: 'chat',
input: [
{ role: 'system', content: 'You are a claims analyst.' },
{ role: 'user', content: 'Review this medical evidence.' },
],
output: [
{ role: 'assistant', content: 'Based on the evidence, I found...' },
],
tokens: { input: 1250, output: 890 },
latencyMs: 1340,
tags: ['claims', 'medical-review'],
});
console.log(entry.hash); // SHA-256 hash of this entry
console.log(entry.sequence); // Position in chainVerify Chain Integrity
const result = ledger.verify();
console.log(result.valid); // true — chain intact
console.log(result.entriesChecked); // 147
console.log(result.headHash); // Current chain head
console.log(result.verificationMs); // 12ms for 147 entries
// If tampered:
// result.valid === false
// result.brokenAt === 42
// result.brokenReason === "Entry 42 hash mismatch — content was modified"SDK Middleware (Anthropic)
import Anthropic from '@anthropic-ai/sdk';
import { LedgerEngine, createAnthropicWrapper } from 'promptledger';
const client = new Anthropic();
const ledger = new LedgerEngine({ agent: 'my-app' });
const wrap = createAnthropicWrapper({ ledger, provider: 'anthropic' });
// Wrap any SDK call — automatically recorded to ledger
const request = {
model: 'claude-sonnet-4-6',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello' }],
};
const { response, entryId, entryHash } = await wrap(
() => client.messages.create(request),
request,
);
// response = normal Anthropic response
// entryId = UUID of the ledger entry
// entryHash = SHA-256 hash for this callSDK Middleware (OpenAI)
import OpenAI from 'openai';
import { LedgerEngine, createOpenAIWrapper } from 'promptledger';
const client = new OpenAI();
const ledger = new LedgerEngine({ agent: 'my-app' });
const wrap = createOpenAIWrapper({ ledger, provider: 'openai' });
const request = {
model: 'gpt-4o',
messages: [{ role: 'user', content: 'Hello' }],
};
const { response, entryId } = await wrap(
() => client.chat.completions.create(request),
request,
);Export for Compliance
const exportData = ledger.export();
// exportData includes:
// - version: '1.0'
// - verification: { valid: true, entriesChecked: 147, ... }
// - stats: { totalEntries: 147, totalTokens: { ... }, ... }
// - entries: [ ... all entries ... ]
// Write to file
import { writeFileSync } from 'fs';
writeFileSync('audit-export.json', JSON.stringify(exportData, null, 2));Query & Filter
// By agent
const agentCalls = ledger.query({ agent: 'claims-processor' });
// By model
const claudeCalls = ledger.query({ model: 'claude-sonnet-4-6' });
// By time range
const todayCalls = ledger.query({
since: '2026-04-01T00:00:00Z',
until: '2026-04-02T00:00:00Z',
});
// Combined
const recent = ledger.query({
agent: 'security-analyst',
operation: 'chat',
limit: 50,
});How It Works
Entry #0 (Genesis) Entry #1 Entry #2
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ previousHash: 000… │ │ previousHash: a3f… │ │ previousHash: 7d2… │
│ input: [...] │ │ input: [...] │ │ input: [...] │
│ output: [...] │ │ output: [...] │ │ output: [...] │
│ tokens: {in, out} │ │ tokens: {in, out} │ │ tokens: {in, out} │
│ hash: a3f… ─────┼──→│ hash: 7d2… ─────┼──→│ hash: e91… │
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
If Entry #1 output is modified after recording:
→ Recomputed hash ≠ stored hash → CHAIN BROKEN at #1
→ Entry #2 previousHash ≠ Entry #1 new hash → CHAIN BROKEN at #2Each entry's hash covers: sequence + previousHash + timestamp + provider + model + operation + input + output + tokens. Change any field and the hash changes. The chain catches both content tampering and entry reordering.
CLI Reference
promptledger scenario Tamper detection demo
promptledger verify Verify chain integrity (exit code 0=intact, 1=broken)
promptledger stats Chain statistics
promptledger recent [N] Most recent N entries (default: 10)
promptledger export [file] Export ledger as JSON (default: promptledger-export.json)
promptledger --help Help
Options:
--db <path> Database path (default: ~/.promptledger/ledger.db)
--json JSON outputStorage
PromptLedger uses SQLite (via better-sqlite3) for storage:
- Default location:
~/.promptledger/ledger.db - WAL mode for concurrent read performance
- Indexed on: sequence, timestamp, agent, session_id, model
- Override with
--db <path>(CLI) orstoragePathoption (API) - Use
:memory:for testing / ephemeral ledgers
Compliance Mapping
| Requirement | Framework | How PromptLedger Addresses It | |------------|-----------|-------------------------------| | Traceability | EU AI Act Art. 12 | Every LLM call recorded with full input/output | | Logging & Monitoring | NIST AI RMF MG-2.2 | Append-only ledger with chain verification | | Audit Trail | ISO 42001 A.6.2.6 | Hash-chained entries with tamper detection | | Audit Record Content | NIST 800-53 AU-3 | Structured records: who, what, when, tokens | | Audit Record Integrity | NIST 800-53 AU-10 | SHA-256 hash chain prevents undetected modification | | Non-repudiation | NIST 800-53 AU-10(2) | Chain verification proves entry authenticity |
License
MIT
Built by ACE — Advanced Consulting Experts
Regular logs tell you what happened. PromptLedger proves nobody changed the story.
