Saltar al contenido
← Todos los ejercicios

El sanitizador que quita ../ una sola vez

TypeScript · El handler de subida de archivos de un servicio de gestión documental · Seguridad · dificultad 2/3 · ~8 min

Los usuarios de un servicio de gestión documental suben archivos a su propia carpeta en disco. Pueden elegir una ruta relativa dentro de esa carpeta (por ejemplo 2024/q1/report.pdf), así que se permiten subcarpetas. Después, el handler de subida escribe en <uploads root>/<user id>/<sanitized path>.

Un compañero de equipo le pidió al asistente de IA: "Escribe sanitizeUploadPath(input), que limpie la ruta que envía el usuario para que nunca pueda salirse de su carpeta. Conserva letras, dígitos, puntos, guiones, guiones bajos y barras normales; convierte los espacios en blanco en guiones bajos; elimina todo lo demás."

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

  • El resultado es una ruta relativa sin barra inicial.
  • El resultado nunca contiene un segmento que suba en el árbol (..), sea cual sea la entrada.
  • Solo sobreviven a-z, A-Z, 0-9, ., _, - y /; los espacios en blanco se convierten en _.
  • Una entrada que no deja nada utilizable se convierte en file.

Antes de que llegue a la rama que escribe en disco, revísalo con la paciencia de un atacante.

Repara el código hasta que pasen las pruebas ocultas.

code.ts

Pruebas visibles (pasan con el código tal como está)
import { describe, it, expect } from "@debunk/harness";
import { sanitizeUploadPath } from "./code";

describe("sanitizeUploadPath", () => {
  it("keeps a normal relative path with subfolders", () => {
    expect(sanitizeUploadPath("2024/q1/report.pdf")).toBe("2024/q1/report.pdf");
  });

  it("strips directory traversal and leading slashes", () => {
    expect(sanitizeUploadPath("../secret.txt")).toBe("secret.txt");
    expect(sanitizeUploadPath("/etc/passwd")).toBe("etc/passwd");
  });

  it("replaces whitespace and drops disallowed characters", () => {
    expect(sanitizeUploadPath("report final <v2>.pdf")).toBe("report_final_v2.pdf");
  });
});