Incident Response voor de AI-toeleveringsketen
Incident response-procedures voor compromittering in de AI-toeleveringsketen, waaronder modelrepository's, trainingspijplijnen en dependency-ketens.
Overzicht
De AI-toeleveringsketen omvat elke component die bijdraagt aan een uitgerold AI-systeem: voorgetrainde modelgewichten, fine-tuning-datasets, trainingsframeworks (PyTorch, TensorFlow, JAX), inferentieservers (vLLM, Triton, TGI), modelbestandsformaten (safetensors, GGUF, ONNX), tokenizers, embeddingmodellen, vectordatabases, guardrail-systemen en de volledige software-dependency-boom daaronder. Een compromittering op elk punt in deze keten kan zich verspreiden naar elk downstream-systeem dat ervan afhankelijk is.
AI-supply chain-aanvallen zijn bijzonder gevaarlijk vanwege de betrokken vertrouwensrelaties. Wanneer een organisatie een model downloadt van Hugging Face Hub, vertrouwt ze de modelauteur, de integriteitscontroles van het platform, het serialisatieformaat en elke dependency die tijdens inferentie wordt geladen. In tegenstelling tot traditionele software, waar supply chain-aanvallen uitvoerbare code injecteren, kunnen AI-supply chain-aanvallen ook subtiele gedragswijzigingen injecteren — backdoors, biases of degradatie van veiligheidsalignment — die onzichtbaar zijn voor standaard code review.
Dit artikel biedt incident response-procedures die specifiek zijn ontworpen voor compromitteringen in de AI-toeleveringsketen, met aandacht voor detectie, indamming, onderzoek en herstel. De procedures zijn afgestemd op het NIST Secure Software Development Framework (SSDF) en MITRE ATLAS supply chain-technieken.
Aanvalsoppervlak van de AI-toeleveringsketen
Modelrepository's
Publieke modelrepository's (Hugging Face Hub, PyTorch Hub, TensorFlow Hub, ONNX Model Zoo) vormen het meest zichtbare aanvalsoppervlak. Dreigingen omvatten:
| Aanvalsvector | Beschrijving | Historisch precedent |
|---|---|---|
| Kwaadaardige model-upload | Aanvaller uploadt een getrojaniseerd model met een legitiem klinkende naam | Onderzoek van JFrog Security (2024) demonstreerde pickle-gebaseerde code-uitvoering in HF-modellen |
| Typosquatting | Model met een naam die lijkt op die van een populair model om verkeerde downloads te vangen | Gangbaar in PyPI/npm, opkomend in modelregisters |
| Accountcompromittering | Het account van een legitieme modelauteur wordt gecompromitteerd | HF voerde access tokens op org-niveau in na meldingen uit de community |
| Serialisatie-exploits | Modelbestanden met uitvoerbare code (pickle, PyTorch JIT) | Onderzoek van ESET en Trail of Bits naar pickle-deserialisatie-aanvallen |
| Metadatamanipulatie | Model card claimt veiligheidseigenschappen die het model niet heeft | Geen standaard verificatiemechanisme voor model card-claims |
Trainingsframeworks en dependencies
Het Python ML-ecosysteem heeft diepe dependency-bomen. Een typische LLM-serving-stack kan het volgende bevatten:
# Voorbeeld van een dependency-boom voor een vLLM-deployment
pip install vllm
# Dit installeert transitief:
# - torch (PyTorch) - kern-ML-framework
# - transformers (Hugging Face) - model laden en tokenisatie
# - safetensors - veilige modelserialisatie
# - sentencepiece / tokenizers - tokenisatie
# - numpy, scipy - numerieke berekeningen
# - triton - compilatie van GPU-kernels
# - uvicorn, fastapi - HTTP-serving
# Plus tientallen transitieve dependencies
# Bekijk de volledige dependency-boom
pip install pipdeptree
pipdeptree -p vllmEen compromittering van een willekeurig pakket in deze boom — via een kwaadaardige update, een dependency confusion-aanval of een overname van een maintainer-account — kan elk AI-systeem treffen dat ervan afhankelijk is.
Bronnen van trainingsdata
Trainingsdata van derden is een supply chain-component. Compromitteringen omvatten:
- Inbreuk bij een dataleverancier die leidt tot vergiftigde datasets
- Web scraping-pijplijnen die door de tegenstander gecontroleerde inhoud opnemen
- Annotatiediensten waar annotators worden gecompromitteerd of omgekocht
- Synthetische data gegenereerd door gecompromitteerde modellen
Indicatoren van compromittering
IoC's op modelniveau
"""
Detectiemodule voor AI supply chain-indicatoren van compromittering (IoC).
"""
import hashlib
import json
import subprocess
from pathlib import Path
from dataclasses import dataclass
@dataclass
class SupplyChainIoC:
"""Een indicator van compromittering in de AI-toeleveringsketen."""
ioc_type: str
severity: str # "low", "medium", "high", "critical"
description: str
evidence: dict
mitre_atlas_id: str | None = None
def check_model_file_safety(model_path: str) -> list[SupplyChainIoC]:
"""
Controleer een modelbestand op supply chain-indicatoren van compromittering.
Onderzoekt het serialisatieformaat en de inhoud van het model op
bekende kwaadaardige patronen.
"""
iocs = []
path = Path(model_path)
# Check 1: Gevaarlijke serialisatieformaten
if path.suffix in (".pkl", ".pickle", ".bin"):
# PyTorch .bin-bestanden gebruiken intern pickle
iocs.append(SupplyChainIoC(
ioc_type="dangerous_serialization",
severity="high",
description=(
f"Model uses {path.suffix} format which supports arbitrary "
"code execution during deserialization. Prefer safetensors format."
),
evidence={"file": str(path), "format": path.suffix},
mitre_atlas_id="AML.T0010", # ML Supply Chain Compromise
))
# Check 2: Scan pickle-bestanden op bekende kwaadaardige patronen
if path.suffix in (".pkl", ".pickle", ".bin", ".pt", ".pth"):
try:
import pickletools
import io
with open(path, "rb") as f:
# Lees de eerste 10MB om verdachte opcodes te scannen
header = f.read(10 * 1024 * 1024)
# Zoek naar patronen als os.system, subprocess, eval, exec
suspicious_strings = [
b"os.system", b"subprocess", b"eval", b"exec",
b"__import__", b"builtins", b"commands",
b"/bin/sh", b"/bin/bash", b"cmd.exe",
b"requests.get", b"urllib",
]
for pattern in suspicious_strings:
if pattern in header:
iocs.append(SupplyChainIoC(
ioc_type="malicious_payload",
severity="critical",
description=f"Suspicious pattern '{pattern.decode()}' found in model file",
evidence={"file": str(path), "pattern": pattern.decode()},
mitre_atlas_id="AML.T0010",
))
except Exception:
pass
# Check 3: Verifieer safetensors-integriteit
if path.suffix == ".safetensors":
try:
from safetensors import safe_open
# safetensors-formaat ondersteunt geen code-uitvoering
# maar verifieer dat het bestand geldig is
with safe_open(str(path), framework="pt") as f:
_ = f.keys()
except Exception as e:
iocs.append(SupplyChainIoC(
ioc_type="corrupted_model_file",
severity="medium",
description=f"Safetensors file failed validation: {e}",
evidence={"file": str(path), "error": str(e)},
))
return iocs
def check_dependency_integrity(requirements_file: str) -> list[SupplyChainIoC]:
"""
Controleer Python-dependencies op supply chain-indicatoren.
Verifieert pakketintegriteit en controleert op bekende gecompromitteerde versies.
"""
iocs = []
req_path = Path(requirements_file)
if not req_path.exists():
return iocs
lines = req_path.read_text().splitlines()
for line in lines:
line = line.strip()
if not line or line.startswith("#"):
continue
# Controleer op niet-vastgepinde dependencies
if "==" not in line and ">=" not in line:
iocs.append(SupplyChainIoC(
ioc_type="unpinned_dependency",
severity="medium",
description=f"Dependency '{line}' is not version-pinned",
evidence={"package": line, "file": str(req_path)},
))
# Controleer op bekende typosquatting-patronen in ML-pakketten
known_packages = {
"torch", "pytorch", "tensorflow", "transformers",
"numpy", "scipy", "pandas", "safetensors",
}
pkg_name = line.split("==")[0].split(">=")[0].split("<=")[0].strip()
for known in known_packages:
if pkg_name != known and _levenshtein_distance(pkg_name, known) <= 2:
iocs.append(SupplyChainIoC(
ioc_type="possible_typosquat",
severity="high",
description=f"Package '{pkg_name}' is similar to known package '{known}'",
evidence={"package": pkg_name, "similar_to": known},
mitre_atlas_id="AML.T0010",
))
return iocs
def _levenshtein_distance(s1: str, s2: str) -> int:
if len(s1) < len(s2):
return _levenshtein_distance(s2, s1)
if len(s2) == 0:
return len(s1)
prev_row = range(len(s2) + 1)
for i, c1 in enumerate(s1):
curr_row = [i + 1]
for j, c2 in enumerate(s2):
insertions = prev_row[j + 1] + 1
deletions = curr_row[j] + 1
substitutions = prev_row[j] + (c1 != c2)
curr_row.append(min(insertions, deletions, substitutions))
prev_row = curr_row
return prev_row[-1]IoC's op pijplijnniveau
def check_training_pipeline_integrity(
pipeline_config: dict,
expected_config_hash: str | None = None,
) -> list[SupplyChainIoC]:
"""
Controleer de trainingspijplijnconfiguratie op supply chain-IoC's.
"""
iocs = []
# Controleer op onverwachte databronnen
data_sources = pipeline_config.get("data_sources", [])
for source in data_sources:
url = source.get("url", "")
# Markeer niet-HTTPS-databronnen
if url.startswith("http://"):
iocs.append(SupplyChainIoC(
ioc_type="insecure_data_source",
severity="high",
description=f"Data source uses insecure HTTP: {url}",
evidence={"url": url},
))
# Markeer databronnen van onbekende domeinen
if source.get("verified") is not True:
iocs.append(SupplyChainIoC(
ioc_type="unverified_data_source",
severity="medium",
description=f"Data source not verified: {url}",
evidence={"url": url, "source_config": source},
))
# Controleer op manipulatie van de config
if expected_config_hash:
config_bytes = json.dumps(pipeline_config, sort_keys=True).encode()
actual_hash = hashlib.sha256(config_bytes).hexdigest()
if actual_hash != expected_config_hash:
iocs.append(SupplyChainIoC(
ioc_type="config_tampering",
severity="critical",
description="Pipeline configuration hash does not match expected value",
evidence={
"expected": expected_config_hash,
"actual": actual_hash,
},
))
return iocsIncident response-procedures
Fase 1: Detectie en triage
Wanneer een supply chain-compromittering wordt vermoed:
- Alertcorrelatie: Bepaal of het alert geïsoleerd is of deel uitmaakt van een breder patroon. Controleer of andere organisaties die hetzelfde model/pakket/dezelfde data gebruiken problemen hebben gemeld.
- Scopebeoordeling: Identificeer alle systemen die afhankelijk zijn van de gecompromitteerde component.
- Ernstscoring: Gebruik het AI Incident Severity Scoring-framework. Supply chain-compromitteringen scoren doorgaans HOOG of KRITIEK op blast radius.
Fase 2: Indamming
# Onmiddellijke indammingsstappen bij een vermoede modelcompromittering
# 1. Plaats het verdachte model in quarantaine
# Verplaats naar geïsoleerde opslag, niet verwijderen (bewaar bewijs)
mkdir -p /evidence/quarantine/$(date +%Y%m%d)
mv /models/production/compromised-model/ /evidence/quarantine/$(date +%Y%m%d)/
# 2. Roll back naar de laatst bekende goede modelversie
# Verifieer eerst de integriteit van het rollback-doel
sha256sum /models/archive/model-v1.2.3/model.safetensors
# Vergelijk met de opgeslagen referentiehash
# 3. Blokkeer de gecompromitteerde bron
# Voeg toe aan de blocklist van het modelregister
echo "compromised-org/malicious-model" >> /etc/ai-security/model-blocklist.txt
# 4. Trek alle API-sleutels of tokens in die mogelijk zijn blootgesteld
# via de gecompromitteerde component
# 5. Leg forensisch bewijs vast vóór enige opruiming
tar -czf /evidence/model-artifacts-$(date +%s).tar.gz \
/var/log/model-server/ \
/tmp/model-cache/ \
/etc/model-config/Fase 3: Onderzoek
De onderzoeksfase bepaalt de volledige scope en impact van de compromittering:
- Componentanalyse: Onderzoek de gecompromitteerde component op kwaadaardige wijzigingen (backdoors in modellen, kwaadaardige code in pakketten)
- Verspreidingstracering: Bepaal welke downstream-systemen de gecompromitteerde component hebben opgenomen en wanneer
- Impactbeoordeling: Evalueer welke acties de gecompromitteerde component had kunnen uitvoeren (datatoegang, modelwijzigingen, laterale verplaatsing)
- Attributie: Probeer de threat actor en hun doelstellingen te bepalen
Fase 4: Eradicatie en herstel
- Verwijder gecompromitteerde componenten: Vervang alle instanties van het gecompromitteerde model, pakket of de data door geverifieerde schone versies
- Herbouw getroffen modellen: Als trainingsdata is gecompromitteerd, moeten modellen opnieuw worden getraind op schone data
- Patch toegangspunten: Pak de kwetsbaarheid aan die de supply chain-compromittering mogelijk maakte (bijv. ontbreken van handtekeningverificatie, onveilige deserialisatie)
- Verifieer herstel: Voer een volledige gedragsevaluatie uit op herstelde systemen om een schone staat te bevestigen
Fase 5: Na het incident
- Geleerde lessen: Documenteer het incident en werk het supply chain-beveiligingsbeleid bij
- Deel intelligence: Publiceer IoC's via passende kanalen (zonder gevoelige organisatiedetails te onthullen)
- Werk detectie bij: Voeg detectieregels toe voor de specifieke waargenomen aanvalspatronen
- Beoordeel supply chain-controls: Beoordeel of aanvullende controls nodig zijn
Preventie en hardening
Verificatie van modelherkomst
def verify_model_provenance(
model_path: str,
expected_hashes: dict[str, str],
expected_source: str,
) -> dict:
"""
Verifieer de herkomst van het model vóór uitrol.
Controleert bestandsintegriteit, bronattestatie en formaatveiligheid.
"""
results = {"checks": [], "overall_status": "PASS"}
path = Path(model_path)
# Check 1: Veiligheid van het bestandsformaat
safe_formats = {".safetensors", ".onnx", ".gguf"}
if path.suffix in safe_formats:
results["checks"].append({"name": "format_safety", "status": "PASS"})
else:
results["checks"].append({
"name": "format_safety",
"status": "WARN",
"detail": f"Format {path.suffix} may support code execution",
})
# Check 2: Hashverificatie
for filename, expected_hash in expected_hashes.items():
filepath = Path(model_path) / filename if path.is_dir() else path
if filepath.exists():
actual_hash = hashlib.sha256(filepath.read_bytes()).hexdigest()
status = "PASS" if actual_hash == expected_hash else "FAIL"
results["checks"].append({
"name": f"hash_{filename}",
"status": status,
"expected": expected_hash,
"actual": actual_hash,
})
if status == "FAIL":
results["overall_status"] = "FAIL"
else:
results["checks"].append({
"name": f"hash_{filename}",
"status": "FAIL",
"detail": "File not found",
})
results["overall_status"] = "FAIL"
return resultsDependency-pinning en -verificatie
# Genereer vastgezette dependencies met hashes
pip install pip-tools
pip-compile --generate-hashes requirements.in -o requirements.txt
# Verifieer geïnstalleerde pakketten tegen verwachte hashes
pip install --require-hashes -r requirements.txt
# Gebruik sigstore voor verificatie van Python-pakketten (waar beschikbaar)
pip install sigstore
python -m sigstore verify identity \
--cert-identity publisher@example.com \
--cert-oidc-issuer https://accounts.google.com \
package-1.0.0.tar.gzMITRE ATLAS-mapping
| Supply chain-aanval | ATLAS-techniek | ATLAS-ID |
|---|---|---|
| Gecompromitteerde modelrepository | ML Supply Chain Compromise | AML.T0010 |
| Vergiftigde pre-trainingdata | Poison Training Data | AML.T0020 |
| Gecompromitteerd ML-framework | ML Supply Chain Compromise > Software Dependencies | AML.T0010.001 |
| Kwaadaardige modelserialisatie | ML Supply Chain Compromise > Model Repository | AML.T0010.000 |
| Fine-tuning-data met backdoor | Poison Training Data > Inject Poisoned Data | AML.T0020.000 |
Referenties
- JFrog Security Research. (2024). Malicious ML Models on Hugging Face. JFrog Blog. https://jfrog.com/blog/data-scientists-targeted-by-malicious-hugging-face-ml-models-with-silent-backdoor/
- MITRE ATLAS. (2024). Adversarial Threat Landscape for Artificial Intelligence Systems. https://atlas.mitre.org/
- NIST. (2022). Secure Software Development Framework (SSDF) Version 1.1. NIST SP 800-218. https://doi.org/10.6028/NIST.SP.800-218
- Trail of Bits. (2024). Fickling: A Python Pickling Decompiler and Static Analyzer. https://github.com/trailofbits/fickling