CiberPTY/insights ← Volver al sitio
AUTOMATION2026.04.02·12 min de lectura

Triage de alertas con LLMs sin filtrar datos sensibles

Patrón de redacción y enriquecimiento local antes de enviar contexto a un modelo. Compatible con n8n, Ollama y guardrails opensource.

El dilema del SOC moderno

Los analistas de tier 1 procesan entre 200 y 600 alertas al día. La mayoría son falsos positivos o variantes conocidas. Un LLM puede hacer triage inicial brillantemente — pero enviar logs crudos a una API externa expone PII, credenciales, hostnames internos, y a veces secretos en líneas de comando.

La solución no es no usar LLMs. Es construir una capa de redacción determinista antes del modelo, y validar con guardrails después.

Arquitectura de tres capas

[1] EVENT INGEST ↓ wazuh / suricata / falco → n8n webhook ↓ [2] REDACTION LAYER (local, determinista) ↓ regex + presidio + custom dict • IP internas → 10.x.x.x • Hostnames → host_${hash[:6]} • Usuarios → user_${hash[:6]} • Secrets → [REDACTED] ↓ [3] LLM TRIAGE (local Ollama o API) ↓ classify + suggest_action + confidence ↓ [4] GUARDRAIL ↓ verify_no_secrets_leak(response) verify_action_in_allowlist(response) ↓ → human review (Slack)

Capa 2: redacción local

Usamos presidio de Microsoft (open-source) más un diccionario custom para detectar entidades específicas de la organización. La clave: la redacción es biyectiva — guardamos el mapping en memoria por la duración del triage para poder rehidratar la respuesta.

from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine
import hashlib

analyzer = AnalyzerEngine()
anonymizer = AnonymizerEngine()

CUSTOM_PATTERNS = [
    (r"\b10\.\d+\.\d+\.\d+\b", "INTERNAL_IP"),
    (r"\b[a-z]+-prod-\d+\b", "PROD_HOST"),
    (r"AKIA[0-9A-Z]{16}", "AWS_KEY"),
]

def redact(text: str) -> tuple[str, dict]:
    mapping = {}
    for pattern, label in CUSTOM_PATTERNS:
        for match in re.finditer(pattern, text):
            token = f"{label}_{hashlib.sha256(match.group().encode()).hexdigest()[:6]}"
            mapping[token] = match.group()
            text = text.replace(match.group(), token)
    return text, mapping

Capa 3: el prompt

Mantenemos el prompt corto, estructurado, y forzamos JSON output. Para self-hosted, llama3.1:8b o mistral-nemo son suficientes para triage de tier 1.

SYSTEM: You are a SOC tier 1 analyst. Classify the alert.
Output strict JSON:
{
  "severity": "low|medium|high|critical",
  "category": "string",
  "false_positive_probability": 0.0-1.0,
  "next_action": "auto_close|enrich|escalate|isolate_host",
  "reasoning": "string (max 80 words)"
}

USER: ${redacted_alert}

Capa 4: guardrails

Antes de mostrar la respuesta a un humano, validamos que: (1) no tenga tokens redactados sin rehidratar accidentalmente filtrados, (2) la acción sugerida esté en una allowlist explícita, (3) la confianza esté calibrada contra ground truth histórico.

Cuidado Nunca permitas que el LLM ejecute acciones directamente. El output del modelo es una sugerencia. La acción ejecutiva siempre pasa por el humano o por un workflow determinista con allowlist estricta.

Resultados reales

El LLM no reemplaza al analista. Filtra el ruido para que el analista vea solo lo que importa.

¿Quieres implementar este patrón en tu SOC? Hacemos el diseño del workflow, redacción y guardrails sobre tu stack actual.

→ Solicitar diagnóstico