From 2551ecd35ad6d7a77dc7d240c6a978b2d4630b39 Mon Sep 17 00:00:00 2001 From: Sebastian Otaegui Date: Tue, 21 Apr 2026 16:37:06 -0300 Subject: [PATCH 01/10] feat: add AuditIdentity type for macOS code signing tokens Add AuditIdentity interface (signerType, signingId, teamId, cdhash) and identity field to AuditEvent to support AUT_IDENTITY (0xed) tokens. --- src/schema.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/schema.ts b/src/schema.ts index f39cb09..8144347 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -22,6 +22,8 @@ export interface AuditEvent { attributes: AuditAttribute[]; /** Argument tokens (syscall arguments). */ arguments: AuditArgument[]; + /** Code signing identity from AUT_IDENTITY token (macOS). */ + identity: AuditIdentity | null; } export interface AuditSubject { @@ -65,6 +67,17 @@ export interface AuditArgument { desc: string; } +export interface AuditIdentity { + /** Signer type (e.g., 0=unsigned, 3=Apple system). */ + signerType: number; + /** Code signing identifier (e.g., "com.apple.ls"). */ + signingId: string; + /** Team identifier (e.g., "ABCDE12345"). */ + teamId: string; + /** Code directory hash, hex-encoded. */ + cdhash: string; +} + /** Writer interface for pluggable output sinks. */ export interface Writer { write(event: AuditEvent): Promise; From 0e8eed1da1cbd3e0267c4f998c7674aaece3b55f Mon Sep 17 00:00:00 2001 From: Sebastian Otaegui Date: Tue, 21 Apr 2026 16:37:21 -0300 Subject: [PATCH 02/10] fix: handle AUT_DATA, AUT_IN_ADDR, AUT_IDENTITY BSM tokens These three token types appear in virtually every macOS audit record but were unhandled, causing the parser to skip entire records and produce zero JSON output. - AUT_DATA (0x21): skip variable-length arbitrary data - AUT_IN_ADDR (0x2a): skip 4-byte IPv4 address - AUT_IDENTITY (0xed): parse code signing identity into event Closes EVP-45 --- src/bsm-parser.ts | 54 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/bsm-parser.ts b/src/bsm-parser.ts index b9144ef..deff17b 100644 --- a/src/bsm-parser.ts +++ b/src/bsm-parser.ts @@ -7,7 +7,7 @@ * Reference: openbsm/openbsm bsm_token.h, bsm_io.c */ -import type { AuditArgument, AuditAttribute, AuditEvent, AuditReturn, AuditSubject } from "./schema.ts"; +import type { AuditArgument, AuditAttribute, AuditEvent, AuditIdentity, AuditReturn, AuditSubject } from "./schema.ts"; // --- Token type IDs --- @@ -27,8 +27,11 @@ const AUT_TEXT = 0x28; const AUT_EXEC_ARGS = 0x3c; const AUT_ARG32 = 0x2d; const AUT_ARG64 = 0x71; +const AUT_DATA = 0x21; +const AUT_IN_ADDR = 0x2a; const AUT_ATTR32 = 0x3e; const AUT_ATTR64 = 0x73; +const AUT_IDENTITY = 0xed; const TRAILER_MAGIC = 0xb105; const textDecoder = new TextDecoder(); @@ -131,6 +134,7 @@ export function parseRecord(buf: Uint8Array): AuditEvent { text: [], attributes: [], arguments: [], + identity: null, }; // Record size is always at byte 1 (right after the 1-byte token ID) for all header types @@ -178,6 +182,12 @@ export function parseRecord(buf: Uint8Array): AuditEvent { case AUT_TEXT: offset = parseText(view, offset, buf, event); break; + case AUT_DATA: + offset = parseData(view, offset); + break; + case AUT_IN_ADDR: + offset = offset + 4; + break; case AUT_EXEC_ARGS: offset = parseExecArgs(view, offset, buf, event); break; @@ -193,6 +203,9 @@ export function parseRecord(buf: Uint8Array): AuditEvent { case AUT_ATTR64: offset = parseAttr64(view, offset, event); break; + case AUT_IDENTITY: + offset = parseIdentity(view, offset, buf, event); + break; case AUT_TRAILER: offset = parseTrailer(view, offset, recordSize); break; @@ -379,6 +392,18 @@ function parseExecArgs(view: DataView, off: number, buf: Uint8Array, event: Audi return pos; } +// --- Data token parser --- + +const DATA_UNIT_SIZES = [1, 2, 4, 8] as const; + +function parseData(view: DataView, off: number): number { + // how_to_print(1) + basic_unit(1) + unit_count(1) + data(variable) + const basicUnit = view.getUint8(off + 1); + const unitCount = view.getUint8(off + 2); + const unitSize = DATA_UNIT_SIZES[basicUnit] ?? 1; + return off + 3 + unitCount * unitSize; +} + // --- Argument parsers --- function parseArg32(view: DataView, off: number, buf: Uint8Array, event: AuditEvent): number { @@ -430,6 +455,33 @@ function readAttrFields(view: DataView, off: number, devSize: number): AuditAttr }; } +// --- Identity parser (Apple AUT_IDENTITY 0xed) --- + +function parseIdentity(view: DataView, off: number, buf: Uint8Array, event: AuditEvent): number { + // signer_type(4) + signing_id_len(2) + signing_id(n) + truncated(1) + + // team_id_len(2) + team_id(n) + truncated(1) + cdhash_len(2) + cdhash(n) + const signerType = view.getUint32(off, false); + const signingIdLen = view.getUint16(off + 4, false); + if (off + 6 + signingIdLen > buf.byteLength) return buf.byteLength; + const signingId = decodeString(buf, off + 6, signingIdLen); + let pos = off + 6 + signingIdLen + 1; // +1 for truncated flag + + const teamIdLen = view.getUint16(pos, false); + if (pos + 2 + teamIdLen > buf.byteLength) return buf.byteLength; + const teamId = decodeString(buf, pos + 2, teamIdLen); + pos = pos + 2 + teamIdLen + 1; // +1 for truncated flag + + const cdhashLen = view.getUint16(pos, false); + if (pos + 2 + cdhashLen > buf.byteLength) return buf.byteLength; + const cdhashBytes = buf.subarray(pos + 2, pos + 2 + cdhashLen); + const cdhash = Array.from(cdhashBytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + + event.identity = { signerType, signingId, teamId, cdhash }; + return pos + 2 + cdhashLen; +} + // --- Trailer --- function parseTrailer(view: DataView, off: number, expectedSize: number): number { From 06b0e59d2f8644dadae9bab8e0c13a38ffa37335 Mon Sep 17 00:00:00 2001 From: Sebastian Otaegui Date: Tue, 21 Apr 2026 16:37:37 -0300 Subject: [PATCH 03/10] test: add coverage for AUT_DATA, AUT_IN_ADDR, AUT_IDENTITY tokens 8 new tests covering all three previously-unhandled token types: - AUT_DATA with byte, short, int32, and int64 unit sizes - AUT_IN_ADDR IPv4 address token - AUT_IDENTITY with populated and empty fields - Realistic macOS execve record combining all new tokens --- src/bsm-parser.test.ts | 192 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) diff --git a/src/bsm-parser.test.ts b/src/bsm-parser.test.ts index 4c74a0e..f435ac9 100644 --- a/src/bsm-parser.test.ts +++ b/src/bsm-parser.test.ts @@ -325,6 +325,59 @@ function arg64Token(argNum: number, value: number, desc: string): Uint8Array { return concat(bsm(0x71, argNum, ["u64", value]), bsmString(desc)); } +/** + * Build an AUT_DATA token (0x21). + * Format: token_id(1) + how_to_print(1) + basic_unit(1) + unit_count(1) + data(variable) + * Unit sizes: 0=byte(1), 1=short(2), 2=int32(4), 3=int64(8) + */ +function dataToken(howToPrint: number, basicUnit: number, unitCount: number, data: Uint8Array): Uint8Array { + return concat(bsm(0x21, howToPrint, basicUnit, unitCount), data); +} + +/** Build an AUT_IN_ADDR token (0x2a). Format: token_id(1) + ipv4_addr(4) */ +function inAddrToken(a: number, b: number, c: number, d: number): Uint8Array { + return bsm(0x2a, a, b, c, d); +} + +/** + * Build an AUT_IDENTITY token (0xed). + * Format: token_id(1) + signer_type(4) + signing_id_len(2) + signing_id(n) + + * signing_id_truncated(1) + team_id_len(2) + team_id(n) + + * team_id_truncated(1) + cdhash_len(2) + cdhash(n) + */ +function identityToken(opts: { + signerType?: number; + signingId?: string; + teamId?: string; + cdhash?: Uint8Array; +}): Uint8Array { + const signerType = opts.signerType ?? 0; + const signingId = opts.signingId ?? ""; + const teamId = opts.teamId ?? ""; + const cdhash = opts.cdhash ?? new Uint8Array(20); + + const signingIdEncoded = new TextEncoder().encode(signingId); + const signingIdLen = signingIdEncoded.byteLength + 1; // include NUL + const signingIdBuf = new Uint8Array(signingIdLen); + signingIdBuf.set(signingIdEncoded); + + const teamIdEncoded = new TextEncoder().encode(teamId); + const teamIdLen = teamIdEncoded.byteLength + 1; + const teamIdBuf = new Uint8Array(teamIdLen); + teamIdBuf.set(teamIdEncoded); + + return concat( + bsm(0xed, ["u32", signerType], ["u16", signingIdLen]), + signingIdBuf, + bsm(0), // signing_id_truncated + bsm(["u16", teamIdLen]), + teamIdBuf, + bsm(0), // team_id_truncated + bsm(["u16", cdhash.byteLength]), + cdhash, + ); +} + /** Build a record with header64 (0x74). */ function buildRecord64(eventType: number, ...tokens: Uint8Array[]): Uint8Array { // Header64: 1(id) + 4(size) + 1(ver) + 2(event) + 2(mod) + 8(sec) + 8(usec) = 26 @@ -739,6 +792,145 @@ describe("parseRecord", () => { expect(event.paths).toEqual([""]); }); + // --- AUT_DATA (0x21) --- + + test("parses record containing AUT_DATA with byte units", () => { + const data = new Uint8Array([0xde, 0xad, 0xbe, 0xef]); + const record = buildRecord( + 180, + subjectToken({ pid: 1234 }), + dataToken(3, 0, 4, data), // hex print, byte unit, 4 bytes + returnToken(0, 0), + ); + const event = parseRecord(record); + expect(event.event).toBe("execve(2)"); + expect(event.subject!.pid).toBe("1234"); + expect(event.return!.errval).toBe("success"); + }); + + test("parses record containing AUT_DATA with int32 units", () => { + const data = bsm(["u32", 42], ["u32", 99]); + const record = buildRecord( + 180, + subjectToken({ pid: 5678 }), + dataToken(2, 2, 2, data), // decimal print, int32 unit, 2 items + pathToken("/usr/bin/ls"), + returnToken(0, 0), + ); + const event = parseRecord(record); + expect(event.paths).toEqual(["/usr/bin/ls"]); + expect(event.return!.errval).toBe("success"); + }); + + test("parses record containing AUT_DATA with short units", () => { + const data = bsm(["u16", 1], ["u16", 2], ["u16", 3]); + const record = buildRecord( + 180, + dataToken(2, 1, 3, data), // decimal print, short unit, 3 items + returnToken(0, 0), + ); + const event = parseRecord(record); + expect(event.return!.errval).toBe("success"); + }); + + test("parses record containing AUT_DATA with int64 units", () => { + const data = bsm(["u64", 0xdeadbeef]); + const record = buildRecord( + 180, + dataToken(3, 3, 1, data), // hex print, int64 unit, 1 item + returnToken(0, 42), + ); + const event = parseRecord(record); + expect(event.return!.retval).toBe("42"); + }); + + // --- AUT_IN_ADDR (0x2a) --- + + test("parses record containing AUT_IN_ADDR", () => { + const record = buildRecord( + 180, + subjectToken({ pid: 1234 }), + inAddrToken(10, 0, 1, 5), + returnToken(0, 0), + ); + const event = parseRecord(record); + expect(event.subject!.pid).toBe("1234"); + expect(event.return!.errval).toBe("success"); + }); + + // --- AUT_IDENTITY (0xed) --- + + test("parses record containing AUT_IDENTITY", () => { + const cdhash = new Uint8Array([ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + ]); + const record = buildRecord( + 180, + subjectToken({ pid: 1234 }), + identityToken({ + signerType: 3, + signingId: "com.apple.ls", + teamId: "ABCDE12345", + cdhash, + }), + returnToken(0, 0), + ); + const event = parseRecord(record); + expect(event.event).toBe("execve(2)"); + expect(event.subject!.pid).toBe("1234"); + expect(event.identity).not.toBeNull(); + expect(event.identity!.signerType).toBe(3); + expect(event.identity!.signingId).toBe("com.apple.ls"); + expect(event.identity!.teamId).toBe("ABCDE12345"); + expect(event.identity!.cdhash).toBe("0102030405060708090a0b0c0d0e0f1011121314"); + expect(event.return!.errval).toBe("success"); + }); + + test("parses AUT_IDENTITY with empty signing ID and team ID", () => { + const cdhash = new Uint8Array(20); // all zeros + const record = buildRecord( + 180, + identityToken({ signerType: 0, signingId: "", teamId: "", cdhash }), + returnToken(0, 0), + ); + const event = parseRecord(record); + expect(event.identity).not.toBeNull(); + expect(event.identity!.signerType).toBe(0); + expect(event.identity!.signingId).toBe(""); + expect(event.identity!.teamId).toBe(""); + }); + + // --- Realistic macOS execve record with all three new tokens --- + + test("parses realistic macOS execve record with data, identity, and other tokens", () => { + const cdhash = new Uint8Array(20).fill(0xab); + const record = buildRecord( + 180, // execve(2) + subjectToken({ pid: 42, uid: 501 }), + execArgsToken("/usr/bin/ls", "-la"), + dataToken(3, 0, 2, new Uint8Array([0x01, 0x02])), // AUT_DATA + pathToken("/usr/bin/ls"), + attrToken(0o100755, 0, 0, 16777220, 12345678, 16777220), + identityToken({ + signerType: 3, + signingId: "com.apple.ls", + teamId: "", + cdhash, + }), + returnToken(0, 0), + ); + const event = parseRecord(record); + expect(event.event).toBe("execve(2)"); + expect(event.subject!.pid).toBe("42"); + expect(event.execArgs).toEqual(["/usr/bin/ls", "-la"]); + expect(event.paths).toEqual(["/usr/bin/ls"]); + expect(event.attributes).toHaveLength(1); + expect(event.identity).not.toBeNull(); + expect(event.identity!.signingId).toBe("com.apple.ls"); + expect(event.return!.errval).toBe("success"); + }); + test("survives exec_args with count exceeding buffer", () => { // Exec args token claiming 100 args but only containing 1 const header = bsm(0x14, ["u32", 40], 11, ["u16", 180], ["u16", 0], ["u32", 1776000000], ["u32", 123000]); From 9dfe275dee86ba82e9c021c6b1588e9c00f6ff43 Mon Sep 17 00:00:00 2001 From: Sebastian Otaegui Date: Tue, 21 Apr 2026 16:43:28 -0300 Subject: [PATCH 04/10] fix: address PR #1 review feedback (EVP-45) - src/bsm-parser.ts: add bounds check to parseData for malformed tokens - src/bsm-parser.ts: add comment explaining basicUnit fallback behavior - src/bsm-parser.ts: add bounds checks before getUint16 calls in parseIdentity - src/bsm-parser.test.ts: fix Biome formatting --- src/bsm-parser.test.ts | 11 +++-------- src/bsm-parser.ts | 16 +++++++++++----- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/bsm-parser.test.ts b/src/bsm-parser.test.ts index f435ac9..43b300e 100644 --- a/src/bsm-parser.test.ts +++ b/src/bsm-parser.test.ts @@ -847,12 +847,7 @@ describe("parseRecord", () => { // --- AUT_IN_ADDR (0x2a) --- test("parses record containing AUT_IN_ADDR", () => { - const record = buildRecord( - 180, - subjectToken({ pid: 1234 }), - inAddrToken(10, 0, 1, 5), - returnToken(0, 0), - ); + const record = buildRecord(180, subjectToken({ pid: 1234 }), inAddrToken(10, 0, 1, 5), returnToken(0, 0)); const event = parseRecord(record); expect(event.subject!.pid).toBe("1234"); expect(event.return!.errval).toBe("success"); @@ -862,8 +857,8 @@ describe("parseRecord", () => { test("parses record containing AUT_IDENTITY", () => { const cdhash = new Uint8Array([ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, - 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, + 0x14, ]); const record = buildRecord( 180, diff --git a/src/bsm-parser.ts b/src/bsm-parser.ts index deff17b..c4dd839 100644 --- a/src/bsm-parser.ts +++ b/src/bsm-parser.ts @@ -183,7 +183,7 @@ export function parseRecord(buf: Uint8Array): AuditEvent { offset = parseText(view, offset, buf, event); break; case AUT_DATA: - offset = parseData(view, offset); + offset = parseData(view, offset, buf); break; case AUT_IN_ADDR: offset = offset + 4; @@ -396,12 +396,15 @@ function parseExecArgs(view: DataView, off: number, buf: Uint8Array, event: Audi const DATA_UNIT_SIZES = [1, 2, 4, 8] as const; -function parseData(view: DataView, off: number): number { +function parseData(view: DataView, off: number, buf: Uint8Array): number { // how_to_print(1) + basic_unit(1) + unit_count(1) + data(variable) const basicUnit = view.getUint8(off + 1); const unitCount = view.getUint8(off + 2); + // Fallback to 1-byte units for unrecognized basicUnit values (defensive) const unitSize = DATA_UNIT_SIZES[basicUnit] ?? 1; - return off + 3 + unitCount * unitSize; + const end = off + 3 + unitCount * unitSize; + if (end > buf.byteLength) return buf.byteLength; + return end; } // --- Argument parsers --- @@ -460,17 +463,20 @@ function readAttrFields(view: DataView, off: number, devSize: number): AuditAttr function parseIdentity(view: DataView, off: number, buf: Uint8Array, event: AuditEvent): number { // signer_type(4) + signing_id_len(2) + signing_id(n) + truncated(1) + // team_id_len(2) + team_id(n) + truncated(1) + cdhash_len(2) + cdhash(n) + if (off + 6 > buf.byteLength) return buf.byteLength; const signerType = view.getUint32(off, false); const signingIdLen = view.getUint16(off + 4, false); - if (off + 6 + signingIdLen > buf.byteLength) return buf.byteLength; + if (off + 6 + signingIdLen + 1 > buf.byteLength) return buf.byteLength; const signingId = decodeString(buf, off + 6, signingIdLen); let pos = off + 6 + signingIdLen + 1; // +1 for truncated flag + if (pos + 2 > buf.byteLength) return buf.byteLength; const teamIdLen = view.getUint16(pos, false); - if (pos + 2 + teamIdLen > buf.byteLength) return buf.byteLength; + if (pos + 2 + teamIdLen + 1 > buf.byteLength) return buf.byteLength; const teamId = decodeString(buf, pos + 2, teamIdLen); pos = pos + 2 + teamIdLen + 1; // +1 for truncated flag + if (pos + 2 > buf.byteLength) return buf.byteLength; const cdhashLen = view.getUint16(pos, false); if (pos + 2 + cdhashLen > buf.byteLength) return buf.byteLength; const cdhashBytes = buf.subarray(pos + 2, pos + 2 + cdhashLen); From 25ad7fa7e45a2590d9bd5f4e48175b6c33eda8b7 Mon Sep 17 00:00:00 2001 From: Sebastian Otaegui Date: Tue, 21 Apr 2026 16:45:15 -0300 Subject: [PATCH 05/10] style: document OpenBSM AUR_* constants in DATA_UNIT_SIZES --- src/bsm-parser.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bsm-parser.ts b/src/bsm-parser.ts index c4dd839..2b62dbd 100644 --- a/src/bsm-parser.ts +++ b/src/bsm-parser.ts @@ -394,6 +394,7 @@ function parseExecArgs(view: DataView, off: number, buf: Uint8Array, event: Audi // --- Data token parser --- +// Byte sizes indexed by OpenBSM basic_unit: AUR_BYTE=0(1), AUR_SHORT=1(2), AUR_INT32=2(4), AUR_INT64=3(8) const DATA_UNIT_SIZES = [1, 2, 4, 8] as const; function parseData(view: DataView, off: number, buf: Uint8Array): number { From 307245b4acc20d08141aa2f6b5758951b3b4429c Mon Sep 17 00:00:00 2001 From: Sebastian Otaegui Date: Tue, 21 Apr 2026 16:46:22 -0300 Subject: [PATCH 06/10] fix: add missing identity field to writer test fixture --- src/writer.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/writer.test.ts b/src/writer.test.ts index b91775e..f7b4741 100644 --- a/src/writer.test.ts +++ b/src/writer.test.ts @@ -18,6 +18,7 @@ function makeEvent(overrides: Partial = {}): AuditEvent { text: [], attributes: [], arguments: [], + identity: null, ...overrides, }; } From ae41f227c3891747647c4988e0d99a79ce687a03 Mon Sep 17 00:00:00 2001 From: Sebastian Otaegui Date: Tue, 21 Apr 2026 16:48:23 -0300 Subject: [PATCH 07/10] docs: add CLAUDE.md with commands, architecture, and gotchas --- CLAUDE.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3ab9991 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,43 @@ +# bsmtap + +BSM-to-JSON streaming daemon — taps macOS OpenBSM audit events from `/dev/auditpipe` into structured JSON Lines. + +## Commands + +- `bun test` — run all tests +- `bun test --coverage` — run tests with coverage +- `bun run typecheck` — typecheck (tsc --noEmit) +- `bun run lint:fix` — auto-fix lint + formatting +- `bun run format` — auto-fix formatting only +- `bun run lint:ci` — lint check as CI runs it + +### Pre-push verification + +Run all three CI checks before pushing: + +```sh +bun run lint:ci && bun run typecheck && bun test +``` + +## Code style + +Enforced by Biome — do not override manually: + +- Double quotes, trailing commas, 2-space indent, 120-char line width +- Imports are auto-organized by Biome + +## Architecture + +All source is in `src/`: + +- `schema.ts` — `AuditEvent` and related types (the shared contract) +- `bsm-parser.ts` — binary token parser: each token type has a `parse*` function that takes `(view, offset, [buf], event)` and returns the new offset. All multi-byte reads are big-endian. +- `reader.ts` — `AuditPipeReader`: reads raw bytes from `/dev/auditpipe`, uses `extractRecords()` to frame and parse +- `writer.ts` — `JsonLineFileWriter`: writes `AuditEvent` objects as JSON Lines with SIGHUP-based file rotation +- `main.ts` — wires reader → writer + +## Gotchas + +- Adding a field to `AuditEvent` in `schema.ts` requires updating the initializer in `bsm-parser.ts:parseRecord` AND `makeEvent()` in `writer.test.ts` — typecheck catches this +- Token parser tests use binary builder helpers (`bsm()`, `buildRecord()`, token-specific builders) at the top of `bsm-parser.test.ts` +- CI runs on Ubuntu but the daemon targets macOS — reader tests use temp FIFOs to simulate `/dev/auditpipe` From f6185cb19eb07b5c7a9f84f42f6c8366a12ce1c6 Mon Sep 17 00:00:00 2001 From: Sebastian Otaegui Date: Tue, 21 Apr 2026 16:49:15 -0300 Subject: [PATCH 08/10] docs: match pre-push command to CI (bun test --coverage) --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3ab9991..4555548 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,7 +16,7 @@ BSM-to-JSON streaming daemon — taps macOS OpenBSM audit events from `/dev/audi Run all three CI checks before pushing: ```sh -bun run lint:ci && bun run typecheck && bun test +bun run lint:ci && bun run typecheck && bun test --coverage ``` ## Code style From 4015b27c87221e2635178201d2e3006dbf6eb4a3 Mon Sep 17 00:00:00 2001 From: Sebastian Otaegui Date: Tue, 21 Apr 2026 16:50:58 -0300 Subject: [PATCH 09/10] ci: sync CI and release workflow test gates - Pin bun-version to 1.3.11 in both workflows - Run tests with --coverage in release workflow to match CI --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 427deef..2d2f300 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: "1.3" + bun-version: "1.3.11" - name: Install dependencies run: bun install --frozen-lockfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 24250d7..75bfd4a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,8 +29,8 @@ jobs: - name: Typecheck run: bun run typecheck - - name: Run tests - run: bun test + - name: Run tests with coverage + run: bun test --coverage build: name: Build From e29d8c843bb886fa4be1c71f71c3cf83a54a1f35 Mon Sep 17 00:00:00 2001 From: Sebastian Otaegui Date: Tue, 21 Apr 2026 16:59:17 -0300 Subject: [PATCH 10/10] chore: bump version to 1.0.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index acff0a5..a157cb0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bsmtap", - "version": "1.0.2", + "version": "1.0.3", "description": "BSM-to-JSON streaming daemon — taps macOS OpenBSM audit events into structured JSON Lines", "type": "module", "license": "MIT",