Xtrace: Tamper-Evident Audit Trails
HMAC-chained distributed audit logs with optional XorIDA split storage. Each entry is cryptographically chained to its predecessor via SHA-256 hashing, HMAC-signed for independent verification, and optionally split across storage nodes for compliance and fault tolerance. Append-only JSONL format with zero npm dependencies.
Executive Summary
Xtrace creates audit trails where tampering with any entry breaks the cryptographic chain for all subsequent entries, making unauthorized modifications immediately detectable.
Every audit entry is HMAC-SHA256 signed for independent verification. Each entry's hash depends on the previous entry's hash via SHA-256(prevHash + JSON(entry)), creating a tamper-evident chain. Modifying any entry invalidates the chain from that point forward.
For distributed compliance architectures, XorIDA split storage divides each entry into threshold shares across N storage nodes. Any k-of-N nodes can reconstruct the entry, but fewer than k shares reveal zero information about the plaintext — not computationally hard to break, but mathematically impossible.
Append-only JSONL storage ensures immutability at the filesystem level. Zero npm runtime dependencies. Works anywhere the Web Crypto API is available — Node.js, Deno, Bun, Cloudflare Workers.
The Problem
Traditional audit logs are mutable text files. An attacker with write access can delete entries, modify timestamps, or rewrite entire logs without leaving evidence.
Logs are often unverified. Standard logging libraries write plaintext to disk. There's no built-in proof that log entries haven't been altered since creation.
Centralized logs are single points of failure. One compromised log server means the entire audit trail can be tampered with or destroyed.
Compliance requires tamper-evidence. HIPAA, SOC 2, ISO 27001, and SEC 17a-4 all require audit trails that detect unauthorized modifications. Standard logs don't provide cryptographic proof.
Distributed logs lack integrity. Replicating logs across nodes for availability doesn't prevent tampering if all replicas can be modified in sync.
| Property | Standard Logs | Signed Logs | Blockchain | Xtrace |
|---|---|---|---|---|
| Tamper detection | No | Single sigs | Yes | Chained HMAC |
| Distributed storage | Replicas | Replicas | P2P consensus | XorIDA split |
| Single-node compromise | Full loss | Full loss | Partial | Zero info |
| Verification speed | N/A | Per-entry | Full chain | <1ms/entry |
| Independent verification | No | HMAC | Requires sync | HMAC + chain |
| Setup complexity | Trivial | Moderate | High | 5 lines |
| npm dependencies | 1-5 | 10+ | 50+ | 0 |
Real-World Use Cases
Six scenarios where tamper-evident audit trails are compliance or security requirements.
Every PHI access generates a chained audit entry. HIPAA requires tamper-evident logs. Split storage across 3 nodes ensures no single compromise reveals patient data.
splitAuditEntry() + 2-of-3Trade order modifications logged in an immutable chain. SEC requires WORM (write-once-read-many) audit trails. Xtrace provides cryptographic WORM via chained hashes.
appendToChain() + verifyChain()Key rotation events, permission changes, and access grants logged with tamper detection. SOC 2 requires demonstrable log integrity. HMAC chains provide cryptographic proof.
verifyEntryHMAC() + chain validationFederal systems require tamper-evident audit logs for compliance. Split storage across classified and unclassified networks with information-theoretic security.
XorIDA split + HMAC verificationDocument access and modifications tracked in a tamper-evident chain. Legal teams can prove when and by whom evidence was accessed or altered.
ChainedAuditEntry + timestamp validationFirmware deployment events logged with cryptographic proof. Detect unauthorized firmware modifications across fleet of 10,000+ devices.
appendToChain() + GENESIS_HASHSolution Architecture
Four composable functions for building tamper-evident audit systems.
HMAC-Chained Audit Trail
Each entry's hash depends on the previous entry's hash. Tampering with any entry breaks the chain for all subsequent entries.
import { appendToChain, verifyChain, GENESIS_HASH } from '@private.me/auditlog'; // First entry starts from GENESIS_HASH const entry1 = { entryId: 'e1', timestamp: Date.now(), actor: 'admin@corp.com', action: 'key.rotate', resource: 'master-key-v3', }; const chained1 = await appendToChain(entry1, GENESIS_HASH); if (!chained1.ok) throw new Error(chained1.error.message); // Second entry chains from first const entry2 = { entryId: 'e2', timestamp: Date.now(), actor: 'admin@corp.com', action: 'user.create', resource: 'bob@corp.com', }; const chained2 = await appendToChain(entry2, chained1.value.hash); // Verify the entire chain const valid = await verifyChain([chained1.value, chained2.value!]); console.log('Chain valid:', valid.ok); // true
XorIDA Split Storage
Distribute audit entries across N storage nodes. Any k-of-N nodes can reconstruct an entry. Fewer than k shares reveal zero information.
import { splitAuditEntry, reconstructAuditEntry } from '@private.me/auditlog'; // Split entry into 3 shares, require 2 for reconstruction const splits = await splitAuditEntry(chainedEntry, 3, 2); if (!splits.ok) throw new Error(splits.error.message); // Store each share on a different node await storeOnNode('node-1', splits.value[0]); await storeOnNode('node-2', splits.value[1]); await storeOnNode('node-3', splits.value[2]); // Reconstruct from any 2 shares const restored = await reconstructAuditEntry([splits.value[0], splits.value[1]]); console.log('Reconstructed:', restored.ok); // true
Integration
Five lines to add tamper-evident audit logging to any system.
import { appendToChain, verifyChain } from '@private.me/auditlog'; import { readFileSync, appendFileSync } from 'fs'; // Read the existing chain from JSONL const lines = readFileSync('audit.jsonl', 'utf-8').trim().split('\n'); const chain = lines.map(line => JSON.parse(line)); const lastHash = chain[chain.length - 1]?.hash || GENESIS_HASH; // Append new entry const entry = { entryId: crypto.randomUUID(), timestamp: Date.now(), actor: 'system', action: 'backup.complete', resource: 'database-2026-04-10', }; const chained = await appendToChain(entry, lastHash); if (chained.ok) { appendFileSync('audit.jsonl', JSON.stringify(chained.value) + '\n'); }
Integration with Other ACIs
Xtrace composes with other PRIVATE.ME ACIs for comprehensive compliance architectures:
Security Guarantees
Five cryptographic guarantees that make Xtrace tamper-evident.
1. Tamper-Evident Chain
Each entry's hash depends on the previous entry's hash via SHA-256(prevHash + JSON(entry)). Modifying any entry changes its hash, which breaks the chain for all subsequent entries. Verification recomputes hashes and checks prevHash links.
2. HMAC Integrity
Every entry is independently HMAC-SHA256 signed. Individual entries can be verified without replaying the entire chain. The HMAC key is embedded in the hmacSig field (format: base64(key):base64(signature)).
3. HMAC Before Reconstruct
When reconstructing split entries, HMAC verification executes on the padded data before deserialization. Corrupted data is rejected before JSON parsing. This prevents injection attacks via malformed JSONL.
4. Information-Theoretic Security
XorIDA threshold sharing over GF(2) ensures any k-1 or fewer shares reveal zero information about the entry content. Not computationally hard to break — mathematically impossible. Quantum-proof by construction.
5. No Math.random()
All randomness uses crypto.getRandomValues() via @private.me/crypto. No predictable pseudo-random number generation. HMAC keys and XorIDA shares use cryptographically secure random sources.
hmacSig field and in split shares' hmac field. Transport-layer encryption (TLS) should protect entries and shares in transit. Xtrace provides tamper-evidence, not transport encryption.
Performance Benchmarks
Measured on M1 MacBook Pro, Node.js 20.11.0, 100-iteration average.
Scalability
| Operation | 100 entries | 1,000 entries | 10,000 entries |
|---|---|---|---|
| Build chain | ~500ms | ~5s | ~50s |
| Verify chain | ~100ms | ~1s | ~10s |
| Split all entries (3 shares) | ~800ms | ~8s | ~80s |
Honest Limitations
What Xtrace does NOT provide.
1. Chain Order Requirement
Chain verification requires entries in order. Out-of-order entries will fail prevHash checks. Distributed systems must ensure append-only ordering or use timestamp-based conflict resolution.
2. HMAC Key Exposure
The HMAC key is embedded in the hmacSig field (and in split shares' hmac field). Transport-layer encryption (TLS) should protect entries and shares in transit. Xtrace does NOT encrypt the entry content — only the chain integrity is protected.
3. No Automatic Uniqueness
appendToChain() does not enforce uniqueness of entryId. The caller is responsible for generating unique IDs (e.g., crypto.randomUUID()).
4. Metadata JSON Serialization
Metadata is stored as Record<string, unknown> and is included in the hash computation via JSON.stringify(). Objects with circular references or non-serializable values will fail. Use only JSON-compatible data types.
5. No Built-in Timestamp Validation
Xtrace does not validate that entry timestamps are monotonically increasing. The caller must implement timestamp validation if required for compliance (e.g., SEC 17a-4 requires timestamps synchronized to NIST time servers).
6. No Built-in Storage Backend
Xtrace provides the cryptographic primitives for tamper-evident chains. It does NOT include a storage backend. The caller must implement JSONL append-only file writes, database inserts, or object storage uploads. See the Enterprise CLI for reference implementations.
Enterprise CLI
Production-ready HTTP server with JSONL file backend, REST API, and health checks. Part of the PRIVATE.ME Enterprise CLI Suite.
# Install from private npm registry pnpm add @private.me/auditlog-cli # Start server (port 4100) pnpm --filter @private.me/auditlog-cli start # Or run directly node node_modules/@private.me/auditlog-cli/src/server.ts
CLI Endpoints
Complete ACI Surface
Five public functions, five types, five error codes.
Functions
Constants
Error Codes, Storage Backends, Wire Format
Deep dives into error taxonomy, pluggable storage implementations, and JSONL serialization format.
Error Taxonomy
Five error codes covering validation, chain integrity, HMAC failures, split operations, and reconstruction.
| Error Code | Returned By | Cause |
|---|---|---|
| INVALID_ENTRY | appendToChain | Entry is missing required fields (actor, action, or resource is empty/falsy). |
| CHAIN_BROKEN | verifyChain | An entry's prevHash does not match the previous entry's hash, or recomputed hash does not match the stored hash. |
| HMAC_FAILURE | reconstructAuditEntry | HMAC-SHA256 verification of the reconstructed padded data failed. |
| SPLIT_FAILED | splitAuditEntry | totalShares < 2, threshold < 2, threshold > totalShares, or XorIDA split threw. |
| RECONSTRUCT_FAILED | reconstructAuditEntry | No shares provided, insufficient shares, XorIDA failed, PKCS#7 unpad failed, or JSON parse failed. |
AuditError interface: { code: AuditErrorCode; message: string }. Use the Result<T, E> pattern for error handling: check .ok, access .value or .error.
Storage Backends
Three reference implementations: JSONL file, SQLite, and S3 object storage.
1. JSONL File Backend (Reference Implementation)
import { appendFileSync, readFileSync } from 'fs'; function appendEntry(entry: ChainedAuditEntry) { appendFileSync('audit.jsonl', JSON.stringify(entry) + '\n'); } function loadChain(): ChainedAuditEntry[] { const lines = readFileSync('audit.jsonl', 'utf-8').trim().split('\n'); return lines.map(line => JSON.parse(line)); }
2. SQLite Backend (Structured Query)
-- Schema CREATE TABLE audit_chain ( entry_id TEXT PRIMARY KEY, timestamp INTEGER NOT NULL, actor TEXT NOT NULL, action TEXT NOT NULL, resource TEXT NOT NULL, prev_hash TEXT NOT NULL, hash TEXT NOT NULL, hmac_sig TEXT NOT NULL, metadata TEXT ); CREATE INDEX idx_timestamp ON audit_chain(timestamp); CREATE INDEX idx_actor ON audit_chain(actor); CREATE INDEX idx_action ON audit_chain(action);
3. S3 Object Storage (Distributed)
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; async function uploadEntry(entry: ChainedAuditEntry) { const s3 = new S3Client({ region: 'us-east-1' }); await s3.send(new PutObjectCommand({ Bucket: 'audit-logs', Key: `entries/${entry.entryId}.json`, Body: JSON.stringify(entry), })); }
Wire Format
JSONL serialization format for ChainedAuditEntry and SplitAuditEntry.
ChainedAuditEntry Format
{
"entryId": "e1",
"timestamp": 1712745600000,
"actor": "admin@corp.com",
"action": "key.rotate",
"resource": "master-key-v3",
"prevHash": "0000000000000000000000000000000000000000000000000000000000000000",
"hash": "a1b2c3d4e5f6...",
"hmacSig": "key_base64:sig_base64",
"metadata": { "reason": "quarterly rotation" }
}
SplitAuditEntry Format
{
"entryId": "e1",
"shareIndex": 0,
"shareTotal": 3,
"shareThreshold": 2,
"data": "base64_encoded_share_data...",
"hmac": "key_base64:sig_base64",
"originalSize": 512
}
Deployment Options
SaaS Recommended
Fully managed infrastructure. Call our REST API, we handle scaling, updates, and operations.
- Zero infrastructure setup
- Automatic updates
- 99.9% uptime SLA
- Pay per use
SDK Integration
Embed directly in your application. Runs in your codebase with full programmatic control.
npm install @private.me/auditlog- TypeScript/JavaScript SDK
- Full source access
- Enterprise support available
On-Premise Enterprise
Self-hosted infrastructure for air-gapped, compliance, or data residency requirements.
- Complete data sovereignty
- Air-gap capable
- Docker + Kubernetes ready
- RBAC + audit logs included