Skip to content

Audit & Compliance

Every policy decision in Triggerfish is logged with full context. There are no exceptions, no "debug mode" that disables logging, and no way for the LLM to suppress audit records. This provides a complete, tamper-evident record of every security decision the system has made.

What Gets Recorded

Audit logging is a fixed rule -- it is always active and cannot be disabled. Every enforcement hook execution produces an audit record containing:

FieldDescription
timestampWhen the decision was made (ISO 8601, UTC)
hook_typeWhich enforcement hook ran (PRE_CONTEXT_INJECTION, PRE_TOOL_CALL, POST_TOOL_RESPONSE, PRE_OUTPUT, SECRET_ACCESS, SESSION_RESET, AGENT_INVOCATION, MCP_TOOL_CALL)
session_idThe session in which the action occurred
decisionALLOW, BLOCK, or REDACT
reasonHuman-readable explanation of the decision
inputThe data or action that triggered the hook
rules_evaluatedWhich policy rules were checked to reach the decision
taint_beforeSession taint level before the action
taint_afterSession taint level after the action (if changed)
metadataAdditional context specific to the hook type

Audit Record Examples

Allowed Output

json
{
  "timestamp": "2025-01-29T10:23:47Z",
  "hook_type": "PRE_OUTPUT",
  "session_id": "sess_456",
  "decision": "ALLOW",
  "reason": "Classification check passed",
  "input": {
    "target_channel": "telegram",
    "recipient": "owner"
  },
  "rules_evaluated": [
    "no_write_down",
    "channel_classification"
  ],
  "taint_before": "INTERNAL",
  "taint_after": "INTERNAL"
}

Blocked Write-Down

json
{
  "timestamp": "2025-01-29T10:24:12Z",
  "hook_type": "PRE_OUTPUT",
  "session_id": "sess_456",
  "decision": "BLOCK",
  "reason": "Session taint (CONFIDENTIAL) exceeds effective classification (PUBLIC)",
  "input": {
    "target_channel": "whatsapp",
    "recipient": "external_user_789",
    "effective_classification": "PUBLIC"
  },
  "rules_evaluated": [
    "no_write_down",
    "channel_classification",
    "recipient_classification"
  ],
  "taint_before": "CONFIDENTIAL",
  "taint_after": "CONFIDENTIAL"
}

Tool Call with Taint Escalation

json
{
  "timestamp": "2025-01-29T10:23:45Z",
  "hook_type": "POST_TOOL_RESPONSE",
  "session_id": "sess_456",
  "decision": "ALLOW",
  "reason": "Tool response classified and taint updated",
  "input": {
    "tool_name": "salesforce.query_opportunities",
    "response_classification": "CONFIDENTIAL"
  },
  "rules_evaluated": [
    "tool_response_classification",
    "taint_escalation"
  ],
  "taint_before": "PUBLIC",
  "taint_after": "CONFIDENTIAL",
  "metadata": {
    "lineage_id": "lin_789xyz",
    "records_returned": 3
  }
}

Agent Delegation Blocked

json
{
  "timestamp": "2025-01-29T10:25:00Z",
  "hook_type": "AGENT_INVOCATION",
  "session_id": "sess_456",
  "decision": "BLOCK",
  "reason": "Agent ceiling (INTERNAL) below session taint (CONFIDENTIAL)",
  "input": {
    "caller_agent_id": "agent_abc",
    "callee_agent_id": "agent_def",
    "callee_ceiling": "INTERNAL",
    "task": "Generate public summary"
  },
  "rules_evaluated": [
    "delegation_ceiling_check",
    "delegation_allowlist",
    "delegation_depth"
  ],
  "taint_before": "CONFIDENTIAL",
  "taint_after": "CONFIDENTIAL"
}

Audit Trace Capabilities

Audit records can be queried in four ways, each serving a different compliance and forensic need.

Forward Trace

Question: "What happened to data from Salesforce record opp_00123ABC?"

A forward trace follows a data element from its point of origin through every transformation, session, and output. It answers: where did this data go, who saw it, and was it ever sent outside the organization?

Origin: salesforce.query_opportunities
  --> lineage_id: lin_789xyz
  --> classification: CONFIDENTIAL
  --> session: sess_456

Transformations:
  --> Extracted fields: name, amount, stage
  --> LLM summarized 3 records into pipeline overview

Outputs:
  --> Sent to owner via Telegram (ALLOWED)
  --> Blocked from WhatsApp external contact (BLOCKED)

Backward Trace

Question: "What sources contributed to the message sent at 10:24 UTC?"

A backward trace starts from an output and walks back through the lineage chain to identify every data source that influenced the output. This is essential for understanding whether classified data was included in a response.

Output: Message sent to Telegram at 10:24:00Z
  --> session: sess_456
  --> lineage sources:
      --> lin_789xyz: Salesforce opportunity (CONFIDENTIAL)
      --> lin_790xyz: Salesforce opportunity (CONFIDENTIAL)
      --> lin_791xyz: Salesforce opportunity (CONFIDENTIAL)
      --> lin_792xyz: Weather API (PUBLIC)

Classification Justification

Question: "Why is this data marked CONFIDENTIAL?"

Classification justification traces back to the rule or policy that assigned the classification level:

Data: Pipeline summary (lin_789xyz)
Classification: CONFIDENTIAL
Reason: source_system_default
  --> Salesforce integration default classification: CONFIDENTIAL
  --> Configured by: admin_001 at 2025-01-10T08:00:00Z
  --> Policy rule: "All Salesforce data classified as CONFIDENTIAL"

Compliance Export

For legal, regulatory, or internal review, Triggerfish can export the full chain of custody for any data element or time range:

Export request:
  --> Time range: 2025-01-29T00:00:00Z to 2025-01-29T23:59:59Z
  --> Scope: All sessions for user_456
  --> Format: JSON

Export includes:
  --> All audit records in the time range
  --> All lineage records referenced by audit records
  --> All session state transitions
  --> All policy decisions (ALLOW, BLOCK, REDACT)
  --> All taint changes
  --> All delegation chain records

TIP

Compliance exports are structured JSON files that can be ingested by SIEM systems, compliance dashboards, or legal review tools. The export format is stable and versioned.

Data Lineage

Audit logging works in conjunction with Triggerfish's data lineage system. Every data element processed by Triggerfish carries provenance metadata:

json
{
  "lineage_id": "lin_789xyz",
  "content_hash": "sha256:a1b2c3d4...",
  "origin": {
    "source_type": "integration",
    "source_name": "salesforce",
    "record_id": "opp_00123ABC",
    "record_type": "Opportunity",
    "accessed_at": "2025-01-29T10:23:45Z",
    "accessed_by": "user_456",
    "access_method": "plugin_query"
  },
  "classification": {
    "level": "CONFIDENTIAL",
    "reason": "source_system_default",
    "assigned_at": "2025-01-29T10:23:45Z",
    "can_be_downgraded": false
  },
  "transformations": [
    {
      "type": "extraction",
      "description": "Selected fields: name, amount, stage",
      "timestamp": "2025-01-29T10:23:46Z",
      "agent_id": "agent_123"
    },
    {
      "type": "summarization",
      "description": "LLM summarized 3 records into pipeline overview",
      "timestamp": "2025-01-29T10:23:47Z",
      "input_lineage_ids": ["lin_789xyz", "lin_790xyz", "lin_791xyz"],
      "agent_id": "agent_123"
    }
  ],
  "current_location": {
    "session_id": "sess_456",
    "context_position": "assistant_response_3"
  }
}

Lineage records are created at POST_TOOL_RESPONSE (when data enters the system) and updated as data is transformed. Aggregated data inherits max(input classifications) -- if any input is CONFIDENTIAL, the output is at least CONFIDENTIAL.

INFO

Lineage tracking events include:

EventLineage Action
Data read from integrationCreate lineage record with origin
Data transformed by LLMAppend transformation, link input lineages
Data aggregated from multiple sourcesMerge lineage, classification = max(inputs)
Data sent to channelRecord destination, verify classification
Session resetArchive lineage records, clear from context

Storage and Retention

Audit logs are persisted through the StorageProvider abstraction under the audit: namespace. Lineage records are stored under the lineage: namespace.

Data TypeNamespaceDefault Retention
Audit logsaudit:1 year
Lineage recordslineage:90 days
Session statesessions:30 days
Taint historytaint:Matches session retention

SECURITY

Retention periods are configurable, but audit logs default to 1 year to support compliance requirements (SOC 2, GDPR, HIPAA). Reducing the retention period below your organization's regulatory requirement is the administrator's responsibility.

Storage Backends

TierBackendDetails
PersonalSQLiteWAL-mode database at ~/.triggerfish/data/triggerfish.db. Audit records are stored as structured JSON in the same database as all other Triggerfish state.
EnterprisePluggableEnterprise backends (Postgres, S3, etc.) can be used via the StorageProvider interface. This allows integration with existing log aggregation infrastructure.

Immutability and Integrity

Audit records are append-only. Once written, they cannot be modified or deleted by any component of the system -- including the LLM, the agent, or plugins. Deletion occurs only through retention policy expiration.

Each audit record includes a content hash that can be used to verify integrity. If records are exported for compliance review, the hashes can be validated against the stored records to detect tampering.

Enterprise Compliance Features

Enterprise deployments can extend audit logging with:

FeatureDescription
Legal holdSuspend retention-based deletion for specified users, sessions, or time ranges
SIEM integrationStream audit events to Splunk, Datadog, or other SIEM systems in real time
Compliance dashboardsVisual overview of policy decisions, blocked actions, and taint patterns
Scheduled exportsAutomatic periodic exports for regulatory review
Alert rulesTrigger notifications when specific audit patterns occur (e.g., repeated blocked write-downs)

Released under the MIT License.