Comprueba las rutas propuestas por IA antes de escribir con Python
En esta página
Un asistente de IA puede proponer guardar un informe como reports/summary.md. Antes de que una herramienta escriba nada, comprueba adónde conduce realmente ese nombre. Una ruta anidada puede atravesar un enlace simbólico hacia otra carpeta, aunque su escritura parezca inofensiva.
Este tutorial crea un comprobador de destinos de solo lectura y prueba nueve propuestas sintéticas. Complementa una vista previa de renombrados sencillos: aquí permitimos destinos anidados para informes, no solo nombres de archivo sin directorios. Usa Python 3.11 o posterior en Linux o macOS; no necesitas una cuenta de IA ni paquetes externos.
1. Define el contrato de los destinos
La aplicación, no el modelo, elige una carpeta privada de salida. Pide al asistente rutas relativas con letras ASCII minúsculas, dígitos, guiones bajos y guiones, separadores de carpeta con barra normal y extensión final .md. Limita cada ruta a 200 caracteres. Los directorios superiores deben existir; los nombres ocupados se rechazan.
Esto excluye deliberadamente espacios, letras acentuadas, rutas absolutas y ... MITRE describe salir de un directorio restringido mediante rutas procedentes de entradas externas como recorrido de rutas. Rechaza los nombres no admitidos en lugar de repararlos silenciosamente.
Si las propuestas llegan en JSON, valida primero el JSON y después pasa cada cadena de ruta a esta comprobación independiente.
2. Guarda el comprobador de solo lectura
Guarda esto como check_paths.py en un directorio de trabajo nuevo:
from pathlib import Path
import re
def checked_destination(root, raw):
if (
not isinstance(raw, str)
or len(raw) > 200
or not re.fullmatch(r"[a-z0-9_-]+(?:/[a-z0-9_-]+)*\.md", raw)
):
raise ValueError("format")
root = root.resolve(strict=True)
candidate = root / raw
try:
target = candidate.resolve(strict=False)
except (OSError, RuntimeError) as error:
raise ValueError("unresolvable") from error
if not target.is_relative_to(root):
raise ValueError("outside root")
if not target.parent.is_dir():
raise ValueError("missing parent")
if target.exists() or candidate.is_symlink():
raise ValueError("occupied")
return target
La lista de caracteres permitidos comprueba la escritura. La referencia de resolve() de Python explica que la resolución sigue enlaces simbólicos; strict=False permite un destino que aún no existe. Resuelve primero porque is_relative_to() por sí solo es léxico y no inspecciona enlaces.
No sustituyas la comprobación de pertenencia por str(target).startswith(str(root)): una carpeta hermana llamada output-backup comparte el prefijo output. La comprobación de ocupación también rechaza enlaces simbólicos finales rotos, que no apuntan a un archivo existente.
3. Crea datos de prueba desechables
Guarda esto como demo_paths.py junto al comprobador. Solo crea carpetas temporales de prueba, un archivo testigo y dos enlaces simbólicos. El enlace escape apunta a la carpeta hermana output-backup; broken.md apunta a un archivo inexistente.
from pathlib import Path
from tempfile import TemporaryDirectory
from check_paths import checked_destination
with TemporaryDirectory(prefix="ai-paths-") as directory:
base = Path(directory).resolve()
root, outside = base / "output", base / "output-backup"
root.mkdir()
outside.mkdir()
(root / "reports").mkdir()
(root / "existing.md").write_text("keep\n", encoding="utf-8")
(root / "escape").symlink_to(outside, target_is_directory=True)
(root / "broken.md").symlink_to(root / "gone.md")
before = sorted(str(p.relative_to(base)) for p in base.rglob("*"))
cases = [
("nested", "reports/summary.md", "ALLOW"),
("parent", "../outside.md", "BLOCK format"),
("absolute", str(outside / "note.md"), "BLOCK format"),
("symlink", "escape/leak.md", "BLOCK outside root"),
("existing", "existing.md", "BLOCK occupied"),
("extension", "reports/summary.txt", "BLOCK format"),
("backslash", r"reports\summary.md", "BLOCK format"),
("missing-parent", "new/summary.md", "BLOCK missing parent"),
("broken-link", "broken.md", "BLOCK occupied"),
]
for label, raw, expected in cases:
try:
target = checked_destination(root, raw)
assert target == root / "reports/summary.md"
result = "ALLOW"
except ValueError as error:
result = f"BLOCK {error}"
assert result == expected, (label, result)
print(f"{label}: {result}")
assert before == sorted(str(p.relative_to(base)) for p in base.rglob("*"))
assert (root / "existing.md").read_text(encoding="utf-8") == "keep\n"
print("PASS: 9 cases; no proposed files written")
Las aserciones comprueban cada decisión, comparan el inventario de directorios antes y después de la validación y confirman que el contenido del archivo testigo no cambia. El directorio temporal se elimina automáticamente.
4. Ejecuta y valida
Ejecuta normalmente, sin la opción -O de Python, que desactiva las aserciones:
python3 demo_paths.py
Salida esperada:
nested: ALLOW
parent: BLOCK format
absolute: BLOCK format
symlink: BLOCK outside root
existing: BLOCK occupied
extension: BLOCK format
backslash: BLOCK format
missing-parent: BLOCK missing parent
broken-link: BLOCK occupied
PASS: 9 cases; no proposed files written
Probado localmente con Python 3.11.4 en Linux: código de salida 0, stderr vacío y ningún archivo de prueba restante. Los casos negativos son pruebas satisfactorias del rechazo, no ejecuciones fallidas.
El caso del enlace simbólico es el más importante: su entrada cumple la regla de nombres, pero al resolverla sale de la raíz aprobada. Aceptar el caso anidado significa que ese destino cumple la política en el momento de la inspección. No certifica el contenido del informe ni autoriza ejecutar código escrito por el modelo.
5. Resuelve problemas y separa la ejecución
format: solicita un nombre relativo que cumpla las reglas. No elimines componentes de recorrido ni decodifiques el valor después de validarlo.outside root: inspecciona el directorio superior enlazado; no amplíes la raíz permitida para que la prueba pase.missing parent: deja que código de confianza de la aplicación prepare el subdirectorio previsto y vuelve a validar.occupied: elige otro nombre; no borres la entrada existente.- Falla la creación de enlaces simbólicos: usa un sistema de archivos local que los permita. Windows requiere pruebas específicas de plataforma.
Esta es una comprobación puntual, no un entorno aislado de archivos inmune a condiciones de carrera. Otro proceso podría reemplazar un directorio entre la validación y la escritura: una condición de carrera entre comprobación y uso. Un futuro escritor necesita permisos aislados, controles de apertura de archivos resistentes a estas carreras y un comportamiento que impida sobrescribir. Para propuestas por lotes, rechaza también destinos resueltos duplicados antes de escribir nada.
Idea práctica
Deja que la IA proponga destinos mientras el código de la aplicación controla la raíz y comprueba cada ruta. Combina esta vista previa con una lista de campos permitidos, separa la ejecución y añade un caso de prueba cada vez que aparezca un fallo nuevo.
Artículos relacionados
Revisa planes de renombrado de IA sin cambiar archivos
Comprueba las cifras de resúmenes de IA con una tabla de referencia
Comprueba las dependencias de tareas de IA antes de programarlas con Python
Compara versiones de prompts de IA con una prueba de regresión en Python
Comprueba las horas de calendario de IA antes de crear eventos
Rechaza JSON ambiguo de IA antes de automatizar con Python
Comprueba citas textuales de IA con fuentes locales y Python
Evalúa la clasificación de incidencias con una matriz de confusión
Evalúa probabilidades de IA con la puntuación de Brier en Python
Detecta texto repetido entre entrenamiento y prueba antes de evaluar una IA
Comprueba los marcadores de traducciones de IA con Python
Evita actualizaciones de IA obsoletas con un control de versión en SQLite
Últimos artículos
Evalúa campos extraídos por IA con precisión y recall de coincidencia exacta
Evita actualizaciones de IA obsoletas con un control de versión en SQLite
Comprueba las dependencias de tareas de IA antes de programarlas con Python
Revisa los cambios JSON de la IA con una lista de campos permitidos
Combina resultados RAG con fusión de rangos recíprocos
Comprueba las horas de calendario de IA antes de crear eventos
Comprueba citas textuales de IA con fuentes locales y Python
Comprueba las cifras de resúmenes de IA con una tabla de referencia
Elige umbrales de revisión de IA con cobertura y tasas de error
Comprueba los marcadores de traducciones de IA con Python
Compara versiones de prompts de IA con una prueba de regresión en Python