Los archivos TAR son frecuentes en copias de seguridad, paquetes de software, procesos de datos y despliegues. El módulo estándar tarfile de Python permite crear, leer y extraer archivos .tar, .tar.gz, .tar.bz2 y .tar.xz sin instalar dependencias. Sin embargo, extraer contenido recibido de terceros requiere precaución. Un archivo malicioso puede intentar escribir fuera de la carpeta elegida, crear enlaces inesperados, reemplazar archivos o expandirse hasta consumir demasiado espacio. En esta guía aprenderás a usar filtros de extracción, inspeccionar miembros, aplicar límites y construir una función segura y reutilizable.
Por qué un TAR puede ser peligroso
Un miembro TAR almacena más que los bytes de un archivo. También puede incluir nombre, permisos, propietario, grupo, enlaces simbólicos, enlaces físicos y tipos especiales. Si el programa confía en todos esos datos, un nombre como ../../config.py puede intentar salir del directorio de destino. Las rutas absolutas y los enlaces que apuntan al exterior producen riesgos similares.
La política correcta consiste en considerar no confiable cualquier archivo externo. Esto incluye cargas de usuarios autenticados, adjuntos, archivos descargados de proveedores y paquetes producidos por otros servicios internos. La validación debe ocurrir antes de escribir el contenido.
Usa filtros de extracción
Las versiones modernas de Python aceptan el argumento filter en extract() y extractall(). El filtro data suele ser la mejor opción para datos normales, porque reduce metadatos peligrosos y bloquea casos inseguros. El filtro tar conserva más características del formato. El comportamiento permisivo fully_trusted debe reservarse para archivos creados y controlados completamente por tu propio sistema.
from pathlib import Path
import tarfile
archivo = Path("datos.tar.gz")
destino = Path("extraidos")
destino.mkdir(parents=True, exist_ok=True)
with tarfile.open(archivo, "r:*") as tar:
tar.extractall(destino, filter="data")
El modo r:* detecta automáticamente la compresión. El filtro data funciona bien con CSV, JSON, imágenes, documentos y otros recursos que no necesitan conservar propietarios, dispositivos o enlaces especiales.
Inspecciona los miembros antes de extraer
El método getmembers() devuelve objetos TarInfo con nombre, tamaño, tipo y metadatos. Esta inspección permite rechazar el archivo antes de que se escriba cualquier elemento. Puedes limitar el número de miembros, el tamaño total descomprimido, las extensiones permitidas y los tipos aceptados.
MAX_ARCHIVOS = 500
MAX_TAMANO = 100 * 1024 * 1024
PERMITIDAS = {".csv", ".json", ".txt", ".png", ".jpg"}
with tarfile.open("datos.tar.gz", "r:*") as tar:
miembros = tar.getmembers()
if len(miembros) > MAX_ARCHIVOS:
raise ValueError("El TAR contiene demasiados elementos")
total = sum(m.size for m in miembros if m.isfile())
if total > MAX_TAMANO:
raise ValueError("El contenido supera el límite")
for miembro in miembros:
if miembro.isfile() and Path(miembro.name).suffix.lower() not in PERMITIDAS:
raise ValueError(f"Extensión bloqueada: {miembro.name}")
tar.extractall("extraidos", filter="data")
El límite importante es el tamaño descomprimido, no únicamente el tamaño del archivo subido. Una compresión elevada puede ocultar una salida enorme y agotar disco, cuota o tiempo de procesamiento.
Verifica la ruta final
Para reglas adicionales o compatibilidad con entornos antiguos, resuelve cada ruta contra la raíz de destino y confirma que el resultado siga dentro de esa raíz.
def ruta_segura(raiz: Path, nombre: str) -> bool:
raiz = raiz.resolve()
candidato = (raiz / nombre).resolve()
try:
candidato.relative_to(raiz)
return True
except ValueError:
return False
No basta con buscar la cadena ... Las rutas absolutas, separadores específicos del sistema, normalización y enlaces pueden superar verificaciones superficiales. Comparar rutas resueltas expresa mejor la política y facilita las pruebas.
Bloquea enlaces y tipos especiales
La mayoría de las aplicaciones solo necesita directorios y archivos normales. TarInfo ofrece métodos como isfile(), isdir(), issym() e islnk(). Una política estricta puede rechazar enlaces simbólicos, enlaces físicos, dispositivos, tuberías y otros miembros especiales.
for miembro in tar.getmembers():
if not (miembro.isfile() or miembro.isdir()):
raise ValueError(f"Tipo no permitido: {miembro.name}")
Esta decisión reduce la superficie de ataque y hace predecible el directorio resultante. Si realmente necesitas enlaces, valida de forma explícita tanto el enlace como su destino.
Crea una función reutilizable
from pathlib import Path
import tarfile
def extraer_tar_seguro(origen, destino, limite=200_000_000):
origen = Path(origen)
destino = Path(destino)
destino.mkdir(parents=True, exist_ok=True)
with tarfile.open(origen, "r:*") as tar:
miembros = tar.getmembers()
total = sum(m.size for m in miembros if m.isfile())
if total > limite:
raise ValueError("Se superó el límite de extracción")
for miembro in miembros:
if not ruta_segura(destino, miembro.name):
raise ValueError(f"Ruta insegura: {miembro.name}")
if not (miembro.isfile() or miembro.isdir()):
raise ValueError(f"Miembro especial bloqueado: {miembro.name}")
tar.extractall(destino, members=miembros, filter="data")
En una aplicación web, ejecuta esta función dentro de un directorio temporal exclusivo. Consulta la guía de tempfile en Python para aislar el trabajo. Para gestionar rutas, revisa pathlib en Python. También son útiles los artículos sobre usar with con archivos y descomprimir ZIP en Python.
Gestiona errores y resultados parciales
Captura excepciones como tarfile.ReadError, tarfile.CompressionError, otras variantes de TarError y OSError. Si el proceso falla, elimina el directorio incompleto. En flujos importantes, extrae primero en una zona temporal, valida todo el árbol y mueve únicamente los archivos aprobados a su ubicación definitiva.
try:
extraer_tar_seguro("subida.tar.gz", "temporal")
except (tarfile.TarError, OSError, ValueError) as error:
print(f"Error de extracción: {error}")
Registra el identificador del archivo, el miembro bloqueado y la regla que falló. Evita mostrar rutas internas sensibles al usuario final.
Crear archivos TAR correctamente
Para generar un paquete, abre el archivo con w, w:gz, w:bz2 o w:xz. Usa arcname para controlar la ruta interna y no guardar ubicaciones absolutas del sistema.
with tarfile.open("respaldo.tar.gz", "w:gz") as tar:
tar.add("informes", arcname="informes")
Para completar el flujo, combina esta técnica con la guía de copias de seguridad automáticas en Python.
Lista final de seguridad
Usa filter="data", valida rutas resueltas, limita cantidad y tamaño, rechaza tipos innecesarios, extrae en una carpeta aislada, limpia salidas parciales, registra fallos y mantén Python actualizado. Consulta la documentación oficial de tarfile y la guía de OWASP sobre path traversal. Con estas capas, tarfile es una herramienta práctica para importaciones, respaldos y automatizaciones sin confiar ciegamente en el contenido recibido.







