Skip to content

fix(security): reject unsafe entries during archive extraction#91

Merged
albertodebortoli merged 3 commits into
mainfrom
security/harden-archive-extraction
Jun 13, 2026
Merged

fix(security): reject unsafe entries during archive extraction#91
albertodebortoli merged 3 commits into
mainfrom
security/harden-archive-extraction

Conversation

@albertodebortoli

@albertodebortoli albertodebortoli commented Jun 13, 2026

Copy link
Copy Markdown
Member

Description

  • What & why: When Luca installs a tool distributed as a .zip or .tar.gz, it shells out to unzip/tar to extract the archive into the tool's installation directory. The archive is fetched from a remote URL and, when no checksum is configured, is otherwise unverified. unzip (and tar on some platforms) will create a symbolic link and then write through it, so a crafted archive can drop files into arbitrary locations such as ~/.ssh/authorized_keys or shell start-up files — the "Zip Slip" / tar symlink-escape class of attacks.
  • Fix: Unarchiver now inspects the archive's table of contents before extracting anything and refuses the archive if any entry is a symbolic or hard link, uses an absolute path, or contains a .. traversal component. A malicious archive therefore never reaches the extraction step.
  • Escape hatch: A new --ignore-unsafe-entries CLI flag and a per-tool ignoreUnsafeArchiveEntries Lucafile key let users opt out of the check for trusted archives — mirroring the existing --ignore-arch-check / ignoreArchCheck pattern exactly.
  • Trade-offs: Validation runs two quick unzip -Z/tar -t listings per archive. Hard links are rejected alongside symlinks because they can also point outside the destination; legitimate tool archives do not rely on link entries.

Type of Change

  • Feature
  • Bug fix
  • Maintenance / Refactor
  • Documentation
  • CI / Tooling
  • Other (specify)

How Has This Been Tested?

swift build and swift test locally on macOS (arm64).

  • Added / updated unit tests
  • Manually tested locally (built malicious fixtures and confirmed extraction is refused)
  • Tested on macOS (arch: arm64)

New fixtures and tests:

  • MockSymlink.zip, MockSymlink.tar.gz — archives containing a symlink entry; extraction is refused with unsafeArchiveEntry and nothing is written.
  • MockTraversal.tar.gz — archive with a ../escape.txt entry; extraction is refused.
  • test_unarchive_archiveContainingSymlink_ignoringUnsafeEntries_succeeds — verifies symlink archives extract when the flag is set.
  • test_unarchive_archiveWithParentTraversal_ignoringUnsafeEntries_throwsFailedToUnarchiveNotUnsafeEntry — verifies our check is bypassed (tar itself still rejects ../ paths, surfaced as failedToUnarchive).
  • 6 ToolInstallerTests covering global flag true/false, per-tool override true/false, and nil fallback.

Checklist

  • Swift code builds locally (swift build)
  • Tests pass locally (swift test)
  • Code style / formatting respected
  • Documentation updated (DocC comments on new error case, helpers, and Tool field)

CI Considerations

  • Affects build time notably
  • Requires new secrets / env vars
  • Alters release process

Breaking Changes?

  • No

Additional Notes

The --ignore-unsafe-entries flag follows the identical layering as --ignore-arch-check: the CLI flag is the global default, the per-tool ignoreUnsafeArchiveEntries key in the Lucafile overrides it (true always skips, false always validates, nil falls back to the CLI flag).

Unarchiver now scans an archive's table of contents before extracting and
refuses any archive containing a symbolic/hard link, an absolute path, or a
'..' traversal component. This blocks the Zip Slip / tar symlink-escape class
of attacks where a crafted archive writes files outside the installation
directory (e.g. ~/.ssh, shell rc files).

Adds malicious fixtures (symlink zip/tar.gz, parent-traversal tar.gz) and
tests asserting extraction is refused and nothing is written.
@albertodebortoli albertodebortoli added this to the 0.21.0 milestone Jun 13, 2026
@albertodebortoli albertodebortoli added the security Security fixes label Jun 13, 2026
@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.53086% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...urces/ManagerCore/Core/Unarchiver/Unarchiver.swift 97.05% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

…nsafeArchiveEntries override

Mirrors the --ignore-arch-check / ignoreArchCheck pattern so users can opt out
of the Zip Slip / symlink-escape safety check when they trust the archive source.

- Tool gains ignoreUnsafeArchiveEntries: Bool? (nil falls back to the global flag)
- Unarchiver.init accepts ignoreUnsafeArchiveEntries: Bool and skips validateArchiveEntries when set
- ToolInstaller computes the effective flag (per-tool ?? global) and passes it to Unarchiver
- Installer threads ignoreUnsafeArchiveEntries through both public and internal inits
- InstallCommand exposes --ignore-unsafe-entries CLI flag
- Adds fixture Lucafile_mock_ignoreUnsafeArchiveEntries_true.yml and eight new tests
…staller init

- Add MockAbsolutePath.zip fixture (zip with a /evil.txt entry) and a test that
  verifies extraction is refused with unsafeArchiveEntry
- Refactor validateArchiveEntries to use if/else instead of switch, removing the
  dead case .executable: return branch that was never reachable from the call site
- Add test_init_publicInit_ignoreUnsafeArchiveEntries_threadsThrough to exercise
  Installer's public init (which delegates ignoreUnsafeArchiveEntries through to
  the internal init), using a home-directory file manager for an early-exit path
@albertodebortoli albertodebortoli marked this pull request as ready for review June 13, 2026 20:50
@albertodebortoli albertodebortoli merged commit 1314ac7 into main Jun 13, 2026
3 checks passed
@albertodebortoli albertodebortoli deleted the security/harden-archive-extraction branch June 13, 2026 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

security Security fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant