Cómo pensar en seguridad
Una funcionalidad puede funcionar perfectamente y aun así no ser segura. Imagina una ruta de pedidos muy simple:
app.get('/orders/:id', async (req, res) => {
const order = await db.orders.findById(req.params.id)
res.json(order)
})La ruta lee un id, busca un pedido y lo devuelve. Si la pruebas con tu propio pedido, se ve bien. Ahora cambia el id en la URL. Si el servidor nunca verifica quién es el dueño de ese pedido, un cliente que inició sesión puede leer el recibo de otro cliente.
Esa es la mentalidad de seguridad: no basta con preguntarte si el código funciona. Pregúntate qué podría hacer alguien con él.
La seguridad plantea una segunda pregunta: ¿qué pasa cuando alguien la usa de una forma que no habíamos previsto?
Detecta el límite
Un límite de confianza es la línea que separa el código que controlas de algo que puede enviarte un valor que tú no elegiste.
El navegador está del otro lado de esa línea. Un usuario puede cambiar la URL, editar un campo oculto de un formulario, repetir un parámetro de consulta, eliminar una cookie, o saltarse tu front end y enviar una solicitud a mano.
Eso hace del navegador el lugar equivocado para tomar decisiones que te importan.
Las validaciones del navegador son útiles, pero no son el control
La validación del lado del cliente ayuda a las personas a llenar formularios. Detecta errores de escritura temprano y da una mejor experiencia.
El servidor igual tiene que verificar el valor de nuevo, porque el servidor es la parte que protege los datos.
Aquí está la misma ruta de pedidos con la decisión faltante hecha explícita:
app.get('/orders/:id', async (req, res) => {
const order = await db.orders.findById(req.params.id)
if (!order || order.userId !== req.user.id) {
return res.status(404).json({ error: 'Order not found' })
}
res.json(order)
})La línea importante es la comparación entre order.userId y req.user.id. El servidor está diciendo: este pedido puede existir, pero también tiene que pertenecer a la persona que lo está pidiendo.
Una solicitud se permite cuando el servidor puede comprobar la decisión por sí mismo.
Coloca las verificaciones importantes en el servidor. Ahí es donde tu app puede comparar la solicitud con los datos en los que ya confía.
Nombra lo que podría fallar
Una vez que detectas una decisión riesgosa, ponle nombre a la posible falla:
| Pregunta | Palabra de seguridad | Ejemplo |
|---|---|---|
| ¿Quién puede leer esto? | Confidencialidad | Un cliente lee el pedido de otro cliente. |
| ¿Quién puede cambiar esto? | Integridad | Un cliente cambia el precio antes de pagar. |
| ¿La gente sigue pudiendo usar esto? | Disponibilidad | Una sola solicitud hace que la app se ponga lenta para todos. |
En conjunto, esto se llama la tríada CIA: confidencialidad, integridad y disponibilidad.
Un recibo filtrado es un problema de confidencialidad. Un campo de precio oculto es un problema de integridad. Una ruta de búsqueda que le permite a una sola solicitud pedir un millón de registros es un problema de disponibilidad.
Algunos errores tocan más de una propiedad a la vez. Si un cliente puede editar la dirección de entrega de otro cliente, puede tanto verla como cambiarla.
Esas preguntas convierten una preocupación vaga en algo que puedes explicar. Un reporte de error se vuelve más claro cuando puedes nombrar el daño.
Piensa desde el otro lado
La frase "pensar como un atacante" puede sonar dramática. En el desarrollo del día a día, es más tranquila. Elige un dato de entrada y pregúntate:
- ¿Qué pasa si este valor falta?
- ¿Qué pasa si es mucho más largo de lo esperado?
- ¿Qué pasa si es del tipo equivocado?
- ¿Qué pasa si le pertenece a otro usuario?
- ¿Qué pasa si la misma solicitud se envía miles de veces?
Toma como ejemplo un formulario de perfil. La página podría mostrar un campo de texto para displayName, un userId oculto y un botón de enviar. El camino previsto es simple: la persona que inició sesión actualiza su propio nombre.
El camino de la seguridad plantea otras preguntas. ¿Por qué está userId en el formulario en primer lugar? ¿Qué pasa si apunta a otra persona? ¿El servidor usa la sesión iniciada, o confía en el campo enviado?
Oculto no significa confiable
Un campo oculto de un formulario está oculto de la página, no de la persona que envía la solicitud.
Si el valor importa, derívalo en el servidor a partir de la sesión o de la base de datos. No le pidas al navegador que te devuelva una decisión que el servidor ya conoce.
El mismo hábito funciona fuera de los formularios. Un archivo subido afirma tener un nombre de archivo y un tipo de contenido. Un token afirma una identidad. Un webhook afirma que ocurrió un evento. Verifica la afirmación en el punto donde tu código está a punto de depender de ella.
Eso es pensar en seguridad, en una forma pequeña y práctica.
Hacia dónde va esto ahora
Esta mentalidad es la preparación para la siguiente parte del curso.
El modelado de amenazas se pregunta qué podría salir mal antes de que una funcionalidad salga a producción. OWASP te da nombres compartidos para los errores en aplicaciones reales. La clasificación de errores te ayuda a decidir qué arreglar primero.
Mantén el ciclo simple:
- Encuentra el límite.
- Nombra lo que podría fallar.
- Verifica la afirmación en el servidor.

