Skip to content

core.archive

← Module catalog

soul-sidesoulstack.filesfilesroot required

Extract archives (tar/tar.gz/tar.bz2/zip) into a directory.

core.archive extracts the archive path into the directory dest using the Go stdlib in-process — without an external tar/unzip and without subprocesses, which gives per-entry security control (zip-slip/zip-bomb/symlink) that backend utilities do not provide. format is optional — auto-detected from the path extension. Idempotent: the SHA-256 of the source archive is written to the marker <dest>/.soul-archive.sha256, and the same archive is not extracted again (changed=false). Soul-side.

Requirements

  • Rootrequired
  • Sidesoul-side
  • Collectionsoulstack.files
  • Categoryfiles
Capabilitiesfs_write_root

States

core.archive.extracted — The archive path is extracted into the directory dest (tar/tar.gz/tar.bz2/zip).

Changes when

The <dest>/.soul-archive.sha256 marker is missing or its hash ≠ the current archive's hash. The check is grounded on the archive hash — the integrity of the extracted files in dest is not verified.

Stays unchanged when

The marker is present and its hash matched the current archive.

Parameters

ParamTypeRequired / defaultDescription
pathstringrequiredPath to the source archive.
deststringrequiredExtraction directory.
formatstringoptionalFormat (tar|tar.gz|tar.bz2|zip); omitted — auto-detect by extension.
max_sizestringoptionalLimit on the total extracted size (byte count or N[KiB|MiB|GiB]); default 1GiB. Guards against zip-bomb.
max_entriesintegeroptionalLimit on the number of entries in the archive; default 100000. Guards against zip-bomb.
max_ratiointegeroptionalLimit on the ratio of extracted bytes to compressed bytes (compression ratio); default 100, 0 — disabled. Guards against a zip-bomb with a small compressed size.

Example — Extract a downloaded tarball; format auto-detected from .tar.gz

- name: Extract node_exporter tarball
module: core.archive.extracted
params:
path: /tmp/node_exporter-1.9.0.tar.gz
dest: /tmp/node_exporter-1.9.0
max_size: "500MiB"

Output

FieldTypeDescription
pathstring
deststring
sha256string
extractedtrue

Reference

Supported formats

format is optional — when absent it is detected from the path suffix; an unrecognized suffix → the step fails (cannot auto-detect format).

FormatExtensions (auto-detect)Note
tar.tar
tar.gz.tar.gz, .tgz
tar.bz2.tar.bz2, .tbz2extract only (bzip2 in the stdlib is decompress-only)
zip.zip

Notes

  • Extraction is in-process on the Go stdlib (archive/tar, archive/zip, compress/gzip, compress/bzip2) — without an external tar/unzip and without spawning subprocesses. No host extraction utilities are needed, and there is per-entry security control that backend utilities do not provide.
  • max_size accepts a bare number (bytes) or a number with a binary suffix KiB/MiB/GiB (suffix case does not matter). Decimal SI suffixes (KB/MB/GB) and fractions are not supported — an unrecognized suffix or garbage → an explicit invalid size error, not a silent drop of the tail.
  • The .soul-archive.sha256 marker lives inside dest — keep that in mind when other steps later compare the directory's contents. changed=true checks "the same archive" (the marker's hash), not "every file inside dest is present".
  • Unsupported entry types (hardlink, block/char devices, fifo, socket) → an explicit error (unsupported type), not a silent skip. Housekeeping PAX/GNU headers and sparse metadata are silently ignored.
  • dest is created (MkdirAll, mode 0755) if it does not exist.

Security & defaults

  • zip-slip / path-traversal — fail-fast: each entry's target path is built via filepath-securejoin relative to dest plus a lexical escape check; an entry with .. or an absolute path pointing outside → the whole step fails (entry escapes dest), the already-extracted files remain, and the marker is not written. Not a silent clamp — a write outside is never created.
  • zip-bomb — the limits max_size (default 1GiB, total extracted size), max_entries (default 100000, number of entries) and max_ratio (default 100, 0 — off; ratio of extracted bytes to compressed). Exceeding any → the step fails, naming the limit that was breached.
  • symlink — within-dest only: a symlink from the archive is created only if its target (resolved relative to the symlink's own directory) stays inside dest; an absolute target or one leading outside dest → the step fails. Writing "through" a symlinked directory from the same archive is fail-closed (securejoin resolves against the path already on disk) — address the target directory directly.
  • setuid / setgid / sticky are always masked off (mode is applied as entry.Mode() & 0o777) — an archive cannot smuggle in a setuid-root binary. owner/group are not taken from the archive — files get the owner of the soul agent process.
  • Checksum — for idempotency, not for source verification: the SHA-256 is computed over the archive already on disk ("is this the same archive as last time") and is not checked against a trusted reference hash/signature. For untrusted artifacts, verify the signature/hash in a separate step (register + failed_when:) before extraction, and extract into an isolated, unprivileged dest.
  • Privileges: the manifest declares only fs_write_root, not run_as_root and not exec_subprocess (no subprocesses are spawned); to extract into system paths the agent in practice runs as root — which makes the setuid masking and within-dest invariants all the more valuable.

See also