🛡️ fetch-sentinel: El cortafuegos local (CPU-Only) para blindar la ventana de contexto de tus agentes de IA frente a inyecciones indirectas de prompts. Internet es hostil por defecto. Tu agente ya no tiene por qué estar expuesto.
fetch-sentinel es un guardia estructural en el punto de entrada cuando un agente autonomo hace fetch de contenido web arbitrario. Su trabajo es decidir, antes de que el contenido externo entre al contexto del LLM, que partes son dato y que partes son instruccion.
Este post NO presenta fetch-sentinel como un producto listo para produccion. Lo presenta como un repositorio alfa con cuatro capas obligatorias implementadas y verificadas localmente con 161 tests, y con dos KI conocidos abiertos (KI-10, KI-11) que una auditoria independiente identifico en la segunda ronda de revision y que requieren un refactor mayor para cerrarse.
El problema: inyeccion indirecta de prompts via contenido fetched
Cuando un agente LLM navega la web por su cuenta, el contenido fetched es input no confiable. Un atacante puede inyectar instrucciones en paginas que el agente va a leer como si fueran parte del prompt del sistema:
- Texto invisible en comentarios HTML, atributos alt, metadata.
- Codepoints Unicode ofuscados (TAG block, ZWSP, BIDI override) que sobreviven a la mayoria de los pipelines de sanitizacion.
- Manipulacion semantica sin instruccion explicita: propaganda o "hechos" seleccionados empaquetados como resumen.
- Exfiltracion en cadena: si el agente tiene acceso a shell, email o API keys, una inyeccion exitosa escala a accion real no autorizada.
Los firewalls semanticos no resuelven esto (intentar defenderse contra manipulacion semantica convierte el componente en algo que no funciona). Lo que resuelve el problema es defender el punto de entrada.
Que hace fetch-sentinel
Cuatro capas obligatorias:
| Capa | Modulo | Que hace |
|---|---|---|
| 1 - Fetch aislado | core/fetcher.py |
Extraccion readability sobre html.parser (stdlib), descarta <script>, <style>, <iframe>, <noscript>, <object>, <embed>, <template>, comentarios HTML, atributos. Solo http/https, allowlist DNS opcional, timeout, max_bytes. |
| 2 - Deteccion estructural | core/structural_guard.py |
Reutiliza mcp-tool-sanitizer para eliminar TAG block, ZWSP y BIDI override. Envuelve el texto en delimitadores <fetched_content> que el LLM downstream debe tratar como dato. Score de sospecha heuristico. |
| 3 - Separacion de privilegios | core/sandbox.py |
El proceso corre sin shell, sin escritura fuera de ~/.local/share/fetch-sentinel/ y ~/.config/fetch-sentinel/. Filtra OPENAI_API_KEY, ATW_WITNESS_KEY del agente principal. |
| 4.1 - Trazabilidad firmada | core/witness_client.py |
Cada fetch firma un evento con clave HMAC dedicada de fetch-sentinel (no del agente principal). El payload fetched NUNCA se embebe, solo su SHA-256. Append-only JSONL 0o600. |
| 4.2 - Citas ancladas | core/citation_tracer.py |
Anclaje substring + SHA-256 entre resumen del agente y texto fuente. |
Stack: stdlib + mcp-tool-sanitizer + agent-trace-witness. Cero dependencias nativas.
Resultados de verificacion local
-
161 tests verde (138 base + 23 de cobertura extendida).
pytest tests/→ 161 passed in 0.6s. -
ruff check .→ All checks passed! -
python -m compileall core/ main.py→ limpio. - LICENSE verbatim de gnu.org/licenses/agpl-3.0.txt, 661 lineas, hash SHA-256
0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0. - Cabeceras SPDX en todos los archivos
.py(17 archivos).
KI abiertos: lo que fetch-sentinel NO cierra
Una segunda ronda de auditoria independiente (Claude, revision en clon limpio) identifico dos huecos estructurales que el fix inicial dejo pasar. Ambos siguen abiertos en HEAD y son bloqueantes para considerar el repo "estable":
-
KI-10 (DNS rebinding / TOCTOU en pinning de IP) —
_resolve_and_validate_blocked()valida una IP resolviendo DNS, perourllib.request.urlopenresuelve DNS por su cuenta. Un servidor DNS controlado por el atacante puede devolver IP publica benigna en la primera consulta y 127.0.0.1 en la segunda. Fix planeado (T47): refactor ahttp.client.HTTPConnectioncon pinning de IP explicito. -
KI-11 (redirect TOCTOU) — el handler de redirects de
urllibsigue la cadena recursivamente dentro deopener.open()antes de devolver control. La validacion post-redirect de IP llega despues de que la conexion al destino ya ocurrio. Fix planeado (T48): loop manual de redirects conmax_redirects=5y validacion antes de cada hop.
Ademas, KI declarados pero aceptados como limitacion documentada:
-
KI-1 (homoglifos) — mcp-tool-sanitizer Fase 1 no detecta homoglifos (cirilico, etc.). Depende de PR upstream de Fase 2. Caso de regresion esperada con test
_KNOWN_LIMITATIONen el corpus de fuzzing. - KI-7 (SSRF) — mitigado parcialmente en HEAD. Bloquea IPs loopback, link-local, private, unspecified, multicast, reserved. Falla cerrado por defecto.
-
KI-8 (delimitador auto-cierre) — el cuerpo entre
<fetched_content>y</fetched_content>se neutraliza contra las secuencias literales que el payload podria contener. Incluye tolerar espacios y tabs.
Detalles completos en sdd/KNOWN_ISSUES.md.
Lo que aprendimos en dos rondas de auditoria
El repo atraveso dos rondas de auditoria independiente en clon limpio. La primera ronda encontro tres KI que el spike de viabilidad no habia detectado (KI-7, KI-8, KI-9). La segunda, sobre el commit que pretendia cerrarlos, encontro cuatro mas (KI-10 a KI-13), dos de ellos revelando que el fix de KI-7 era TOCTOU (KI-10) y el de KI-8 introducia una regresion de integridad en el hash (KI-12).
El patron es consistente: los tests en verde no garantizan correccion. Los bugs que el auditor cazo estaban en el codigo de borde — paths que los tests no ejercian o cuyo mock no reproducia la condicion adversaria. Las notas red-team del verify_implementation.md documentan que el auditor verificaba explicitamente las suposiciones de cobertura de cada test.
La leccion operativa: la verificacion independiente de clon fresco es irrenunciable, no un nice-to-have. El self-report del agente sobre sus propios tests es unreliable por diseño (MEMORY.md del proyecto, regla NO-ASUNCIONES EN OPERACIONES).
Como reproducir
git clone https://github.com/amurlaniakea/fetch-sentinel
cd fetch-sentinel
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pip install -e /path/to/mcp-tool-sanitizer
.venv/bin/pip install -e /path/to/agent-trace-witness
.venv/bin/python -m pytest tests/
.venv/bin/python -m ruff check .
Nota: las dependencias runtime se instalan desde el path editable local hasta que se publique la primera version estable. Esto es por diseño — KI-10 y KI-11 requieren refactor de core/fetcher.py antes de cualquier distribucion.
Links
- Repo: https://github.com/amurlaniakea/fetch-sentinel
- Constitucion (contrato no negociable): sdd/constitution.md
- Known Issues: sdd/KNOWN_ISSUES.md (KI-1 a KI-13 con reproducciones literales)
- Verificacion del proyecto: sdd/verify_implementation.md
- Dependencias: mcp-tool-sanitizer, agent-trace-witness
Licencia: AGPL-3.0-or-later
Top comments (0)