Saltar al contenido
← El bug de la semana

El bug de la semana · 19 de octubre de 2026

La redirección que se fía de una barra inicial

La redirección después del login en una API Express · Seguridad · dificultad 1/3

Después de un login correcto, la API redirige al usuario a la página que había pedido originalmente. El destino viene de un parámetro de query ?next=, así que lo controla el usuario. Un compañero de equipo le pidió al asistente de IA: "Escribe resolveRedirect(target, fallback) para nuestro endpoint de login. Permite solo redirecciones a páginas de nuestro propio sitio; cualquier otra cosa debe ir al fallback."

El código llegó con tests que pasan. El contrato que promete el docstring:

  • resolveRedirect(target, fallback) devuelve target cuando es una ruta local de este sitio, y fallback en caso contrario.
  • Un target ausente, vacío o formado solo por espacios en blanco usa el fallback.
  • Las URL absolutas hacia otros hosts (https://evil.example) nunca deben devolverse.
  • isLocalPath(url) es el predicado que hay detrás: true solo para rutas que el navegador resolverá en el origen actual.

Ten en cuenta cómo interpreta un navegador una cabecera Location: no solo busca http://. Revisa el código antes de que salga a producción.

Sin cuenta. Sin pistas. El reloj está corriendo.0 s

Haz clic en la línea (o las líneas) donde está el defecto, elige su categoría y envía. Con el teclado: flechas para moverte, Espacio para marcar. En este modo el código es de solo lectura.

1/**
2 * Resolves the post-login redirect target.
3 *
4 * Returns `target` when it points to a page on this site, otherwise
5 * `fallback`. Used by the login endpoint to honour `?next=`.
6 */
7export function resolveRedirect(
8 target: string | null | undefined,
9 fallback = "/dashboard",
10): string {
11 if (!target) return fallback;
12 const trimmed = target.trim();
13 if (trimmed.length === 0) return fallback;
14 if (!isLocalPath(trimmed)) return fallback;
15 return trimmed;
16}
17
18/**
19 * True when `url` is a path on the current origin, not a link to another host.
20 */
21export function isLocalPath(url: string): boolean {
22 const lower = url.toLowerCase();
23 if (lower.startsWith("http://") || lower.startsWith("https://")) return false;
24 if (lower.includes("://")) return false;
25 return url.startsWith("/");
26}
0 líneas marcadas