Behoud van conversaties
Het behouden van bewijs uit AI-conversaties: het vastleggen van interactielogs, reconstructie van het contextvenster, integriteit van multi-turn-conversaties, behoud van de keten van tool-aanroepen en de opbouw van een forensische tijdlijn.
Conversaties zijn het primaire forensische artefact in de meeste AI-beveiligingsincidenten. Elke jailbreak, data-exfiltratie en modelmanipulatie-aanval produceert een conversatieregistratie die precies laat zien wat de aanvaller verstuurde en wat het model terugstuurde. Goed bewaard conversatiebewijs kan de aanvalstechniek aantonen, de datablootstelling kwantificeren, intentie bewijzen of weerleggen, en een tijdlijn van gebeurtenissen vaststellen.
Het behoud van conversaties is echter complexer dan het lijkt. Een conversatie met een AI-systeem is niet simpelweg een opeenvolging van tekstberichten — het omvat systeemprompts, contextvensters met beperkte capaciteit, tool-aanroepen en hun resultaten, multimodale in- en uitvoer, en metadata die forensisch net zo waardevol kan zijn als de inhoud zelf.
Wat een volledige conversatieregistratie inhoudt
Kerncomponenten
Een forensisch volledige conversatieregistratie omvat al het volgende:
# evidence_preservation/conversation_record.py
"""
Volledige conversatieregistratie voor forensisch behoud.
"""
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
@dataclass
class ConversationRecord:
# Sessie-identificatie
session_id: str
conversation_id: str
user_id: str
user_ip: str
user_agent: str
# Temporele data
session_start: datetime
session_end: Optional[datetime]
# Systeemcontext (wat het model gezegd werd te zijn)
system_prompt: str
system_prompt_version: str
model_id: str
model_version: str
# Berichten op volgorde
messages: list = field(default_factory=list)
# Toolinteracties
tool_calls: list = field(default_factory=list)
# RAG-ophalingen (welke context werd geïnjecteerd)
rag_retrievals: list = field(default_factory=list)
# Guardrail-gebeurtenissen
guardrail_events: list = field(default_factory=list)
# Metadata
total_input_tokens: int = 0
total_output_tokens: int = 0
api_cost: float = 0.0
@dataclass
class Message:
role: str # "system", "user", "assistant", "tool"
content: str # Berichttekst
timestamp: datetime # Wanneer het bericht is verzonden/ontvangen
sequence_number: int # Positie in de conversatie
token_count: int # Aantal tokens voor dit bericht
# Voor gebruikersberichten
original_input: str = "" # Voor enige voorverwerking
preprocessed_input: str = "" # Na guardrail-verwerking
injection_score: float = 0.0
# Voor assistant-berichten
raw_output: str = "" # Voor uitvoerfiltering
filtered_output: str = "" # Na uitvoerfiltering
was_filtered: bool = False
filter_reason: str = ""
# Voor tool-berichten
tool_name: str = ""
tool_input: str = ""
tool_output: str = ""
@dataclass
class RAGRetrieval:
query: str # De ophaalquery
timestamp: datetime
documents_retrieved: list # Document-ID's en scores
documents_injected: list # Welke documenten aan de context werden toegevoegd
total_injected_tokens: intReconstructie van het contextvenster
Het contextvenster is wat het model daadwerkelijk ziet bij het genereren van een reactie. In een multi-turn-conversatie kunnen eerdere berichten worden ingekort of samengevat om binnen de contextlimiet van het model te passen. Forensische analyse moet de exacte toestand van het contextvenster bij elke beurt reconstrueren.
# evidence_preservation/context_reconstruction.py
"""
Reconstrueer het exacte contextvenster op elk punt in
een conversatie.
"""
class ContextWindowReconstructor:
def __init__(self, model_config):
self.max_tokens = model_config.context_window_size
self.tokenizer = model_config.tokenizer
def reconstruct_at_turn(self, conversation: ConversationRecord,
turn_number: int) -> dict:
"""
Reconstrueer wat het contextvenster van het model bevatte
bij het genereren van de reactie bij de opgegeven beurt.
"""
# Begin met de systeemprompt (altijd aanwezig)
context = [{
"role": "system",
"content": conversation.system_prompt,
"tokens": self._count_tokens(conversation.system_prompt),
}]
total_tokens = context[0]["tokens"]
# Voeg berichten toe tot (maar niet inclusief) de doelbeurt
messages_to_include = [
m for m in conversation.messages
if m.sequence_number < turn_number
]
# Controleer of beheer van het contextvenster is toegepast
# (berichten kunnen zijn weggelaten of samengevat)
if self._exceeds_window(messages_to_include, total_tokens):
context_managed = self._apply_context_management(
messages_to_include, total_tokens
)
context.extend(context_managed["included_messages"])
return {
"turn_number": turn_number,
"context_window": context,
"total_tokens": context_managed["total_tokens"],
"truncated_messages": context_managed["truncated"],
"summarized": context_managed.get("summary_applied",
False),
"warning": "Context window management was applied. "
"Some earlier messages may not have been "
"visible to the model.",
}
# Voeg RAG-ophalingen toe die bij deze beurt zijn geïnjecteerd
rag_at_turn = [
r for r in conversation.rag_retrievals
if r.timestamp <= conversation.messages[turn_number - 1].timestamp
]
for rag in rag_at_turn:
for doc in rag.documents_injected:
context.append({
"role": "context",
"content": doc["content"],
"source": doc["source"],
"tokens": doc["token_count"],
})
total_tokens += doc["token_count"]
# Voeg conversatieberichten toe
for msg in messages_to_include:
context.append({
"role": msg.role,
"content": msg.content,
"tokens": msg.token_count,
"original_input": msg.original_input
if msg.role == "user" else None,
})
total_tokens += msg.token_count
return {
"turn_number": turn_number,
"context_window": context,
"total_tokens": total_tokens,
"window_utilization": total_tokens / self.max_tokens,
"truncated_messages": [],
}Integriteit van multi-turn-conversaties
Behoud van de berichtvolgorde
De berichtvolgorde is forensisch kritiek. Bij een multi-turn-aanval stelt de opeenvolging van berichten de aanvalsmethodologie vast — welke probes het eerst kwamen, hoe de aanvaller zich aanpaste aan modelreacties, en wanneer de veiligheidsbypass plaatsvond.
# evidence_preservation/ordering_verification.py
"""
Verifieer en handhaaf de integriteit van de berichtvolgorde.
"""
def verify_message_ordering(conversation: ConversationRecord) -> dict:
"""
Verifieer dat berichten in de juiste chronologische en
sequentiële volgorde staan. Markeer eventuele anomalieën.
"""
issues = []
for i in range(1, len(conversation.messages)):
prev = conversation.messages[i - 1]
curr = conversation.messages[i]
# Controleer chronologische volgorde
if curr.timestamp < prev.timestamp:
issues.append({
"type": "timestamp_reversal",
"position": i,
"prev_timestamp": prev.timestamp.isoformat(),
"curr_timestamp": curr.timestamp.isoformat(),
"severity": "high",
})
# Controleer continuïteit van het volgnummer
if curr.sequence_number != prev.sequence_number + 1:
issues.append({
"type": "sequence_gap",
"position": i,
"expected_seq": prev.sequence_number + 1,
"actual_seq": curr.sequence_number,
"severity": "critical" if (
curr.sequence_number - prev.sequence_number > 1
) else "medium",
})
# Controleer rolafwisseling (user/assistant)
expected_roles = {
"user": {"assistant", "tool"},
"assistant": {"user", "tool"},
"tool": {"assistant"},
}
if curr.role not in expected_roles.get(prev.role, set()):
# Niet noodzakelijk een fout — tool-aanroepen doorbreken de afwisseling
if curr.role != "tool" and prev.role != "tool":
issues.append({
"type": "unexpected_role_sequence",
"position": i,
"prev_role": prev.role,
"curr_role": curr.role,
"severity": "medium",
})
return {
"messages_checked": len(conversation.messages),
"issues_found": len(issues),
"issues": issues,
"integrity": "verified" if not issues else "issues_found",
}Gapdetectie
Ontbrekende berichten in een conversatielog kunnen wijzen op manipulatie van de log, systeemfouten of opzettelijke verwijdering. Detecteer gaten door het volgende te analyseren:
- Continuïteit van volgnummers — Ontbrekende volgnummers wijzen op weggelaten berichten
- Tijdstempelgaten — Ongebruikelijk lange gaten tussen berichten kunnen wijzen op ontbrekende interacties
- Consistentie van tokenaantallen — Als de reactie van het model verwijst naar inhoud die niet in de bewaarde conversatie staat, kunnen berichten ontbreken
- Analyse van het contextvenster — Als het gedrag van het model suggereert dat het inhoud zag die niet in de log staat, is de log onvolledig
Behoud van de keten van tool-aanroepen
Het vastleggen van volledige toolinteracties
Voor agentic AI-systemen zijn tool-aanroepen kritiek forensisch bewijs. Een keten van tool-aanroepen laat zien welke acties de AI ondernam, welke data het benaderde en welke neveneffecten het produceerde.
# evidence_preservation/tool_chain.py
"""
Behoud volledige ketens van tool-aanroepen voor forensische analyse.
"""
@dataclass
class ToolCallRecord:
call_id: str
timestamp: datetime
sequence_in_conversation: int
# Wat het model heeft aangevraagd
tool_name: str
tool_arguments: dict
model_reasoning: str # Waarom het model deze tool koos
# Wat er gebeurde
tool_result: str
execution_duration_ms: int
success: bool
error_message: str = ""
# Neveneffecten
side_effects: list = field(default_factory=list)
# bijv. "schreef bestand X", "stuurde e-mail naar Y", "bevroeg database Z"
# Autorisatie
was_authorized: bool = True
authorization_check: str = ""
# Context
preceding_message: str = "" # Gebruikersbericht dat tot deze aanroep leidde
following_response: str = "" # Modelreactie na het toolresultaatHet behouden van neveneffecten
Tool-aanroepen kunnen neveneffecten produceren die buiten de conversatielog bestaan — aangemaakte bestanden, verzonden e-mails, uitgevoerde databasequery's, aangeroepen API's. Deze neveneffecten moeten worden gedocumenteerd als onderdeel van het conversatiebewijs:
def document_side_effects(tool_call: ToolCallRecord) -> list:
"""
Documenteer neveneffecten van een tool-aanroep voor forensische registraties.
"""
side_effects = []
if tool_call.tool_name == "file_write":
side_effects.append({
"type": "file_created",
"path": tool_call.tool_arguments.get("path"),
"content_hash": hash_content(
tool_call.tool_arguments.get("content", "")
),
"timestamp": tool_call.timestamp,
})
elif tool_call.tool_name == "database_query":
side_effects.append({
"type": "database_query",
"query": tool_call.tool_arguments.get("query"),
"rows_returned": len(
json.loads(tool_call.tool_result)
) if tool_call.success else 0,
"tables_accessed": extract_tables(
tool_call.tool_arguments.get("query", "")
),
})
elif tool_call.tool_name == "send_email":
side_effects.append({
"type": "email_sent",
"recipient": tool_call.tool_arguments.get("to"),
"subject": tool_call.tool_arguments.get("subject"),
"timestamp": tool_call.timestamp,
})
return side_effectsOpbouw van de forensische tijdlijn
Het correleren van conversatiegebeurtenissen met systeemgebeurtenissen
Een forensische tijdlijn combineert conversatiegebeurtenissen met systeemgebeurtenissen (logregels, waarschuwingen, configuratiewijzigingen) om een compleet verhaal van het incident te creëren.
# evidence_preservation/timeline.py
"""
Opbouw van een forensische tijdlijn uit meerdere bewijsbronnen.
"""
class ForensicTimeline:
def __init__(self):
self.events = []
def add_conversation_events(self,
conversation: ConversationRecord):
"""Voeg conversatieberichten toe aan de tijdlijn."""
for msg in conversation.messages:
self.events.append({
"timestamp": msg.timestamp,
"source": "conversation",
"type": f"{msg.role}_message",
"content_preview": msg.content[:200],
"session_id": conversation.session_id,
"details": {
"injection_score": msg.injection_score,
"was_filtered": msg.was_filtered,
"token_count": msg.token_count,
},
})
def add_guardrail_events(self,
conversation: ConversationRecord):
"""Voeg guardrail-triggers toe aan de tijdlijn."""
for event in conversation.guardrail_events:
self.events.append({
"timestamp": event["timestamp"],
"source": "guardrail",
"type": event["action"],
"content_preview": event.get("reason", ""),
"session_id": conversation.session_id,
})
def add_system_events(self, system_logs: list):
"""Voeg gebeurtenissen op systeemniveau toe (waarschuwingen, configuratiewijzigingen)."""
for log in system_logs:
self.events.append({
"timestamp": log["timestamp"],
"source": "system",
"type": log["event_type"],
"content_preview": log.get("message", ""),
})
def build(self) -> list:
"""Geef een chronologisch gesorteerde tijdlijn terug."""
return sorted(self.events, key=lambda e: e["timestamp"])
def export_for_report(self) -> str:
"""Exporteer de tijdlijn in een formaat dat geschikt is voor incidentrapporten."""
timeline = self.build()
lines = ["# Forensic Timeline", ""]
for event in timeline:
ts = event["timestamp"].strftime("%Y-%m-%d %H:%M:%S.%f UTC")
source = event["source"].upper()
etype = event["type"]
preview = event["content_preview"][:100]
lines.append(f"**{ts}** [{source}] {etype}")
if preview:
lines.append(f"> {preview}")
lines.append("")
return "\n".join(lines)Best practices voor behoud
Leg vast voor en na guardrail-verwerking
Bewaar zowel de oorspronkelijke gebruikersinvoer (voor de guardrails) als de voorverwerkte invoer (na de guardrails). Bewaar op dezelfde manier zowel de ruwe modeluitvoer als de gefilterde uitvoer. Het verschil tussen voor- en naverwerking onthult wat de guardrails hebben opgevangen.
Neem RAG-context op
Als de conversatie via RAG opgehaalde context bevat, bewaar dan welke documenten zijn opgehaald, hun gelijkenisscores en de exacte tekst die in het contextvenster is geïnjecteerd. RAG-context is vaak de plek waar de aanvalspayload vandaan komt.
Bewaar metadata naast de inhoud
Tijdstempels, tokenaantallen, injection-scores, gebruikers-IP's en sessie-identifiers zijn forensisch net zo waardevol als de conversatie-inhoud zelf. Strip nooit metadata tijdens het behoud.
Hash op het moment van verzameling
Bereken en registreer cryptografische hashes van conversatieregistraties op het moment van verzameling. Verifieer de hashes vóór elke analyse om te bevestigen dat het bewijs niet is gewijzigd.
Verder lezen
- Overzicht bewijsbehoud — Het bredere bewijsraamwerk
- Modelmomentopnamen — Bijbehorend bewijstype
- Forensisch onderzoek van promptlogs — Bewaarde conversaties analyseren
- Forensisch onderzoek van tool-aanroepen — Bewaarde toolinteracties analyseren