Structured, tamper-evident-friendly audit events for healthcare systems.
The included
InMemorySinkis 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.
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.
- 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
AuditSinkinterface for organization-owned persistence. - Zero runtime dependencies.
- Node.js 18 or newer, native ESM, and generated TypeScript declarations.
npm install @peerbits/audit-loggerimport {
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)); // falseCreate 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.
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.
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);
}
}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",
);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.
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.
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.
- 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.
npm ci
npm run check
npm pack --dry-runnpm 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.
This project is informed by:
- HHS HIPAA Audit Protocol — §164.312(b)
- NIST SP 800-66 Rev. 2, especially §5.3.2
- Node.js
crypto.createHash
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.