ktg-plugin-marketplace/plugins/llm-security/scanners/lib/audit-trail.mjs
Kjell Tore Guttormsen 269c14445c feat(governance): add structured JSONL audit trail with SIEM-ready schema
New audit-trail.mjs writes structured events to LLM_SECURITY_AUDIT_LOG path.
Integrated into post-session-guard at 6 warning emission points: trifecta,
escalation-after-input, data flow, volume threshold, slow-burn, behavioral drift.
No-op when env var not set — zero overhead for existing users.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 13:25:59 +02:00

83 lines
2.5 KiB
JavaScript

// 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;
}