core.archive
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
- Side
soul-side - Collection
soulstack.files - Category
files
fs_write_rootStates
core.archive.extracted — The archive path is extracted into the directory dest (tar/tar.gz/tar.bz2/zip).
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.
The marker is present and its hash matched the current archive.
Parameters
| Param | Type | Required / default | Description |
|---|---|---|---|
path | string | required | Path to the source archive. |
dest | string | required | Extraction directory. |
format | string | optional | Format (tar|tar.gz|tar.bz2|zip); omitted — auto-detect by extension. |
max_size | string | optional | Limit on the total extracted size (byte count or N[KiB|MiB|GiB]); default 1GiB. Guards against zip-bomb. |
max_entries | integer | optional | Limit on the number of entries in the archive; default 100000. Guards against zip-bomb. |
max_ratio | integer | optional | Limit 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
| Field | Type | Description |
|---|---|---|
path | string | |
dest | string | |
sha256 | string | |
extracted | true |
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).
| Format | Extensions (auto-detect) | Note |
|---|---|---|
| tar | .tar | — |
| tar.gz | .tar.gz, .tgz | — |
| tar.bz2 | .tar.bz2, .tbz2 | extract 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.