Threat Events¶
Security-relevant events are written to the threat_events table in PostgreSQL. The table is append-only and feeds the Phase 4 Threat Analyst agent that analyzes patterns across deployments.
Schema¶
CREATE TABLE threat_events (
id BIGSERIAL PRIMARY KEY,
timestamp TIMESTAMPTZ NOT NULL DEFAULT now(),
event_type TEXT NOT NULL,
task_id TEXT,
user_id BIGINT,
severity TEXT NOT NULL, -- 'info' | 'warning' | 'critical'
payload JSONB NOT NULL,
source_module TEXT NOT NULL
);
Event types¶
| Event type | Severity | What triggers it |
|---|---|---|
INJECTION_DETECTED |
critical | Tier-1 prompt-injection pattern matched in sanitize_input(). The input was rejected. |
TOOL_HASH_MISMATCH |
critical | A loaded tool's code hash did not match its stored hash in tool_registry. Invocation blocked. |
MODEL_INTEGRITY_MISMATCH |
critical | A model's weights or metadata didn't match the expected fingerprint at load time. |
PREFLIGHT_BUDGET_EXCEEDED |
warning | A pre-execution cost estimate would have exceeded the per-task token budget. The call was blocked before being made. |
PII_REDACTED |
info | sanitize_output() removed PII from text being logged or returned. |
LOOP_DETECTED |
warning | The action-history hash showed the same tool-call signature 3 times within a 5-step window. Task halted. |
STEP_LIMIT_REACHED |
warning | LangGraph hit the hard recursion limit. Task halted. |
TOKEN_BUDGET_EXCEEDED |
warning | Cumulative token usage for the task crossed 100% of its budget. Task force-ended. |
TOOL_ARG_INJECTION |
critical | Suspicious patterns in tool call arguments produced by the LLM. Common attack: agent passing user-controlled content into a shell tool unsanitized. |
TOOL_RESULT_INJECTION |
critical | Tool output contained patterns indicative of injection back toward the model (e.g., a fetched web page containing prompt-injection payloads). |
GUARDIAN_DENIED |
warning | Guardian denied a tool invocation. The payload contains which check failed. |
GUARDIAN_UNREACHABLE |
critical | Framework could not reach Guardian on its expected port. By policy, this halts execution; the framework will not fall back to running without Guardian. |
HITL_DENIED |
info | A human-in-the-loop approval gate was denied by the operator. |
RATE_LIMIT_TRIGGERED |
warning | A per-user, per-provider, or per-IP rate limit fired. |
KEY_NOT_FOUND |
critical | A Keychain lookup at startup returned empty for a required secret. |
Payload conventions¶
The payload JSONB column carries event-specific detail. By convention:
- For
INJECTION_DETECTED,payload.matched_patternsis an array of pattern IDs. - For
TOOL_HASH_MISMATCH,payload.tool_name,payload.expected_hash,payload.actual_hash. - For
LOOP_DETECTED,payload.action_signature(the MD5 hash that repeated) andpayload.window(the actual history slice). - For
TOKEN_BUDGET_EXCEEDED,payload.budget,payload.actual,payload.breakdownby tool. - For
GUARDIAN_DENIED,payload.check_name(which of the 7 checks failed),payload.tool_name,payload.reason.
Never log raw user prompts or secrets in payloads. The sanitization that runs at the trust boundary is enforced before writing.
Querying¶
Common queries:
-- Event counts by type, last 7 days
SELECT event_type, COUNT(*)
FROM threat_events
WHERE timestamp > now() - interval '7 days'
GROUP BY event_type
ORDER BY COUNT(*) DESC;
-- All critical events from a specific user
SELECT timestamp, event_type, payload
FROM threat_events
WHERE user_id = 42
AND severity = 'critical'
ORDER BY timestamp DESC
LIMIT 50;
-- Recent Guardian denials with reasons
SELECT timestamp, payload->>'tool_name' AS tool, payload->>'check_name' AS check, payload->>'reason' AS reason
FROM threat_events
WHERE event_type = 'GUARDIAN_DENIED'
AND timestamp > now() - interval '24 hours'
ORDER BY timestamp DESC;
Retention¶
By default, threat_events is retained for 90 days (set in the hardware profile as audit_log_retention_days). The same retention window applies to audit_log. Older rows are pruned by a nightly maintenance job.
For compliance scenarios that require longer retention, the recommended approach is to export to cold storage rather than extending the live retention window — threat_events is hot, indexed, and read frequently.