Momentopnamen van modeltoestand
Technieken voor het vastleggen en behouden van de toestand van een AI-model tijdens incidentrespons: momentopnamen van gewichten, vastlegging van configuratie, gedragsfingerprinting en integriteitsverificatie van modelartefacten.
Momentopnamen van modeltoestand zijn het AI-forensische equivalent van schijfimages in traditionele digitale forensica. Een volledige modelmomentopname legt alles vast wat nodig is om het gedrag van het model op het moment van het incident te reproduceren — niet alleen de modelgewichten, maar de volledige deploymentconfiguratie inclusief systeemprompts, guardrails, tooldefinities en runtime-parameters.
De uitdaging is dat de modeltoestand complexer en minder gestandaardiseerd is dan de toestand van een bestandssysteem. Twee deployments van dezelfde modelgewichten met verschillende systeemprompts gedragen zich anders. Een model achter een guardrail-proxy gedraagt zich anders dan hetzelfde model dat rechtstreeks wordt benaderd. De momentopname moet de volledige gedragscontext vastleggen, niet alleen het modelartefact.
Wat vast te leggen
Volledige inventaris van de modeltoestand
Een forensisch volledige modelmomentopname omvat al het volgende:
# evidence_preservation/model_snapshot.py
"""
Volledige vastlegging van de modeltoestand voor forensisch behoud.
"""
import hashlib
import json
import os
import shutil
from datetime import datetime
from dataclasses import dataclass, field
@dataclass
class ModelSnapshot:
# Identificatie
snapshot_id: str
incident_id: str
captured_by: str
capture_timestamp: datetime
# Modelidentiteit
model_name: str
model_version: str
model_provider: str # "self-hosted" of naam van de aanbieder
model_registry_url: str
# Modelartefacten (voor self-hosted modellen)
weights_hash: str = ""
weights_location: str = ""
tokenizer_hash: str = ""
tokenizer_location: str = ""
# Configuratie
system_prompt: str = ""
system_prompt_hash: str = ""
generation_parameters: dict = field(default_factory=dict)
# temperature, top_p, max_tokens, enz.
# Guardrails en filters
guardrail_config: dict = field(default_factory=dict)
guardrail_config_hash: str = ""
content_policy: dict = field(default_factory=dict)
content_policy_hash: str = ""
# Tooldefinities (voor agentic systemen)
tool_definitions: list = field(default_factory=list)
tool_definitions_hash: str = ""
# RAG-configuratie (indien van toepassing)
rag_config: dict = field(default_factory=dict)
embedding_model: str = ""
vector_db_snapshot_location: str = ""
# Gedragsfingerprint
behavioral_fingerprint: dict = field(default_factory=dict)
# Infrastructuurcontext
deployment_manifest: dict = field(default_factory=dict)
infrastructure_version: str = ""Vastlegprocedures per deploymenttype
Self-hosted modellen:
def capture_self_hosted(model_path: str, config_path: str,
output_dir: str) -> ModelSnapshot:
"""
Leg de volledige toestand van een self-hosted model vast.
"""
snapshot = ModelSnapshot(
snapshot_id=generate_id(),
capture_timestamp=datetime.utcnow(),
model_provider="self-hosted",
)
# Stap 1: Hash de modelgewichten (niet kopiëren — te groot)
snapshot.weights_hash = hash_file(
os.path.join(model_path, "model.safetensors")
)
snapshot.weights_location = model_path
# Stap 2: Hash en kopieer de tokenizer
tokenizer_path = os.path.join(model_path, "tokenizer.json")
snapshot.tokenizer_hash = hash_file(tokenizer_path)
shutil.copy2(tokenizer_path,
os.path.join(output_dir, "tokenizer.json"))
# Stap 3: Leg de systeemprompt en configuratie vast
with open(config_path) as f:
config = json.load(f)
snapshot.system_prompt = config.get("system_prompt", "")
snapshot.system_prompt_hash = hashlib.sha256(
snapshot.system_prompt.encode()
).hexdigest()
snapshot.generation_parameters = {
"temperature": config.get("temperature"),
"top_p": config.get("top_p"),
"max_tokens": config.get("max_tokens"),
"stop_sequences": config.get("stop_sequences", []),
}
# Stap 4: Leg de guardrail-configuratie vast
guardrail_path = config.get("guardrail_config_path")
if guardrail_path and os.path.exists(guardrail_path):
with open(guardrail_path) as f:
snapshot.guardrail_config = json.load(f)
snapshot.guardrail_config_hash = hash_file(guardrail_path)
shutil.copy2(guardrail_path,
os.path.join(output_dir, "guardrails.json"))
# Stap 5: Leg de tooldefinities vast
tool_path = config.get("tool_definitions_path")
if tool_path and os.path.exists(tool_path):
with open(tool_path) as f:
snapshot.tool_definitions = json.load(f)
snapshot.tool_definitions_hash = hash_file(tool_path)
# Stap 6: Schrijf het manifest van de momentopname
manifest_path = os.path.join(output_dir, "snapshot_manifest.json")
with open(manifest_path, "w") as f:
json.dump(snapshot.__dict__, f, indent=2, default=str)
return snapshotAPI-gehoste modellen (OpenAI, Anthropic, enz.):
def capture_api_hosted(api_config: dict,
output_dir: str) -> ModelSnapshot:
"""
Leg de toestand van een API-gehoste modeldeployment vast.
Kan geen gewichten vastleggen, maar legt al het andere vast.
"""
snapshot = ModelSnapshot(
snapshot_id=generate_id(),
capture_timestamp=datetime.utcnow(),
model_provider=api_config["provider"],
model_name=api_config["model_name"],
model_version=api_config.get("model_version", "unknown"),
)
# Voor API-modellen kunnen we geen gewichten hashen, maar moeten we
# de exacte model-identifier registreren
snapshot.weights_hash = "N/A — API-hosted model"
snapshot.weights_location = (
f"{api_config['provider']}:{api_config['model_name']}"
)
# Leg de configuratie op applicatieniveau vast
snapshot.system_prompt = api_config.get("system_prompt", "")
snapshot.system_prompt_hash = hashlib.sha256(
snapshot.system_prompt.encode()
).hexdigest()
snapshot.generation_parameters = {
k: v for k, v in api_config.items()
if k in ["temperature", "top_p", "max_tokens",
"frequency_penalty", "presence_penalty"]
}
return snapshotGedragsfingerprinting
Waarom gedragsfingerprints belangrijk zijn
Modelgewichten en configuratie beschrijven wat het model is. Gedragsfingerprints beschrijven wat het model doet. Voor forensische doeleinden zijn beide noodzakelijk, omdat:
- Dezelfde gewichten met verschillende prompts ander gedrag produceren
- Modelaanbieders modelversies kunnen bijwerken zonder de model-identifier te wijzigen
- Gedragsbewijs betekenisvoller is voor niet-technische belanghebbenden (advocaten, toezichthouders, leidinggevenden) dan hashes van gewichten
Een gedragsfingerprint creëren
# evidence_preservation/behavioral_fingerprint.py
"""
Gedragsfingerprinting voor het vergelijken van modeltoestanden.
"""
import numpy as np
from collections import Counter
class BehavioralFingerprint:
def __init__(self, model_endpoint):
self.endpoint = model_endpoint
def generate_fingerprint(self, probe_suite: dict,
repetitions: int = 5) -> dict:
"""
Genereer een gedragsfingerprint door gestandaardiseerde probes
uit te voeren en de reactiepatronen te karakteriseren.
"""
fingerprint = {
"timestamp": datetime.utcnow().isoformat(),
"model_version": self.endpoint.version,
"probe_results": {},
"aggregate_metrics": {},
}
all_response_lengths = []
all_refusal_rates = {}
for category, prompts in probe_suite.items():
category_results = []
for prompt in prompts:
prompt_results = []
refusals = 0
for rep in range(repetitions):
response = self.endpoint.generate(prompt)
is_refusal = self._is_refusal(response.text)
prompt_results.append({
"response_length": len(response.text),
"token_count": response.token_count,
"is_refusal": is_refusal,
"response_hash": hashlib.sha256(
response.text.encode()
).hexdigest()[:16],
})
all_response_lengths.append(len(response.text))
if is_refusal:
refusals += 1
refusal_rate = refusals / repetitions
all_refusal_rates[f"{category}:{prompt[:50]}"] = refusal_rate
category_results.append({
"prompt": prompt,
"refusal_rate": refusal_rate,
"avg_response_length": np.mean(
[r["response_length"] for r in prompt_results]
),
"response_variance": np.var(
[r["response_length"] for r in prompt_results]
),
"unique_responses": len(set(
r["response_hash"] for r in prompt_results
)),
})
fingerprint["probe_results"][category] = category_results
# Geaggregeerde metrieken
fingerprint["aggregate_metrics"] = {
"overall_refusal_rate": np.mean(
list(all_refusal_rates.values())
),
"avg_response_length": np.mean(all_response_lengths),
"response_length_std": np.std(all_response_lengths),
"safety_probe_refusal_rate": np.mean([
v for k, v in all_refusal_rates.items()
if "safety" in k
]) if any("safety" in k for k in all_refusal_rates) else None,
}
return fingerprint
def _is_refusal(self, response_text: str) -> bool:
"""Heuristische controle op een weigering van het model."""
refusal_indicators = [
"i cannot", "i can't", "i'm unable to",
"i am unable to", "i will not", "i won't",
"i'm not able to", "as an ai",
"against my guidelines", "not appropriate",
]
lower = response_text.lower()
return any(indicator in lower
for indicator in refusal_indicators)Vergelijking van fingerprints
Vergelijk fingerprints van voor en na het incident om gedragsveranderingen te identificeren:
def compare_fingerprints(pre_incident: dict,
post_incident: dict) -> dict:
"""
Vergelijk twee gedragsfingerprints om veranderingen te identificeren.
"""
changes = {
"metric_changes": {},
"category_changes": {},
"significant_changes": [],
}
# Vergelijk geaggregeerde metrieken
pre_metrics = pre_incident["aggregate_metrics"]
post_metrics = post_incident["aggregate_metrics"]
for metric in pre_metrics:
if pre_metrics[metric] is None or post_metrics.get(metric) is None:
continue
pre_val = pre_metrics[metric]
post_val = post_metrics[metric]
delta = post_val - pre_val
changes["metric_changes"][metric] = {
"pre": pre_val,
"post": post_val,
"delta": delta,
"percent_change": (delta / pre_val * 100)
if pre_val != 0 else float("inf"),
}
# Markeer significante veranderingen
if abs(delta / (pre_val + 1e-10)) > 0.1: # >10% verandering
changes["significant_changes"].append({
"metric": metric,
"change": f"{delta / (pre_val + 1e-10) * 100:.1f}%",
"direction": "increase" if delta > 0 else "decrease",
})
return changesIntegriteitsverificatie van het model
Detecteren van ongeautoriseerde modelwijzigingen
Verifieer voor self-hosted modellen dat het uitgerolde model overeenkomt met de verwachte versie door cryptografische hashes van modelartefacten te vergelijken:
# evidence_preservation/integrity_check.py
"""
Verifieer de integriteit van modelartefacten tegen bekende, goede hashes.
"""
class ModelIntegrityChecker:
def __init__(self, registry):
self.registry = registry
def verify_deployed_model(self, deployment_path: str,
expected_version: str) -> dict:
"""
Verifieer dat de uitgerolde modelartefacten overeenkomen met
de geregistreerde versie.
"""
expected = self.registry.get_version(expected_version)
results = {"verified": True, "checks": []}
# Controleer elk artefact
artifacts_to_check = [
("model weights", "model.safetensors",
expected.weights_hash),
("tokenizer", "tokenizer.json",
expected.tokenizer_hash),
("config", "config.json",
expected.config_hash),
]
for name, filename, expected_hash in artifacts_to_check:
filepath = os.path.join(deployment_path, filename)
if not os.path.exists(filepath):
results["checks"].append({
"artifact": name,
"status": "MISSING",
"expected_hash": expected_hash,
})
results["verified"] = False
continue
actual_hash = hash_file(filepath)
match = actual_hash == expected_hash
results["checks"].append({
"artifact": name,
"status": "MATCH" if match else "MISMATCH",
"expected_hash": expected_hash,
"actual_hash": actual_hash,
})
if not match:
results["verified"] = False
return resultsOpslag en bewaring
Forensische opslagvereisten
Modelmomentopnamen moeten worden opgeslagen met dezelfde beveiligings- en integriteitsgaranties als ander forensisch bewijs:
- Write-once-opslag. Gebruik write-once-media of append-only-opslag om wijziging na verzameling te voorkomen.
- Versleuteling at-rest. Versleutel de opslag van momentopnamen om het intellectuele eigendom van het model en eventuele gevoelige data in de configuratie te beschermen.
- Toegangscontroles. Beperk de toegang tot het forensicateam en juridisch advies. Log alle toegang tot de opslag van momentopnamen.
- Geografische overwegingen. Als het incident regelgevende kennisgeving vereist (GDPR, HIPAA), zorg er dan voor dat de opslag van momentopnamen voldoet aan de vereisten voor dataresidentie.
- Bewaartermijn. Bewaar momentopnamen voor de duur die het incidenttype vereist — doorgaans totdat de gerechtelijke procedures zijn afgerond of de door de toepasselijke regelgeving voorgeschreven bewaartermijn verstrijkt.
Leg de modeltoestand onmiddellijk vast
Leg vóór enige inperkingsactie de volledige modeltoestand vast, inclusief de hash van de gewichten, systeemprompt, guardrails en tooldefinities.
Genereer een gedragsfingerprint
Voer de gestandaardiseerde probe-suite uit tegen het live model om de gedragstoestand vast te leggen die niet alleen uit statische artefacten kan worden afgeleid.
Verifieer de integriteit van artefacten
Vergelijk de uitgerolde modelartefacten met het modelregister om ongeautoriseerde wijzigingen te detecteren.
Sla op met chain of custody
Sla alle vastgelegde artefacten op in forensische opslag met hashverificatie, toegangslogging en documentatie van de chain of custody.
Verder lezen
- Overzicht bewijsbehoud — Het bredere bewijsraamwerk
- Behoud van conversaties — Interactieregistraties behouden
- Modelforensica — Bewaarde modelartefacten analyseren
- Manipulatiedetectie — Ongeautoriseerde modelwijzigingen detecteren