npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

llm-audit-log

v0.2.2

Published

Tamper-evident compliance-ready audit logging for LLM I/O

Downloads

236

Readme

llm-audit-log

Tamper-evident, compliance-ready audit logging for LLM input/output.

npm version npm downloads license node


Description

llm-audit-log creates SOC 2, GDPR, and HIPAA-grade audit trails for LLM API interactions without requiring a hosted observability platform. Every call to OpenAI, Anthropic, Google, Azure OpenAI, AWS Bedrock, or any custom provider is recorded as a structured, append-only entry with HMAC-SHA256 integrity chains, PII detection and redaction, JSONL file storage with automatic rotation, configurable retention policies, and multi-format export.

Everything runs locally. Everything is stored locally. No network connection is required.

Key properties of the audit log:

  • Tamper-evident -- HMAC-SHA256 chains detect insertion, deletion, modification, and reordering of entries.
  • Compliance-ready -- structured entries capture actor, model, provider, tokens, latency, cost, tool calls, errors, and custom metadata.
  • Privacy-aware -- built-in PII detection and redaction for emails, phone numbers, SSNs, credit card numbers, and IP addresses.
  • Zero runtime dependencies -- uses only Node.js built-ins (node:crypto, node:fs, node:path).

Installation

npm install llm-audit-log

Requires Node.js 18 or later.


Quick Start

import { createAuditLog } from 'llm-audit-log';

const log = createAuditLog({
  storagePath: './audit.jsonl',
  hmacSecret: process.env.AUDIT_HMAC_SECRET,
  redactPii: true,
});

// Record an LLM interaction
const entry = await log.log({
  actor: 'user:[email protected]',
  model: 'gpt-4o',
  provider: 'openai',
  input: [{ role: 'user', content: 'Summarize the Q3 report' }],
  output: 'Q3 revenue was $4.2B, up 12% YoY...',
  tokens: { input: 2400, output: 350 },
  latencyMs: 1842,
  cost: 0.0385,
  metadata: { session: 'abc-123', env: 'production' },
});

// Verify the HMAC integrity chain
const result = await log.verify();
console.log(result.valid); // true

// Query entries
const entries = await log.query({ actor: 'user:[email protected]' });

// Export as CSV for auditor review
const csv = await log.export('csv', { actor: 'user:[email protected]' });

await log.close();

Features

HMAC-SHA256 Integrity Chains

Each entry's HMAC is computed over its canonical JSON content concatenated with the previous entry's HMAC, forming a hash chain. Modifying, inserting, deleting, or reordering any entry breaks the chain from that point forward. The first entry in the chain uses a configurable seed value (auto-generated if not provided).

PII Detection and Redaction

Built-in regex-based detection for five PII types: email addresses, phone numbers, US Social Security Numbers, credit card numbers, and IPv4 addresses. PII is automatically replaced with [REDACTED] before the entry is written to storage. Custom regex patterns can be added for domain-specific identifiers (medical record numbers, account IDs, etc.).

JSONL Storage with File Rotation

Entries are stored as one JSON object per line in append-only .jsonl files. When the file exceeds the configured size limit (default 50 MiB), it is automatically rotated to a numbered archive (audit.jsonl.1, audit.jsonl.2, etc.). Queries and exports read across all rotated files transparently. Files are created with mode 0o600 (owner read/write only).

Retention Policies

Configure automatic purging of entries older than a specified number of days. The retention manager runs an initial purge on start and schedules periodic checks at a configurable interval (default 24 hours). The timer is unreffed so it does not prevent process exit.

Multi-Format Export

Export entries as JSON (pretty-printed array), JSONL (compact, one object per line), or CSV (flattened with proper escaping). All export formats support query filters so you can export a subset of entries -- for example, all entries for a specific actor to fulfill a GDPR Subject Access Request.

Multi-Session Chain Continuity

The HMAC chain is automatically resumed across process restarts. When a new AuditLogger is created against an existing log file, it reads the last entry's HMAC and continues the chain from that point. No manual state management is required.


API Reference

Factory Function

createAuditLog(options?: AuditLogOptions): AuditLogger

Create a configured AuditLogger instance.

import { createAuditLog } from 'llm-audit-log';

const log = createAuditLog({
  storagePath: './audit.jsonl',
  hmacSecret: 'your-secret-key',
});

AuditLogger Class

The core class that orchestrates logging, querying, verification, export, and retention.

import { AuditLogger } from 'llm-audit-log';

const logger = new AuditLogger({
  storagePath: './audit.jsonl',
  hmacSecret: 'your-secret-key',
});

Properties

| Property | Type | Description | |----------|------|-------------| | active | boolean | Whether the logger is active (not closed). | | entryCount | number | Total entries written since creation. |

log(input: RecordInput): Promise<AuditEntry>

Record a single LLM interaction. Automatically generates id (UUIDv4), v (schema version 1), timestamp (ISO 8601 UTC), computes tokens.total, merges PII fields, applies PII redaction if enabled, computes the HMAC if a secret is configured, and appends the entry to storage.

const entry = await logger.log({
  actor: 'user:alice',
  model: 'gpt-4o',
  provider: 'openai',
  input: 'What is 2+2?',
  output: '2+2 equals 4.',
  tokens: { input: 10, output: 8 },
  latencyMs: 500,
  cost: 0.001,
  toolCalls: [{ name: 'calculator', arguments: { expr: '2+2' }, id: 'tc-1', result: 4 }],
  error: null,
  metadata: { session: 'xyz', tags: ['math'] },
  piiFields: ['metadata.userId'],
});

Throws Error with message 'AuditLogger is closed' if called after close().

query(filters?: QueryFilters): Promise<AuditEntry[]>

Search and filter stored entries. Returns entries matching all provided filters.

const results = await logger.query({
  actor: 'user:alice',
  model: 'gpt-4o',
  startDate: new Date('2026-01-01'),
  endDate: new Date('2026-12-31'),
  tags: ['production'],
  limit: 50,
  offset: 0,
  excludeTombstones: true,
});

verify(): Promise<VerificationResult>

Walk the HMAC integrity chain, recompute each HMAC, and report the chain's status. Returns an error message if no hmacSecret was configured.

const result = await logger.verify();
if (!result.valid) {
  console.error(`Chain broken at index ${result.firstInvalidIndex}`);
  console.error(`Entry ID: ${result.invalidEntryId}`);
  console.error(`Expected: ${result.expectedHmac}`);
  console.error(`Actual:   ${result.actualHmac}`);
}
console.log(`Verified ${result.entryCount} entries in ${result.durationMs}ms`);

export(format: ExportFormat, filters?: QueryFilters): Promise<string>

Export entries in the specified format. Supported formats: 'json', 'csv', 'jsonl'.

const json = await logger.export('json');
const csv = await logger.export('csv', { actor: 'user:alice' });
const jsonl = await logger.export('jsonl', { model: 'gpt-4o' });

purge(before: Date): Promise<number>

Remove all entries with a timestamp before the given date. Returns the count of purged entries. Rewrites the storage file with remaining entries and removes rotated archive files.

const purged = await logger.purge(new Date('2025-01-01'));
console.log(`Purged ${purged} entries`);

startRetention(retentionDays: number, checkIntervalMs?: number): Promise<void>

Start the automatic retention manager. Runs an immediate purge of entries older than retentionDays days, then schedules periodic purge checks at checkIntervalMs (default: 86,400,000 ms / 24 hours).

await logger.startRetention(365); // Purge entries older than 1 year
await logger.startRetention(90, 3_600_000); // 90 days, check every hour

close(): Promise<void>

Stop the retention manager, release storage resources, and mark the logger as inactive. Idempotent -- safe to call multiple times.

await logger.close();
console.log(logger.active); // false

HMAC Functions

computeHmac(entry: AuditEntry, secret: string | Buffer, previousHmac?: string | null, seed?: string): string

Compute the HMAC-SHA256 hex digest for a single audit entry. The hmac and hmacSeed fields on the entry are excluded from the computation. For the first entry in a chain, pass null for previousHmac and provide a seed. For subsequent entries, pass the previous entry's HMAC.

import { computeHmac } from 'llm-audit-log';

const hmac = computeHmac(entry, 'secret', null, 'initial-seed');       // First entry
const hmac2 = computeHmac(entry2, 'secret', hmac);                     // Chained entry

verifyChain(entries: AuditEntry[], secret: string | Buffer): { valid: boolean; brokenAt: number; expectedHmac?: string; actualHmac?: string }

Verify the integrity of an ordered array of audit entries. Returns { valid: true, brokenAt: -1 } for a valid chain (including an empty array). On failure, brokenAt is the index of the first invalid entry, and expectedHmac/actualHmac provide the mismatched values.

import { verifyChain } from 'llm-audit-log';

const result = verifyChain(entries, 'secret');
if (!result.valid) {
  console.error(`Broken at index ${result.brokenAt}`);
}

canonicalJSON(obj: unknown): string

Produce a deterministic JSON string with object keys sorted alphabetically at every nesting level. Arrays preserve element order. Used internally for HMAC computation to ensure identical entries always produce identical hashes regardless of property insertion order.

import { canonicalJSON } from 'llm-audit-log';

canonicalJSON({ z: 1, a: 2 }); // '{"a":2,"z":1}'
canonicalJSON({ b: { z: 1, a: 2 }, a: 1 }); // '{"a":1,"b":{"a":2,"z":1}}'

PII Functions

detectPii(text: string): PiiMatch[]

Scan a string for PII patterns and return an array of matches sorted by start position. Each match includes the PII type, matched value, and character offsets.

import { detectPii } from 'llm-audit-log';

const matches = detectPii('Email: [email protected], SSN: 123-45-6789');
// [
//   { type: 'email', value: '[email protected]', start: 7, end: 23 },
//   { type: 'ssn', value: '123-45-6789', start: 30, end: 41 },
// ]

Detected types: 'email', 'phone', 'ssn', 'creditCard', 'ipAddress'.

redactString(text: string, placeholder?: string, customPatterns?: RegExp[]): string

Replace all detected PII patterns in a string with a placeholder (default: '[REDACTED]'). Overlapping matches are merged into a single replacement. Custom regex patterns can be provided alongside the built-in patterns.

import { redactString } from 'llm-audit-log';

redactString('Email: [email protected]'); // 'Email: [REDACTED]'
redactString('Card: 4111 1111 1111 1111', '***'); // 'Card: ***'
redactString('Patient MRN-12345', '[REDACTED]', [/MRN-\d+/g]); // 'Patient [REDACTED]'

redactFields(entry: AuditEntry, options?: { piiFields?: string[]; redactPatterns?: boolean; customPatterns?: RegExp[]; placeholder?: string }): AuditEntry

Redact PII in an audit entry (mutated in place). Two modes:

  • Field-level redaction (piiFields): replaces the value at each dot-notation path with the placeholder. Works on strings, objects, and arrays.
  • Pattern-based redaction (redactPatterns: true): scans all string values in input, output, and metadata for PII patterns and replaces matches.
import { redactFields } from 'llm-audit-log';

redactFields(entry, { piiFields: ['input', 'metadata.email'] });
redactFields(entry, { redactPatterns: true, customPatterns: [/MRN-\d+/g] });

Storage

JsonlStorage Class

The built-in JSONL file storage backend implementing the StorageBackend interface. Typically used internally by AuditLogger, but can be instantiated directly for advanced use cases.

import { JsonlStorage } from 'llm-audit-log';

const storage = new JsonlStorage({
  filePath: './audit.jsonl',
  maxFileSize: 52_428_800, // 50 MiB
  autoRotate: true,
});

await storage.init();
await storage.append(entry);
const entries = await storage.read();
const count = await storage.count();
const size = await storage.size();
await storage.close();

Methods: init(), append(entry), read(), query(filters), purge(before), export(format, filters?), count(), size(), close().


Retention

RetentionManager Class

Manages automatic purging of old entries on a configurable schedule.

import { RetentionManager } from 'llm-audit-log';

const manager = new RetentionManager(storageBackend, {
  maxAge: 365 * 24 * 60 * 60 * 1000, // 1 year in ms
  checkIntervalMs: 86_400_000,         // 24 hours
});

await manager.start();   // Initial purge + schedule
await manager.runPurge(); // Manual one-off purge
manager.getCutoffDate();  // Current cutoff as Date
manager.stop();           // Cancel scheduled purges

Export Functions

Standalone formatting functions for converting entries to different output formats.

exportEntries(backend: StorageBackend, format: ExportFormat, filters?: QueryFilters): Promise<string>

Read entries from a storage backend and export them in the given format.

toJson(entries: AuditEntry[]): string

Format entries as a pretty-printed JSON array.

toJsonl(entries: AuditEntry[]): string

Format entries as JSONL (one compact JSON object per line, trailing newline). Returns an empty string for an empty array.

toCsv(entries: AuditEntry[]): string

Format entries as CSV with a header row. Nested fields (tokens.input, tokens.output, tokens.total) are flattened. Complex values (input, output, toolCalls, error, metadata, piiFields) are JSON-stringified. Values containing commas, quotes, or newlines are properly escaped.

CSV columns: id, v, timestamp, actor, model, provider, input, output, tokens_input, tokens_output, tokens_total, latencyMs, cost, toolCalls, error, metadata, piiFields, hmac, tombstone.


Configuration

AuditLogOptions

| Option | Type | Default | Description | |--------|------|---------|-------------| | storagePath | string | './audit.jsonl' | Path to the JSONL storage file. Directories are created recursively if they do not exist. | | hmacSecret | string \| Buffer | undefined | HMAC secret key. When provided, every entry receives an HMAC and the integrity chain is enabled. | | hmacSeed | string | auto-generated (32 random hex bytes) | Seed value for the first entry in the HMAC chain. | | retentionDays | number | undefined | Maximum entry age in days. Used with startRetention(). | | maxFileSize | number | 52428800 (50 MiB) | Maximum file size in bytes before automatic rotation. | | defaultPiiFields | string[] | ['input', 'output'] | Field paths tagged as containing PII on every entry. | | redactPii | boolean | false | When true, automatically scan and redact PII patterns in input, output, and metadata before writing. | | piiPatterns | RegExp[] | undefined | Additional regex patterns to redact alongside the built-in PII patterns. | | autoRotate | boolean | true | When true, rotate the storage file when it exceeds maxFileSize. | | onError | (error: Error) => void | console.error | Callback invoked on internal errors (storage failures, etc.). |

RecordInput

| Field | Type | Required | Description | |-------|------|----------|-------------| | model | string | Yes | Model name as passed to the API (e.g., 'gpt-4o', 'claude-3-opus'). | | provider | Provider | Yes | One of 'openai', 'anthropic', 'google', 'azure-openai', 'aws-bedrock', 'custom'. | | input | unknown | Yes | The prompt or messages sent to the LLM. Can be a string, array, or object. | | output | unknown | Yes | The response received from the LLM. Can be a string or object. | | tokens | { input: number; output: number } | Yes | Token counts. total is computed automatically. | | latencyMs | number | Yes | Response latency in milliseconds. | | actor | string \| null | No | The user or service that triggered the call (e.g., 'user:alice'). Defaults to null. | | cost | number \| null | No | Estimated cost in USD. Defaults to null. | | toolCalls | Array<{ name: string; arguments: Record<string, unknown>; id?: string; result?: unknown }> | No | Tool/function calls made during the interaction. Defaults to null. | | error | { message: string; code?: string; statusCode?: number } | No | Error details if the call failed. Defaults to null. | | metadata | Record<string, unknown> | No | Arbitrary key-value metadata. Defaults to {}. | | piiFields | string[] | No | Additional PII field paths for this entry (merged with defaultPiiFields). |

QueryFilters

| Field | Type | Default | Description | |-------|------|---------|-------------| | startDate | Date | -- | Include entries at or after this date. | | endDate | Date | -- | Include entries at or before this date. | | actor | string | -- | Exact match on actor. | | model | string | -- | Exact match on model. | | tags | string[] | -- | All specified tags must be present in metadata.tags. | | limit | number | -- | Maximum number of entries to return. | | offset | number | -- | Number of entries to skip (for pagination). | | excludeTombstones | boolean | true | Exclude tombstoned (logically deleted) entries. |


Error Handling

llm-audit-log uses two error-handling mechanisms:

Thrown errors -- log() throws if the logger is closed or if a storage write fails. Callers should wrap log() calls in try/catch if uninterrupted operation is required.

try {
  await logger.log(input);
} catch (err) {
  // Handle storage failure, closed logger, etc.
}

Error callback -- Internal errors (e.g., storage I/O failures during log()) are passed to the onError callback before being re-thrown. This allows centralized error reporting without wrapping every call.

const logger = new AuditLogger({
  storagePath: './audit.jsonl',
  onError: (err) => {
    alerting.send(`Audit log error: ${err.message}`);
  },
});

Verification errors -- verify() does not throw. It returns a VerificationResult object with valid: false and an error string if verification itself fails (e.g., no HMAC secret configured), or with firstInvalidIndex pointing to the first broken entry if the chain is invalid.

Malformed data -- The JSONL reader silently skips malformed lines rather than failing the entire read operation. This prevents a single corrupted line from blocking access to the rest of the audit log.


Advanced Usage

Multi-Session HMAC Chain Continuity

The HMAC chain automatically resumes across process restarts. When a new AuditLogger is created against an existing log file with hmacSecret set, it reads all existing entries, recovers the last HMAC, and chains new entries from that point.

// Session 1
const logger1 = createAuditLog({ storagePath: './audit.jsonl', hmacSecret: 'secret' });
await logger1.log(input1);
await logger1.log(input2);
await logger1.close();

// Session 2 -- chain continues seamlessly
const logger2 = createAuditLog({ storagePath: './audit.jsonl', hmacSecret: 'secret' });
await logger2.log(input3);

const result = await logger2.verify();
console.log(result.valid);      // true
console.log(result.entryCount); // 3
await logger2.close();

Custom PII Patterns

Add domain-specific patterns alongside the built-in detectors:

const logger = createAuditLog({
  storagePath: './audit.jsonl',
  redactPii: true,
  piiPatterns: [
    /MRN-\d{5,}/g,           // Medical record numbers
    /ACCT-[A-Z0-9]{8,}/g,    // Account identifiers
    /\b[A-Z]{2}\d{6,9}\b/g,  // Passport numbers
  ],
});

Field-Level PII Redaction

Redact specific fields by dot-notation path, independent of pattern matching:

import { redactFields } from 'llm-audit-log';

redactFields(entry, {
  piiFields: ['input', 'metadata.customerEmail', 'metadata.ssn'],
  placeholder: '[SCRUBBED]',
});

Custom Storage Backend

Implement the StorageBackend interface to use a database, cloud storage, or any other persistence layer:

import type { StorageBackend, AuditEntry, QueryFilters, ExportFormat } from 'llm-audit-log';

class PostgresStorage implements StorageBackend {
  async init(): Promise<void> { /* create tables */ }
  async append(entry: AuditEntry): Promise<void> { /* INSERT */ }
  async read(): Promise<AuditEntry[]> { /* SELECT * */ }
  async query(filters: QueryFilters): Promise<AuditEntry[]> { /* SELECT WHERE */ }
  async purge(before: Date): Promise<number> { /* DELETE */ }
  async export(format: ExportFormat, filters?: QueryFilters): Promise<string> { /* format */ }
  async count(): Promise<number> { /* COUNT */ }
  async size(): Promise<number> { /* pg_total_relation_size */ }
  async close(): Promise<void> { /* pool.end() */ }
}

Standalone Integrity Verification

Verify a chain of entries without an AuditLogger instance:

import { verifyChain } from 'llm-audit-log';

const entries = JSON.parse(fs.readFileSync('./audit.jsonl', 'utf-8')
  .trim().split('\n').map(line => JSON.parse(line)));

const result = verifyChain(entries, process.env.AUDIT_HMAC_SECRET!);
if (!result.valid) {
  process.exit(1);
}

File Rotation Control

Disable automatic rotation for environments where a separate log rotation tool (logrotate, etc.) is used:

const logger = createAuditLog({
  storagePath: './audit.jsonl',
  autoRotate: false,
  maxFileSize: 100_000_000, // 100 MiB -- ignored when autoRotate is false
});

Filtered Export for Compliance

Export a specific actor's data for GDPR Subject Access Requests:

const subjectData = await logger.export('json', {
  actor: 'user:[email protected]',
});

fs.writeFileSync('./sar-jane-doe.json', subjectData);

Export entries for a specific time window and model for an internal audit:

const auditReport = await logger.export('csv', {
  startDate: new Date('2026-01-01'),
  endDate: new Date('2026-03-31'),
  model: 'gpt-4o',
});

TypeScript

llm-audit-log is written in TypeScript and ships type declarations (dist/index.d.ts) alongside the compiled JavaScript. All public types are exported from the package entry point.

import type {
  AuditEntry,
  AuditLogOptions,
  ExportFormat,
  PiiMatch,
  Provider,
  QueryFilters,
  RecordInput,
  RetentionPolicy,
  StorageBackend,
  VerificationResult,
} from 'llm-audit-log';

Type Summary

| Type | Description | |------|-------------| | AuditEntry | A complete audit log entry with all fields (id, timestamp, actor, model, provider, input, output, tokens, latency, cost, tool calls, error, metadata, PII fields, HMAC, tombstone). | | AuditLogOptions | Configuration for createAuditLog() and the AuditLogger constructor. | | RecordInput | Partial entry accepted by log(). Required fields: model, provider, input, output, tokens, latencyMs. | | QueryFilters | Filtering options for query() and export(). | | VerificationResult | Result of HMAC chain verification including validity, entry count, break location, and timing. | | PiiMatch | A single PII detection result with type, value, start, and end offsets. | | Provider | Union type: 'openai' \| 'anthropic' \| 'google' \| 'azure-openai' \| 'aws-bedrock' \| 'custom'. | | ExportFormat | Union type: 'json' \| 'csv' \| 'jsonl'. | | StorageBackend | Interface for custom storage implementations. | | RetentionPolicy | Configuration for the retention manager: maxAge, checkIntervalMs, archiveBeforePurge, archiveDir. |


License

MIT