// audit-trail.mjs — Structured JSONL audit trail writer // Writes SIEM-ready events to the path specified by LLM_SECURITY_AUDIT_LOG. // No-op when env var is not set. Zero external dependencies. import { appendFileSync, writeFileSync, accessSync, constants } from 'node:fs'; import { dirname } from 'node:path'; let auditPath = null; let initialized = false; /** * Initialize audit trail. Validates the path is writable on first call. * @returns {boolean} true if audit trail is enabled and writable */ function initAuditTrail() { if (initialized) return auditPath !== null; initialized = true; const envPath = process.env.LLM_SECURITY_AUDIT_LOG; if (!envPath) return false; try { // Ensure parent directory exists and is writable const dir = dirname(envPath); accessSync(dir, constants.W_OK); // Touch file if it doesn't exist try { accessSync(envPath); } catch { writeFileSync(envPath, ''); } auditPath = envPath; return true; } catch (err) { process.stderr.write(`[llm-security] Audit trail path not writable: ${envPath} (${err.message})\n`); return false; } } /** * Write a structured audit event as one JSON line. * No-op when LLM_SECURITY_AUDIT_LOG is not set. * * @param {object} event * @param {string} event.event_type - e.g. trifecta_warning, injection_detected * @param {string} event.severity - critical|high|medium|low|info * @param {string} event.source - hook or scanner name * @param {object} [event.details] - event-specific payload * @param {string[]} [event.owasp] - OWASP categories * @param {string} [event.action_taken] - blocked|warned|allowed */ export function writeAuditEvent(event) { if (!initAuditTrail()) return; const entry = { timestamp: new Date().toISOString(), session_id: String(process.ppid || process.pid), event_type: event.event_type || 'unknown', severity: event.severity || 'info', source: event.source || 'unknown', details: event.details || {}, owasp: event.owasp || [], action_taken: event.action_taken || 'warned', }; try { appendFileSync(auditPath, JSON.stringify(entry) + '\n'); } catch (err) { process.stderr.write(`[llm-security] Audit trail write failed: ${err.message}\n`); } } /** * Check whether audit trail is enabled (for guard clauses in hooks). * @returns {boolean} */ export function isAuditEnabled() { return initAuditTrail(); } /** * Reset internal state (for testing only). */ export function _resetForTest() { auditPath = null; initialized = false; }