Extracción TAR segura con tarfile en Python

Publicado el: 26/10/2026
Tempo de leitura: 4 minutos
Carpeta de documentos protegidos que representa extracción TAR segura en Python

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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Dos monitores ejecutando código Python en paralelo
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    concurrent.interpreters: paralelismo con aislamiento

    Aprende concurrent.interpreters en Python para crear workers aislados, intercambiar datos y aprovechar paralelismo multinúcleo.

    Ler mais

    Tempo de leitura: 7 minutos
    25/10/2026
    Código Python con anotaciones de tipo en un portátil
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: lee anotaciones con seguridad

    Aprende annotationlib en Python para leer anotaciones como valores, cadenas o referencias futuras con seguridad.

    Ler mais

    Tempo de leitura: 6 minutos
    25/10/2026
    Icono de compresión de archivos ZIP
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ZipInfo.compress_level: controla la compresión ZIP

    Aprende ZipInfo.compress_level en Python para elegir compresión por archivo y equilibrar tamaño, CPU, latencia y compatibilidad.

    Ler mais

    Tempo de leitura: 5 minutos
    24/10/2026
    Código binario que representa bytearray.resize en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    bytearray.resize: redimensiona buffers con seguridad

    Aprende bytearray.resize en Python para ampliar y reducir búferes con límites, memoryview y compatibilidad.

    Ler mais

    Tempo de leitura: 5 minutos
    24/10/2026
    Código Python en un portátil para filtrar valores None
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    operator.is_none: filtra None sin perder falsy

    Aprende operator.is_none e is_not_none para filtrar None sin eliminar valores falsy válidos en Python.

    Ler mais

    Tempo de leitura: 4 minutos
    23/10/2026
    Código Python en un portátil para crear dataclasses dinámicas
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    make_dataclass decorator: transforma dataclasses

    Aprende a usar el parámetro decorator de make_dataclass para validar, registrar y transformar dataclasses dinámicas en Python.

    Ler mais

    Tempo de leitura: 6 minutos
    23/10/2026