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>
This commit is contained in:
parent
2116e702df
commit
269c14445c
3 changed files with 241 additions and 0 deletions
|
|
@ -42,6 +42,7 @@ import { tmpdir } from 'node:os';
|
|||
import { createHash } from 'node:crypto';
|
||||
import { extractMcpServer } from '../../scanners/lib/mcp-description-cache.mjs';
|
||||
import { jensenShannonDivergence, buildDistribution } from '../../scanners/lib/distribution-stats.mjs';
|
||||
import { writeAuditEvent } from '../../scanners/lib/audit-trail.mjs';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
|
|
@ -799,6 +800,14 @@ if (!(classes.length === 1 && (classes[0] === 'neutral' || classes[0] === 'deleg
|
|||
const sensitiveExfil = checkSensitiveExfil(window);
|
||||
messages.push(formatWarning(evidence, mcpInfo, sensitiveExfil));
|
||||
appendEntry(stateFile, { type: 'warning', ts: Date.now() });
|
||||
writeAuditEvent({
|
||||
event_type: 'trifecta_warning',
|
||||
severity: mcpInfo.concentrated || sensitiveExfil ? 'critical' : 'high',
|
||||
source: 'post-session-guard',
|
||||
details: { evidence, mcp_concentrated: mcpInfo.concentrated, sensitive_exfil: sensitiveExfil },
|
||||
owasp: ['ASI01', 'ASI02', 'LLM01'],
|
||||
action_taken: TRIFECTA_MODE === 'block' && (mcpInfo.concentrated || sensitiveExfil) ? 'blocked' : 'warned',
|
||||
});
|
||||
|
||||
// --- Rule of Two: Block mode ---
|
||||
// Block for high-confidence trifecta: MCP-concentrated OR sensitive path + exfil
|
||||
|
|
@ -823,6 +832,14 @@ if (classes.includes('delegation')) {
|
|||
if (escalation.detected && !hasEscalationWarning(window)) {
|
||||
messages.push(formatEscalationWarning(detail, escalation.inputDetail));
|
||||
appendEntry(stateFile, { type: 'escalation_warning', ts: Date.now() });
|
||||
writeAuditEvent({
|
||||
event_type: 'escalation_after_input',
|
||||
severity: 'medium',
|
||||
source: 'post-session-guard',
|
||||
details: { tool: detail, input_source: escalation.inputDetail },
|
||||
owasp: ['ASI01'],
|
||||
action_taken: 'warned',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -840,6 +857,14 @@ if (!(classes.length === 1 && classes[0] === 'neutral')) {
|
|||
if (detected) {
|
||||
messages.push(formatDataFlowWarning(evidence, flowLink.sourceEntries));
|
||||
appendEntry(stateFile, { type: 'data_flow_warning', ts: Date.now() });
|
||||
writeAuditEvent({
|
||||
event_type: 'data_flow_trifecta',
|
||||
severity: 'high',
|
||||
source: 'post-session-guard',
|
||||
details: { evidence, flow_sources: flowLink.sourceEntries.length },
|
||||
owasp: ['ASI01', 'ASI02'],
|
||||
action_taken: 'warned',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -855,6 +880,14 @@ if (outputSize > 0) {
|
|||
if (totalVolume >= bytes && !hasVolumeWarning(allEntries, bytes)) {
|
||||
messages.push(formatVolumeWarning(totalVolume, label, severity));
|
||||
appendEntry(stateFile, { type: 'volume_warning', ts: Date.now(), threshold: bytes });
|
||||
writeAuditEvent({
|
||||
event_type: 'volume_threshold',
|
||||
severity: severity.toLowerCase(),
|
||||
source: 'post-session-guard',
|
||||
details: { total_bytes: totalVolume, threshold: label },
|
||||
owasp: ['ASI02'],
|
||||
action_taken: 'warned',
|
||||
});
|
||||
break; // only emit highest unwarned threshold
|
||||
}
|
||||
}
|
||||
|
|
@ -869,6 +902,14 @@ if (outputSize > 0) {
|
|||
if (slowBurn.detected && !hasSlowBurnWarning(longWindow)) {
|
||||
messages.push(formatSlowBurnWarning(slowBurn.spread));
|
||||
appendEntry(stateFile, { type: 'slow_burn_warning', ts: Date.now() });
|
||||
writeAuditEvent({
|
||||
event_type: 'slow_burn_trifecta',
|
||||
severity: 'medium',
|
||||
source: 'post-session-guard',
|
||||
details: { spread: slowBurn.spread },
|
||||
owasp: ['ASI06', 'ASI08'],
|
||||
action_taken: 'warned',
|
||||
});
|
||||
}
|
||||
|
||||
// Behavioral drift: JSD on tool distribution (first vs last DRIFT_SAMPLE_SIZE)
|
||||
|
|
@ -876,6 +917,14 @@ if (outputSize > 0) {
|
|||
if (drift.drifted && !hasDriftWarning(longWindow)) {
|
||||
messages.push(formatDriftWarning(drift.jsd, drift.firstTools, drift.lastTools));
|
||||
appendEntry(stateFile, { type: 'drift_warning', ts: Date.now() });
|
||||
writeAuditEvent({
|
||||
event_type: 'behavioral_drift',
|
||||
severity: 'medium',
|
||||
source: 'post-session-guard',
|
||||
details: { jsd: drift.jsd, first_tools: drift.firstTools, last_tools: drift.lastTools },
|
||||
owasp: ['ASI06', 'ASI08'],
|
||||
action_taken: 'warned',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue