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
- TypeScript
- Cloudflare Workers
- D1 (SQLite)
- Turnstile
- JavaScript sin framework
- Enlaces
- portfolio-chatbot Código, rondas de ataques y documentación de seguridad
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
- Un chat flotante que solo habla del perfil público de Jordi, con la personalidad de Patu
- Captcha invisible (Turnstile) y un pase firmado de 30 minutos antes de llegar al modelo
- 20 mensajes por visitante y 100 al día en total: el plan gratuito nunca se agota
- Un señuelo en las instrucciones y un filtro que tapa cualquier respuesta que lo filtre
- Conversaciones guardadas 30 días, sin IP, para revisar qué se intenta con él
- 220 pruebas en el backend, 39 en el widget y 14 ataques que se repiten en cada ronda
Cómo funciona, en vídeo
Patu enseña cómo lo protegen, capa a capa.
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.
Pulsa una pieza del diagrama para ver qué hace y con qué se conecta.
- Widget
- JavaScript sin framework. Pinta todo con
textContent(una prueba prohíbeinnerHTML), 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
| Aspecto | Gratis (este bot) | De pago |
|---|---|---|
| Modelo | Llama 3.1 8B: pequeño, se le puede convencer de cambiar de papel y a veces rechaza preguntas normales | Claude (Anthropic) o GPT (OpenAI): entiende mejor, sigue mejor las reglas y se inventa menos |
| Capacidad | 10.000 neuronas al día de cuota gratuita; para no pasarse, el bot se corta a los 100 mensajes | Pagas por uso y escala con la demanda, con un tope de gasto que decides tú |
| Protección | Captcha y reglas propias | WAF gestionado con reglas antibots, moderación del proveedor y guardrails |
| Datos | SQLite (D1), 30 días, sin réplicas | PostgreSQL gestionado: copias automáticas, réplicas, cifrado y contrato de tratamiento de datos (RGPD) |
| Conocimiento | Un texto fijo dentro del código; cambiarlo es desplegar | RAG: los documentos de la empresa en una búsqueda vectorial, actualizables sin tocar el código |
| Vigilancia | Un panel local hecho a mano | Métricas, trazas y alertas cuando sube un ataque o el gasto |
| Garantías | Ninguna: sin SLA ni soporte | SLA, soporte y por contrato que tus datos no entrenan modelos |
Tres formas de montarlo
- Rápida: n8n como orquestador
-
- Widget
- n8n
- Claude o GPT
- PostgreSQL
- Avisos
Pulsa un paso para ver qué hace.
- 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
-
- Widget
- WAF
- Spring Boot
- Redis
- Claude
- PostgreSQL + pgvector
Pulsa un paso para ver qué hace.
- 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
-
- Widget
- CloudFront + WAF
- API Gateway
- Lambda
- Bedrock (Claude)
- RDS PostgreSQL
Pulsa un paso para ver qué hace.
- 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
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.