Durante bastante tiempo pensé en la seguridad web como algo que venía después.
Primero haces que la web funcione. Luego que se vea bien. Luego que cargue rápido. Luego que se pueda posicionar. Y al final, si queda tiempo, miras seguridad.
Cada vez me parece peor orden.
No porque ahora crea que todo desarrollador frontend tenga que comportarse como un auditor de seguridad. No es mi caso. Estoy aprendiendo ciberseguridad y todavía tengo mucho por ordenar. Pero justo por eso me interesa explicarlo desde un punto más realista: qué debería empezar a mirar un desarrollador web antes de publicar algo.
Si una web va a estar publicada, esto forma parte del trabajo.
Mi forma de verlo ahora
Cuando reconstruí mi portfolio con Astro, dediqué bastante tiempo a rendimiento, SEO técnico, estructura de páginas, sitemap, datos estructurados y mantenimiento. Todo eso tenía una intención clara: que la web no fuera solo una tarjeta bonita, sino una base profesional.
Con la seguridad me pasa algo parecido.
No quiero aprender seguridad para llenar artículos con palabras técnicas. Me interesa porque cambia la forma de tomar decisiones:
- Qué datos acepto.
- Qué scripts cargo.
- Qué dependencias instalo.
- Qué guardo en el navegador.
- Qué expongo en producción.
- Qué puede hacer alguien si modifica una petición.
- Qué ocurre si mañana mi proyecto deja de ser solo una demo.
Ese cambio de mirada es importante.
Un proyecto personal puede empezar pequeño, pero si lo publicas ya está en internet. Y en internet no existe “solo lo van a usar mis amigos” como garantía técnica.
No hace falta saberlo todo para empezar
Una web segura no es una web imposible de atacar. Eso no existe.
Una web mejor planteada es la que evita errores conocidos, limita daños razonables y no deja decisiones sensibles en manos de la parte equivocada.
Para mí, el punto no está en saber nombrar todos los ataques. Está en hacer mejores preguntas mientras construyes.
Por ejemplo:
- ¿Este dato viene de una fuente confiable?
- ¿Estoy validando en el sitio correcto?
- ¿Estoy mostrando contenido de usuario como texto o como HTML?
- ¿Este usuario puede hacer esta acción o solo está logueado?
- ¿Esta dependencia merece entrar en el proyecto?
- ¿Estoy filtrando información en errores, logs o URLs?
- ¿Qué parte de esta web queda abierta al publicarla?
Eso ya cambia mucho.
No te convierte en experto, pero te saca de una forma muy cómoda de construir: probar solo el caso en el que todo sale bien.
El navegador no manda
Esta es una de las ideas que más me está cambiando la forma de programar.
Todo lo que vive en el navegador puede ser modificado. El HTML, el CSS, el JavaScript, los campos ocultos, los atributos required, los valores enviados, los IDs, las cookies accesibles desde JavaScript y las peticiones.
Tu interfaz es una propuesta de uso. No es una frontera de seguridad.
Si una regla es importante, no puede depender solo del frontend.
Ejemplos:
- Un campo
requiredmejora la experiencia, pero no protege el servidor. - Un botón deshabilitado no impide que alguien mande la petición.
- Un campo oculto no protege un precio, un rol o un descuento.
- Un ID válido no significa que el usuario tenga permiso sobre ese recurso.
- Una validación en JavaScript puede saltarse completamente.
El frontend ayuda al usuario. El backend decide si el sistema acepta algo.
Esta separación parece básica, pero se olvida mucho cuando estás centrado en que la interfaz funcione.
Seguridad es decidir qué puede entrar
Todo lo que llega desde fuera debería tratarse como no confiable.
No hablo solo de un formulario de contacto. También entran aquí parámetros de URL, JSON de una API, cookies, cabeceras, archivos subidos, datos de terceros y respuestas de servicios externos.
Validar no significa bloquear caracteres raros al azar. Significa definir qué esperas recibir.
Hay una diferencia importante entre dos tipos de validación:
- Validar la forma.
- Validar el significado.
Validar la forma es comprobar que un email parece un email, que una fecha tiene formato de fecha o que un número realmente es un número.
Validar el significado es comprobar si ese dato tiene sentido dentro del sistema.
Un número puede ser válido y aun así ser peligroso si representa una cantidad negativa. Un ID puede existir y aun así no pertenecer al usuario. Una fecha puede estar bien escrita y aun así quedar fuera de un rango permitido.
Esta segunda parte es la que suele separar una validación superficial de una validación útil.
También importa cómo sale la información
No basta con mirar lo que entra.
También importa cómo muestras después esos datos.
Si un usuario escribe texto y tu aplicación lo pinta como HTML, el navegador puede interpretarlo como instrucciones. Ahí aparece una de las familias de problemas más conocidas: XSS.
No hace falta convertir esto en una clase de explotación. La regla práctica es más sencilla:
No mezcles datos no confiables con código.
No es lo mismo mostrar un dato en:
- Texto HTML.
- Un atributo.
- Una URL.
- Un bloque JavaScript.
- Un email.
Cada contexto tiene sus reglas.
Por eso APIs como innerHTML no deberían usarse con contenido que viene de usuarios o fuentes externas sin entender muy bien qué estás haciendo.
Si quieres mostrar texto, trátalo como texto. Si quieres permitir HTML enriquecido, necesitas saneamiento real y una lista clara de lo permitido.
Login no significa permiso
Autenticación y autorización no son lo mismo.
Autenticación: saber quién es el usuario.
Autorización: saber qué puede hacer ese usuario.
Parece una distinción teórica hasta que empiezas a pensar en endpoints reales.
Que un usuario esté logueado no significa que pueda borrar cualquier proyecto, acceder a cualquier factura, modificar cualquier cuenta o consultar cualquier recurso.
El backend tiene que comprobar contexto:
- Quién hace la petición.
- Qué recurso quiere tocar.
- A quién pertenece ese recurso.
- Qué acción intenta hacer.
- Qué permisos tiene.
Ocultar un botón en la interfaz no es autorización.
Si el endpoint acepta la acción sin comprobar permisos, el problema sigue ahí aunque el usuario no vea el botón.
Las sesiones son una parte sensible del producto
Cuando una web tiene login, las sesiones pasan a ser una pieza crítica.
Aquí entran cookies, tokens, expiración, cierre de sesión, recuperación de contraseña, dispositivos compartidos y almacenamiento en navegador.
No todas las decisiones tienen una respuesta universal, pero algunas preguntas son obligatorias:
- ¿Esta cookie debería tener
HttpOnly? - ¿Debería enviarse solo por HTTPS con
Secure? - ¿Qué política
SameSitetiene sentido? - ¿Cuánto dura una sesión?
- ¿Dónde se guarda un token?
- ¿Qué pasa si hay XSS?
- ¿Qué pasa si una petición se dispara desde otro sitio?
Guardar tokens en localStorage puede parecer cómodo. Pero si hay XSS, JavaScript puede leerlos. Usar cookies HttpOnly reduce ese riesgo, aunque también obliga a pensar en CSRF y configuración de cookies.
No lo reduciría a “usa siempre una cosa”. Lo importante es no elegir por comodidad sin entender qué problema estás dejando abierto.
HTTPS es el suelo, no el techo
HTTPS es obligatorio en una web profesional.
Protege la comunicación entre navegador y servidor frente a lectura o manipulación en tránsito. Sin eso, todo lo demás empieza mal.
Pero HTTPS no arregla:
- Validaciones débiles.
- XSS.
- Control de acceso roto.
- Cookies mal configuradas.
- Dependencias vulnerables.
- Secretos subidos al repositorio.
- Paneles de administración expuestos.
- Errores que filtran información interna.
HTTPS evita una clase de problemas. No convierte una aplicación mal diseñada en segura.
Por eso lo veo como algo que debe estar, no como algo de lo que presumir.
Las cabeceras no son decoración
Cuando trabajé las cabeceras de mi web, una cosa me quedó bastante clara: muchas decisiones de seguridad no se ven en la interfaz, pero cambian cómo se comporta el navegador.
Cabeceras como Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options, Referrer-Policy o Permissions-Policy no hacen que una web sea segura por sí solas.
Pero ayudan.
Limitan recursos, refuerzan HTTPS, controlan información enviada a terceros y bloquean capacidades que la web no necesita.
Ya lo expliqué con más detalle en cabeceras de seguridad que debería conocer un desarrollador web. En la práctica, una web también debería decirle al navegador qué está permitido y qué no.
Instalar dependencias también es decidir seguridad
En frontend es muy fácil instalar una librería para resolver cualquier cosa.
No digo que esté mal. Muchas dependencias ahorran tiempo y están mantenidas por gente que sabe mucho. El problema es instalar sin pensar.
Cada dependencia añade código que no has escrito tú. Y a veces añade más dependencias por debajo.
Antes de instalar algo, merece la pena preguntarse:
- ¿Lo necesito de verdad?
- ¿Qué problema resuelve?
- ¿Cuándo se actualizó por última vez?
- ¿Cuánta cadena de dependencias trae?
- ¿Va a tocar una parte sensible?
- ¿Puedo resolverlo de forma simple sin meter otro paquete?
Reducir dependencias innecesarias no es solo una decisión de rendimiento. También es mantenimiento y seguridad.
Los errores también hablan
Un error público debería ayudar al usuario, no explicar cómo está construida la aplicación.
No tiene sentido mostrar trazas internas, nombres de tablas, rutas del servidor, mensajes SQL, tokens, variables de entorno o detalles de proveedores externos.
Eso debe ir a logs internos, y aun así con cuidado de no guardar secretos ni datos personales innecesarios.
Hay una diferencia entre:
No hemos podido procesar la solicitud.Y:
DatabaseError: users.password_hash failed on production-db-01El primer mensaje puede ser poco detallado, pero no regala información. El segundo ayuda más a quien quiere entender por dónde atacar.
Publicar una web también es seguridad
La seguridad no termina en el código.
También está en cómo publicas:
- Variables de entorno.
- Configuración del hosting.
- Permisos.
- CORS.
- Redirecciones.
- Caché.
- Archivos técnicos.
- Dependencias.
- Dominios.
- Paneles expuestos.
Una web puede tener buen código y mala configuración.
Por eso cada vez me parece más importante revisar producción como parte del trabajo, no como un trámite final.
No basta con que el build pase. Hay que mirar qué queda realmente expuesto.
Cosas que miraría antes de publicar
No lo planteo como auditoría completa. Es más bien lo mínimo que no querría saltarme si la web va a estar online.
- HTTPS funcionando en todas las páginas.
- Cabeceras básicas configuradas.
- Formularios y endpoints validados en servidor.
- Permisos comprobados fuera de la interfaz.
- Datos de usuario mostrados de forma segura.
- Cookies sensibles con configuración adecuada.
- Sin secretos en el repositorio.
- Dependencias justificadas y actualizadas.
- Errores públicos sin detalles internos.
- CORS limitado a lo necesario.
- Datos sensibles fuera de
localStoragesalvo decisión consciente. - Límites contra abuso en acciones expuestas.
No sirve para decir “esta web es segura”. Sí sirve para no publicar completamente a ciegas.
Por qué me interesa este tema
No quiero hablar de seguridad web para aparentar que sé más de lo que sé.
Me interesa porque me obliga a construir con más criterio.
Si estoy construyendo webs profesionales, no puedo mirar solo diseño, SEO o rendimiento. También tengo que pensar qué acepta la web, qué muestra, qué guarda, qué expone y qué podría hacer alguien si no sigue el camino previsto.
Esa es la asociación que me interesa construir con mi trabajo: no “sé todas las vulnerabilidades”, sino “no publico sin pensar qué estoy exponiendo”.
Para mí, la seguridad web empieza ahí.