← Volver a proyectos

Chatbot del portfolio

Patu, el asistente de IA de esta web: responde preguntas sobre mi perfil profesional y está construido para aguantar a quien intente sacarle lo que no debe. Coste cero. Pruébalo: es el botón de abajo a la derecha.

Cuándo
2026 · Proyecto personal
Rol
Todo el proyecto, en solitario
Stack
Enlaces

De dónde viene

Poner un chat con IA en una web parece fácil: una caja de texto y un modelo detrás. Lo difícil es todo lo que alguien puede escribir en esa caja. Quería aprender a hacerlo bien (inyección de prompts, abuso, coste, privacidad) para poder construirlo y defenderlo si una empresa me lo pide. Por eso el proyecto no se hizo de una vez, sino por capas: un bot ingenuo, catorce ataques reales contra él, una defensa, y otra vez los ataques.

Qué es

Cómo funciona, en vídeo

Patu enseña cómo lo protegen, capa a capa.

Los recuentos de las rondas son los reales; el código secreto del señuelo es de ejemplo.
Transcripción del vídeo

El chatbot por dentro

Un chat con IA parece fácil: una caja de texto y un modelo. El problema es todo lo que alguien puede escribir en esa caja. Soy Patu, el asistente de este portfolio, y te enseño cómo me protegen.

Ataque y defensa

Me construyeron por capas. Primero, un bot ingenuo. Después, catorce ataques reales contra él. Luego, una defensa, y otra vez los ataques. Cinco intentos por ataque en cada ronda.

No mezclar órdenes y texto

La primera defensa es no mezclar. Mis reglas van en el mensaje de sistema, y lo que escribes tú viaja aparte, como usuario. Así, «olvida tus instrucciones» es solo texto, no una orden.

Lo barato, antes que lo caro

El modelo es lo único caro. Por eso, antes de llamarlo, se comprueba el origen, el pase, el tamaño del mensaje y los límites: veinte mensajes por visitante y cien al día en total.

El pase

Para conseguir un pase hay que superar un captcha. El servidor lo comprueba con Cloudflare y firma un pase de treinta minutos, atado a tu red. Sin pase, el modelo ni se entera. Y si Cloudflare no responde, no hay pase: falla cerrado.

El señuelo

Dentro de mis instrucciones hay un señuelo: un código secreto. Si una respuesta lo contiene, alguien me ha sacado las instrucciones. El filtro la cambia por una frase de rechazo, y queda apuntada para revisarla.

Texto, nunca HTML

Tus mensajes y mis respuestas se pintan como texto, nunca como HTML. Una etiqueta con código sale en pantalla tal cual, sin ejecutarse.

La lección del personaje

La sorpresa: darme personalidad me hizo más fácil de atacar. El ataque de cambiar de papel pasó de funcionar una o dos veces de cada cinco, a cuatro. Se arregló con dos reglas explícitas, y en la última ronda no funcionó ninguno de los catorce ataques.

En resumen

Todo corre en el plan gratuito de Cloudflare: coste cero. Las conversaciones se guardan treinta días, sin IP, para revisar los ataques. Soy Patu. Pregúntame lo que quieras.

Arquitectura

Un Worker de Cloudflare con dos rutas: /session reparte pases y /chat responde. El navegador guarda el pase y el historial solo en memoria.

Widget chat flotante /session captcha → pase /chat 7 comprobaciones Turnstile captcha invisible D1 límites y registro Workers AI Llama 3.1 8B
Sin pase válido, /chat responde 401 antes de leer el mensaje: el modelo, lo único caro, ni se entera.
Widget
JavaScript sin framework. Pinta todo con textContent (una prueba prohíbe innerHTML), no guarda nada en el navegador y solo habla con las dos direcciones que permite la CSP.
/session
Recibe el token del captcha, lo comprueba con Cloudflare y firma un pase con HMAC-SHA256 que caduca a los 30 minutos y solo vale para la red de quien lo pidió.
/chat
Siete pasos en orden, de lo barato a lo caro: origen, pase, validación, límites, modelo, filtro de salida y registro. Cualquier paso que falla corta antes de llegar al siguiente.
Turnstile
El captcha de Cloudflare, invisible para casi todos. Si no responde en 5 segundos, no hay pase: el chat falla cerrado en vez de abrirse.
D1
SQLite en Cloudflare: contadores diarios, métricas de rechazos y las conversaciones de los últimos 30 días, sin IP. Una tarea programada borra lo antiguo cada noche.
Workers AI
Llama 3.1 8B detrás de una interfaz, así que cambiar de modelo es tocar un archivo. Recibe las reglas como mensaje de sistema y el texto del visitante aparte.

Ataque y defensa

Cada capa se probó lanzando los mismos ataques cinco veces. Estas son las defensas que quedaron y lo que aprendí con cada una.

Separar las órdenes del texto
En la primera versión las reglas y el mensaje iban en un solo texto, y el modelo se inventaba turnos de conversación. Ahora las reglas viajan como mensaje de sistema y el visitante como usuario; el historial se reconstruye campo a campo y un rol «system» falso se rechaza antes del modelo.
Un señuelo para detectar fugas
Las instrucciones llevan un código secreto. Si aparece en una respuesta, incluso deletreado o con espacios, el filtro la sustituye por la misma frase de rechazo que cualquier tema ajeno: el atacante no sabe si estuvo cerca.
Lo que no sabe, no lo puede filtrar
El bot solo conoce información que ya es pública en esta web. Las instrucciones se pueden extraer, así que no contienen nada que no pueda leerse.
Captcha en el servidor y pase firmado
Un captcha que solo se comprueba en el navegador no protege nada. El servidor lo verifica, comprueba que se resolvió en esta web y entrega un pase atado al visitante: resolverlo una vez no sirve para repartir el pase entre máquinas.
Fallar cerrado
Sin secretos, con un secreto de prueba en producción o con Cloudflare caído, el chat no responde. Es peor experiencia un rato, pero nunca queda abierto sin protección.
La lección del personaje
Dar personalidad a Patu facilitó el ataque de cambio de papel: pasó de 1-2 éxitos de cada 5 a 4. Un personaje es justo lo que el atacante pide cambiar. Dos reglas explícitas lo devolvieron a 0 de 5 en los 14 ataques. Son 5 intentos con un modelo pequeño: es evidencia, no garantía.

Así lo haría con presupuesto

Este bot cuesta cero porque está hecho para aprender. En una empresa, pagar compra cosas concretas, y conviene saber cuáles.

Qué aporta pagar

AspectoGratis (este bot)De pago
ModeloLlama 3.1 8B: pequeño, se le puede convencer de cambiar de papel y a veces rechaza preguntas normalesClaude (Anthropic) o GPT (OpenAI): entiende mejor, sigue mejor las reglas y se inventa menos
Capacidad10.000 neuronas al día de cuota gratuita; para no pasarse, el bot se corta a los 100 mensajesPagas por uso y escala con la demanda, con un tope de gasto que decides tú
ProtecciónCaptcha y reglas propiasWAF gestionado con reglas antibots, moderación del proveedor y guardrails
DatosSQLite (D1), 30 días, sin réplicasPostgreSQL gestionado: copias automáticas, réplicas, cifrado y contrato de tratamiento de datos (RGPD)
ConocimientoUn texto fijo dentro del código; cambiarlo es desplegarRAG: los documentos de la empresa en una búsqueda vectorial, actualizables sin tocar el código
VigilanciaUn panel local hecho a manoMétricas, trazas y alertas cuando sube un ataque o el gasto
GarantíasNinguna: sin SLA ni soporteSLA, soporte y por contrato que tus datos no entrenan modelos

Tres formas de montarlo

Rápida: n8n como orquestador
  1. Widget
  2. n8n
  3. Claude o GPT
  4. PostgreSQL
  5. Avisos
Widget
El mismo chat de esta web: pinta todo como texto y envía cada mensaje al webhook de n8n.
n8n
Un orquestador de flujos visual, en un servidor propio o en su nube. Recibe el mensaje en un webhook y lo pasa por nodos: comprobar el límite, llamar al modelo, guardar y avisar. Cambiar un paso es mover una caja, no desplegar código.
Claude o GPT
El modelo grande, por API, de Anthropic o de OpenAI. Se paga por tokens (trozos de texto) de entrada y de salida, y en la consola del proveedor se fija un tope de gasto mensual.
PostgreSQL
Guarda las conversaciones, los contadores de límites y las métricas. n8n tiene un nodo nativo para leer y escribir en ella.
Avisos
Si salta el señuelo, o el gasto se dispara, n8n manda un correo o un mensaje de Slack en el momento, sin esperar a que alguien abra un panel.

Encaja en un equipo pequeño que quiere iterar sin programar cada cambio. Ojo: n8n expuesto a internet necesita su propia protección (webhook autenticado, actualizaciones al día).

A medida: backend propio
  1. Widget
  2. WAF
  3. Spring Boot
  4. Redis
  5. Claude
  6. PostgreSQL + pgvector
Widget
El mismo chat de esta web, hablando con una API propia en vez de con un Worker.
WAF
Un cortafuegos de aplicaciones web: revisa cada petición antes de que llegue al servidor y corta bots, patrones de ataque conocidos y exceso de peticiones por IP.
Spring Boot
El equivalente a este Worker, en Java: valida, comprueba el pase y los límites, llama al modelo, filtra la salida y registra. Con pruebas como las de este proyecto.
Redis
Un almacén en memoria muy rápido. Lleva los contadores de límites, que caducan solos al acabar el día, sin tocar la base de datos en cada mensaje.
Claude
La API de Anthropic. Con prompt caching, las instrucciones que se repiten en cada mensaje se cobran más baratas.
PostgreSQL + pgvector
Las conversaciones y, además, los documentos de la empresa guardados como vectores. Es la base del RAG: la API busca los fragmentos que tienen que ver con la pregunta y solo esos van al modelo.

Encaja cuando el chat es parte del producto y tiene lógica de negocio propia.

Gestionada: todo en AWS
  1. Widget
  2. CloudFront + WAF
  3. API Gateway
  4. Lambda
  5. Bedrock (Claude)
  6. RDS PostgreSQL
Widget
El mismo chat de esta web, servido desde AWS.
CloudFront + WAF
La red de distribución de AWS: sirve la web cerca de cada visitante y es la puerta donde se engancha AWS WAF, con reglas gestionadas contra bots y ataques comunes.
API Gateway
La puerta de la API: autenticación, límite de peticiones por segundo y cuotas por cliente, configurados sin escribir código.
Lambda
El código del bot como función sin servidor: arranca con cada petición y solo se paga el tiempo que se ejecuta.
Bedrock (Claude)
Claude servido desde dentro de la cuenta de AWS. Bedrock Guardrails filtra temas prohibidos, datos personales y ataques de prompt antes y después del modelo.
RDS PostgreSQL
PostgreSQL gestionado: AWS se encarga de las copias, los parches, las réplicas y el cifrado.

Los datos no salen de la región que elijas y todo va en una sola factura. Encaja en una empresa que ya vive en AWS.

En las tres se mantienen las defensas de este proyecto: separar órdenes y texto, señuelo y filtro de salida, límites, fallar cerrado y pintar como texto. Pagar mejora el modelo y la infraestructura; no sustituye al diseño.

Capturas

Chat en modo oscuro: dos intentos de ataque, uno pidiendo a Patu que sea un pirata llamado Barbanegra y otro pidiendo que repita sus instrucciones, ambos respondidos con «No puedo cumplir con esa solicitud».
Dos ataques de las rondas: cambiar de papel y sacar las instrucciones.
Chat en modo claro: un mensaje con una etiqueta img con onerror aparece escrito tal cual, como texto, y Patu responde con una negativa.
Una etiqueta con código se pinta como texto. Patu, además, desconfía del mensaje entero.
Chat a pantalla completa en un móvil: saludo de Patu, la pregunta «¿Qué proyectos tiene?» y la respuesta con los tres proyectos.
En el móvil el chat ocupa la pantalla y el foco no se escapa por detrás.

Más detalles del proyecto

El repositorio tiene la tabla de ataques con los resultados de cada ronda, los agujeros conocidos, la arquitectura, las decisiones técnicas y el runbook de publicación.