Skip to content

Repository files navigation

@peerbits/audit-logger

Structured, tamper-evident-friendly audit events for healthcare systems.

The included InMemorySink is for demos and tests only. It is not a production audit store. It is volatile and provides no durability, access control, encryption, retention, or immutability. Read the Production Sink Guide before deployment.

1. What problem does this solve?

Healthcare applications need to record who interacted with sensitive resources, what they did, when it happened, and whether it succeeded. Ordinary debug logs rarely provide a consistent or reviewable audit trail.

@peerbits/audit-logger provides a strict event shape, safe builders, structural validation, a storage interface, and SHA-256 hash chaining. It supports the audit controls standard in 45 CFR §164.312(b). It does not make an application, organization, or workflow HIPAA compliant by itself. Compliance also requires appropriate policies, risk analysis, access controls, review procedures, infrastructure, and organizational safeguards.

2. Features

  • Strict TypeScript types for actors, actions, resources, outcomes, and context.
  • Builders for access, export, modification, and authentication-failure events.
  • Namespaced custom actions such as custom:prior-auth-submit.
  • Runtime validation with precise error codes and paths.
  • A deliberately constrained context model with no arbitrary free-text field.
  • Deterministic SHA-256 hash chaining and tamper verification.
  • A pluggable AuditSink interface for organization-owned persistence.
  • Zero runtime dependencies.
  • Node.js 18 or newer, native ESM, and generated TypeScript declarations.

3. Installation

npm install @peerbits/audit-logger

4. Quick start

import {
  InMemorySink,
  opaqueIdentifier,
  recordAccess,
  resourceType,
  verifyChain,
} from "@peerbits/audit-logger";

const sink = new InMemorySink(); // demos/tests only

const event = recordAccess(
  opaqueIdentifier("actor-01HZX9"),
  {
    type: resourceType("Observation"),
    id: opaqueIdentifier("resource-01JAA4"),
  },
  "success",
);

await sink.append(event);

const events = await sink.query();
console.log(verifyChain(events)); // true

// Demonstration only: alteration invalidates the chain.
const altered = events.map((item) => ({ ...item }));
altered[0] = { ...altered[0], outcome: "failure" };
console.log(verifyChain(altered)); // false

5. Core concepts and API

PHI-minimizing identifiers

Create actors and resource IDs with opaqueIdentifier(). Values accept only a restricted identifier alphabet and are branded in TypeScript, making arbitrary text an awkward fit.

const actor = opaqueIdentifier("workforce-7f31");
const resource = {
  type: resourceType("DocumentReference"),
  id: opaqueIdentifier("document-b42e"),
};

Do not use patient names, email addresses, medical record numbers, diagnoses, clinical notes, or other PHI as identifiers. Syntax checks cannot determine the real-world meaning of a value; callers remain responsible for de-identification.

Builders

recordAccess(actor, resource, "success");
recordExport(actor, resource, "success");

recordModification(
  actor,
  resource,
  {
    changedFields: [
      fieldIdentifier("status"),
      fieldIdentifier("component[0].code"),
    ],
  },
  "success",
);

recordAuthFailure(actor, {
  sessionId: opaqueIdentifier("session-c91a"),
  source: opaqueIdentifier("gateway-2"),
});

Modification events record field names only—never previous or new values. Authentication failures always use the fixed authentication-failed reason.

Validation

validateEvent(value) returns a valid AuditEvent or throws an AuditValidationError. The error contains code and path fields suitable for programmatic handling. Unknown properties are rejected to reduce accidental PHI.

try {
  const event = validateEvent(untrustedValue);
  await sink.append(event);
} catch (error) {
  if (error instanceof AuditValidationError) {
    console.error(error.code, error.path);
  }
}

Custom actions

The built-ins are read, create, update, delete, export, and print. Extensions must be lowercase and namespaced with custom::

const event = recordCustomAction(
  actor,
  "custom:prior-auth-submit",
  resource,
  "success",
);

Querying

QueryFilters supports actor, action, resource type/id, outcome, inclusive UTC time bounds, and a positive result limit. Filtering may omit intermediate chain entries, so verify the complete ordered chain before using a filtered subset as evidence.

6. Hash-chain behavior and limits

Each stored entry includes previousHash and hash. The SHA-256 hash covers a stable, explicitly ordered serialization of every event field plus the previous hash. inspectChain() identifies the first invalid index and reason; verifyChain() returns a boolean.

Hash chaining is tamper evidence, not tamper prevention. An attacker who can rewrite every event can recompute the chain. A production design must protect the latest trusted hash/checkpoint separately and enforce append-only storage, access control, durability, monitoring, and backups. SHA-256 also authenticates no writer; use organization-appropriate signing or keyed integrity controls when writer authenticity is required.

The canonical encoding is a compatibility contract. Changing it requires a new schema version and migration plan. The precise byte format and a fixed test vector are documented in Hash Chain Format.

7. Production integration

Implement the AuditSink interface with your approved storage platform:

interface AuditSink {
  append(event: AuditEvent): Promise<void>;
  query(filters?: QueryFilters): Promise<ChainedAuditEvent[]>;
}

Validate before persistence, serialize concurrent appends, preserve total order, store the chain fields atomically, and return defensive copies in chronological chain order. See the Production Sink Guide for the full contract and operational checklist.

8. Security and privacy model

  • Events contain opaque references, not resource payloads.
  • Context supports controlled reason codes and operational identifiers only.
  • There is no generic message, details, metadata, or free-text field.
  • Runtime validation rejects unknown fields and non-canonical timestamps.
  • The package performs no network or file-system I/O.
  • The package does not authorize actions, detect anomalies, or review logs.

Security issues should follow SECURITY.md. Never include PHI, credentials, or client-identifying information in an issue or reproduction.

9. Development and release

npm ci
npm run check
npm pack --dry-run

npm run check runs linting, strict type checking, tests with 100% coverage thresholds, and the production build. Releases are public under the exact package name @peerbits/audit-logger. See CONTRIBUTING.md and CHANGELOG.md.

10. Standards, scope, and Peerbits

This project is informed by:

Out of scope: production persistence drivers, authorization, access control, alerting, anomaly detection, compliance scoring, and storage of PHI in events.

audit-logger is part of the Peerbits HealthTech Open Source initiative. It contains generalized reusable logic and no client-specific implementation.

About

Structured, tamper-evident-friendly audit events for healthcare systems.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages