objectif
Extraire un artefact sans risque de “path traversal” (../, chemins absolus), sans écraser des permissions inattendues et sans suivre des symlinks malveillants.
code minimal
# extraction sûre: valide les chemins, extrait en temp, synchronise vers dest
safe_tar_extract() {
local archive="$1" dest="$2"
[ -r "$archive" ] || { echo "archive introuvable: $archive" >&2; return 1; }
[ -d "$dest" ] || mkdir -p "$dest"
# 1) liste et validation des chemins
mapfile -t entries < <(tar -tf "$archive")
for p in "${entries[@]}"; do
# interdits: absolus, traversal, backslashes Windows, chemins vides
[[ "$p" =~ ^/ ]] && { echo "chemin absolu interdit: $p" >&2; return 2; }
[[ "$p" == "" ]] && { echo "entree vide" >&2; return 2; }
[[ "$p" == *".."* ]] && { echo "traversal detecte: $p" >&2; return 2; }
[[ "$p" == *$'\r'* ]] && { echo "CR dans le nom: $p" >&2; return 2; }
[[ "$p" == *'\'* ]] && { echo "backslash interdit: $p" >&2; return 2; }
done
# 2) extraire dans un dossier temporaire local
local parent tmp
parent="$(cd "$dest/.." && pwd)"
tmp="$(mktemp -d -p "$parent" .untar.XXXXXX)"
# options: pas d'owner/permissions, retard des perms dir (GNU tar), pas d'overwrite dangereux
tar -xpf "$archive" -C "$tmp" --no-same-owner --no-same-permissions --delay-directory-restore
# 3) sync vers dest en neutralisant les symlinks sortants
# -a conserve les liens, --safe-links interdit les liens pointant hors source
rsync -a --delete --safe-links "$tmp"/ "$dest"/
# 4) cleanup
rm -rf "$tmp"
}
# usage:
# safe_tar_extract app.tar.gz /srv/data.pm/current
utilisation
# déployer un build de data.pm
artefact="site-data.pm.tar.zst"
dest="/srv/data.pm/releases/2025-08-15_1200"
mkdir -p "$dest"
# décompression en flux -> tar (zstd)
zstd -dc "$artefact" | tar -xpf - -C "$dest" --no-same-owner --no-same-permissions --delay-directory-restore
# validation supplémentaire (aucun chemin absolu/..)
zstd -dc "$artefact" | tar -tf - | awk '
/^\/|(^|\/)\.\.(\/|$)/ { print "chemin suspect: " $0; bad=1 }
END { exit bad ? 1 : 0 }
'
# alternative: via safe_tar_extract (plus strict, rsync --safe-links)
safe_tar_extract "$artefact" "$dest"
# déployer en "current" via symlink atomique
ln -sfn "$dest" /srv/data.pm/current.new && mv -f /srv/data.pm/current.new /srv/data.pm/current
variante(s) utile(s)
# extraire en supprimant le premier niveau (strip-components=1) dans un temp, puis rsync
tmp="$(mktemp -d -p /srv/data.pm .untar.XXXXXX)"
tar -xpf app.tar.gz -C "$tmp" --strip-components=1 --no-same-owner --no-same-permissions --delay-directory-restore
rsync -a --delete --safe-links "$tmp"/ /srv/data.pm/current/
rm -rf "$tmp"
# refuser les fichiers spéciaux (devices, fifos) en filtrant la liste
tar -tvf app.tar.gz | awk '
/^[cbp]/ { print "special file refuse: " $0; bad=1 }
END { exit bad ? 1 : 0 }
' && tar -xpf app.tar.gz -C /srv/data.pm/current --no-same-owner --no-same-permissions
# macOS (bsdtar): options équivalentes sans --delay-directory-restore
bsdtar -xpf app.tar.gz -C /srv/data.pm/current --uid 0 --gname wheel >/dev/null 2>&1 || true
# (bsdtar ignore par défaut les / initiaux; conservez la validation via tar -tf | awk)
# empêcher l’écrasement de fichiers existants (mode conservateur)
tar -xkpf app.tar.gz -C /srv/data.pm/current --no-same-owner --no-same-permissions
notes
- validez toujours les chemins listés par
tar -tfavant extraction: refusez absolus (/^/) et traversals (../). - extraire dans un répertoire temporaire puis synchroniser avec
rsync --safe-linksneutralise les symlinks qui sortent de l’arborescence. - utilisez
--no-same-owneret--no-same-permissionspour ne pas appliquer des propriétaires/permissions inattendus. --delay-directory-restore(GNU tar) applique les permissions répertoires en fin d’extraction, limitant certains contournements.- combinez avec une bascule de symlink atomique vers
currentpour un déploiement sans interruption.