← Back to Feed CACHED · 2026-10-07 14:51:15 · CACHE_KEY CVE-2026-17106
CVE-2026-17106 · CWE-59 · Disclosed 2026-08-18

CopyEscape: Container-to-Host Arbitrary File Write via Symlink-Following in moby/go-archive Tar Extraction

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

The moving company that lets a stowaway reroute every box to an address of their choosing

CVE-2026-17106, dubbed CopyEscape by Imperva researcher Ron Masas, is a symlink-following path traversal in moby/go-archive — the tar extraction library embedded in Docker Engine, Docker CLI, Docker Desktop, Docker Compose, and Docker Sandboxes. The Unpack, UnpackLayer, Untar/UntarUncompressed, and ApplyLayer functions validate archive entry paths with lexical string checks only. A malicious container process exploits a TOCTOU race during docker cp: it watches via inotify for the daemon's WalkDir to begin, then atomically swaps a directory for an absolute symlink pointing outside the extraction root. The daemon emits stale entries into the tar stream; the CLI extracts them through the dangling symlink, writing arbitrary files with the CLI user's privileges. On Linux with sudo, the attacker overwrites /usr/bin/runc and achieves root code execution the next time *any* container lifecycle event fires. Affected versions: moby/go-archive < 0.3.0, Docker Engine/CLI < 29.7.0, Docker Desktop < 4.86.0, Docker Compose < 5.4.0, Docker Sandboxes < 0.38.0.

The GHSA advisory (GHSA-hfg8-hc9c-6c3h) assigns CVSS v4.0 7.1 (HIGH). That score is directionally correct: the local attack vector (AV:L), active user-interaction requirement (UI:A — someone must invoke docker cp against the malicious container), and TOCTOU prerequisite (AT:P) keep this out of CRITICAL territory for *most* deployments. However, the advisory underweights the blast radius in CI/CD environments where docker cp is scripted to extract build artifacts — there the 'user interaction' is an automated pipeline step, not a human exercising judgment. Imperva's public PoC demonstrates reliable exploitation on Linux (runc overwrite → root code execution) and macOS (LaunchAgent persistence), converting a bare file-write primitive into full host compromise. The gap between the 7.1 advisory score and real-world CI/CD impact warrants a noisgate assessment of 7.5 HIGH.

"Reliable container-escape via docker cp lets attackers write to host — CI/CD supply-chain risk"
02 · The Attack Path

5 steps from start to impact.

STEP 01

Attacker prepares malicious container

The attacker builds or compromises a container image that includes an LD_PRELOAD interposer and an inotify-based watcher script. The interposer presents a deceptive filesystem view: a target path appears as a regular directory to the daemon's WalkDir. A large sentinel file provides a reliable timing signal — when the daemon accesses it, the watcher knows the walk is mid-flight. This setup is entirely within default Docker container capabilities; no privileged flags are needed.
Conditions required:
  • Attacker controls or influences a container image (public registry push, malicious PR modifying Dockerfile, compromised private registry, or lateral movement into a running container)
  • Container runtime allows inotify and LD_PRELOAD (default Docker configuration)
Where this breaks in practice:
  • Attacker must get their image onto a target host or into a CI pipeline — this requires either social engineering (malicious PR), a supply-chain compromise, or prior access to a container registry
Detection/coverage: Container image scanning (Trivy, Grype) will NOT detect this — the payload is runtime behavior, not a known vulnerable package. Runtime behavioral tools (Falco, Sysdig Secure) could alert on LD_PRELOAD injection if configured with the appropriate rule (proc.env contains LD_PRELOAD).
STEP 02

Victim invokes docker cp from the malicious container

A user or automated CI/CD script runs docker cp <container>:/path /host/destination. This triggers the Docker daemon to walk the container's filesystem and serialize it into a tar stream sent to the CLI for extraction. The container must be running (not stopped) for the TOCTOU race to succeed — the malicious process needs to execute the directory-to-symlink swap during the walk. In CI/CD pipelines, build scripts commonly use docker cp to extract compiled artifacts, test reports, or coverage files.
Conditions required:
  • Someone runs docker cp FROM the malicious container (copy-out direction, not copy-in)
  • Container is in running state at the time of the cp invocation
Where this breaks in practice:
  • Requires explicit user action or a CI script that uses docker cp — many production Docker workflows never invoke this command
  • Kubernetes environments use kubectl cp which routes through the exec API and a different tar implementation, not moby/go-archive
  • Stopping the container before docker cp completely neutralizes the attack
Detection/coverage: Docker daemon audit logs (dockerd --log-level=info) record docker cp operations including source container ID. EDR agents on the host can monitor for docker process invocations with cp subcommand.
STEP 03

TOCTOU race: directory swapped for absolute symlink

When the daemon's filepath.WalkDir traverses the sentinel directory, the container's inotify watcher fires. It atomically replaces the directory with an absolute symlink pointing outside the extraction root — e.g., to /usr/bin/ on Linux or ~/Library/LaunchAgents/ on macOS. The daemon has already recorded the path as a directory and continues emitting child entries as if walking under the original tree. Imperva's PoC demonstrates this is a deterministic, repeatable exploit, not a one-shot gamble — the large sentinel file provides a wide, reliable timing window.
Conditions required:
  • Container process has inotify access and can create symlinks (default capabilities)
  • Race window is held open by the sentinel file technique
Where this breaks in practice:
  • The TOCTOU window exists inherently in the WalkDir→tar pipeline; the sentinel file technique effectively eliminates timing uncertainty
Detection/coverage: Runtime security (Falco rule: container.id != host AND evt.type = symlinkat AND fd.name contains /..) could catch the symlink creation. The swap from directory to symlink on the same path is an anomalous filesystem event.
STEP 04

CLI extracts payload through symlink to arbitrary host path

The Docker CLI receives the tar stream and extracts it to the user-specified destination. It validates a *constructed* target path with string-prefix checks — these pass. However, it creates the actual symlink using the raw hdr.Linkname from the tar header without re-validating. Subsequent entries extracted after the symlink are written through it, landing wherever the OS resolves the link — outside the intended destination directory. Files are created with the permissions of the user running docker cp.
Conditions required:
  • Docker CLI is unpatched (< 29.7.0)
  • The host filesystem path targeted by the symlink is writable by the CLI user
Where this breaks in practice:
  • Without sudo/root, writes are limited to the CLI user's writable paths — shell profiles, SSH authorized_keys, source code — dangerous but not system-level
  • macOS Gatekeeper and TCC prompts may block writes to some protected locations
Detection/coverage: File integrity monitoring (AIDE, OSSEC, Wazuh) on known-good paths detects unexpected file creation or modification. Host-based IDS rules for writes to ~/.ssh/, ~/.bashrc, ~/Library/LaunchAgents/ catch the macOS persistence path.
STEP 05

Privilege escalation via runc binary overwrite

On Linux, if docker cp runs as root (common with sudo docker cp or in CI workers running as root), the attacker symlinks to /usr/bin/ and overwrites the runc binary with a malicious payload. The next container lifecycle operation — docker run, docker exec, docker stop — invokes the compromised runc as root. Imperva's PoC confirms this path: the replaced binary creates a root-owned marker file, proving arbitrary code execution as PID 1's parent. This converts a file-write primitive into persistent root-level host compromise.
Conditions required:
  • docker cp runs as root or with sudo
  • Writable access to /usr/bin/runc (standard for root)
  • Any subsequent container lifecycle event triggers the payload
Where this breaks in practice:
  • Rootless Docker configurations are immune — runc is not writable
  • Organizations restricting sudo for Docker operations block this escalation path
  • Docker Desktop for macOS runs the daemon in a Linux VM; runc overwrite compromises the VM, not the macOS host directly
Detection/coverage: EDR (CrowdStrike, SentinelOne, Defender for Endpoint) should detect modification of /usr/bin/runc — a system binary change outside of a package manager transaction is a high-fidelity alert. Binary integrity checking against known-good hashes catches this immediately.
03 · Compensating Control

1
HIGH 7.5→LOW 2.0
SEVERITY REDUCED
Stop containers before running docker cp — The TOCTOU race requires a running container process to swap directories during the daemon's WalkDir. Running docker stop <container> before docker cp completely breaks the race — no process can manipulate the filesystem mid-walk. This is the fastest, most effective compensating control. Deploy within 30 days per the noisgate mitigation SLA for HIGH severity. Update all CI/CD pipeline scripts, runbooks, and developer documentation to enforce docker stop before any docker cp operation.
2
HIGH 7.5→IGNORE 0.0
SEVERITY REDUCED
Replace docker cp with bind mounts or named volumes — Instead of using docker cp to extract artifacts, mount the output directory as a bind mount (docker run -v /host/path:/container/output ...) or use a named volume (docker volume create artifacts && docker run -v artifacts:/output ...). This eliminates the tar serialization/extraction pipeline entirely and removes the vulnerable code path from the workflow. In CI/CD, refactor build scripts to use --mount type=bind for artifact extraction. Deploy within 30 days per the noisgate mitigation SLA for HIGH severity.
3
HIGH 7.5→MEDIUM 5.5
SEVERITY REDUCED
Remove root/sudo from docker cp invocations — Without root privileges, the attacker cannot overwrite system binaries (/usr/bin/runc). File writes are limited to the CLI user's writable paths — shell profiles, SSH keys, source code — still dangerous but the host-takeover-via-runc path is eliminated. Configure rootless Docker (dockerd-rootless-setuptool.sh install) or restrict sudo access for Docker operations. In CI/CD, run build steps as a non-root user where possible. Deploy within 30 days.
4
HIGH 7.5→IGNORE 0.0
SEVERITY REDUCED
Patch to Docker Engine/CLI 29.7.0+ and Desktop 4.86.0+ — Apply the vendor patch, which vendors moby/go-archive v0.3.0 with hardened symlink resolution during tar extraction. This is the definitive fix. Deploy within 180 days per the noisgate remediation SLA for HIGH severity. Prioritize CI/CD workers and shared development environments first; developer workstations second; production container hosts (which rarely use docker cp) last. For Go projects importing moby/go-archive directly, run go get github.com/moby/[email protected] and rebuild.
What doesn't work
  • Container image scanning (Trivy, Grype, Snyk Container): The exploit payload is runtime behavior — an LD_PRELOAD interposer and inotify watcher that execute when the container runs, not a vulnerable package in the image manifest. Static image scanning will never flag this.
  • SELinux / AppArmor container profiles: These policies restrict what the container process can do, but the directory-to-symlink swap and inotify operations are permitted under default Docker SELinux and AppArmor profiles. More importantly, the actual file writes happen on the host side during CLI extraction, entirely outside the container's security context.
  • Network segmentation / firewall rules: CVE-2026-17106 is a local attack — docker cp is a local CLI operation communicating with the daemon over a Unix socket. Network controls have zero bearing on exploitability.
  • Docker Content Trust (DCT) / image signing: DCT verifies image provenance and integrity, not runtime behavior. A validly signed image from a trusted publisher could still contain the LD_PRELOAD exploit payload if the publisher's build pipeline is compromised or if a developer intentionally adds it.
  • Read-only container filesystem (--read-only): The exploit uses inotify and symlink creation in the container's writable layers. The --read-only flag only affects the root filesystem; tmpfs mounts (which are writable by default) and any volume mounts still allow the symlink swap. Additionally, the exploit needs the target path to exist in the container's filesystem view, which it does in the default overlay.
04 · Intelligence Metadata

The supporting signals.

In-the-Wild StatusNo confirmed in-the-wild exploitation. Not listed in CISA KEV. No GreyNoise tags or Shadowserver scan activity reported as of 2026-10-07. The local attack vector (docker cp must be explicitly invoked) makes opportunistic mass exploitation unlikely — this is a targeted attack primitive, most dangerous in CI/CD and supply-chain scenarios.
Proof of ConceptPublic, reliable, weaponized PoC available. Imperva published a detailed technical write-up (blog) and the exploit is mirrored at HackSpeak/CVE-2026-17106 (MIT license). Includes macOS variant (non-destructive shell profile overwrite) and Linux variant (runc overwrite → root code execution). Also indexed in XZ1r0/cve-2026-poc-collection and SecureWithUmer/CVE-2026-PoCs. The PoC uses inotify + sentinel file to make the TOCTOU race deterministic.
EPSS0.0044 (0.44%) — low predicted probability of exploitation in the next 30 days (~25th percentile). Consistent with the local attack vector and user-interaction requirement. EPSS does not account for CI/CD automation scenarios where the 'user interaction' is a script.
KEV StatusNot listed in the CISA Known Exploited Vulnerabilities catalog as of 2026-10-07.
CVSS VectorCVSS v4.0: 7.1 CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N — Local, low complexity, attack prerequisites (TOCTOU timing), no privileges, active user interaction, high C/I/A impact on vulnerable system, no cross-system impact. CVSS v3.1: 7.8 CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H. Both sourced from GHSA-hfg8-hc9c-6c3h.
Affected Versionsmoby/go-archive < 0.3.0 · Docker Engine/CLI < 29.7.0 · Docker Desktop < 4.86.0 · Docker Compose < 5.4.0 · Docker Sandboxes < 0.38.0. Any Go binary importing github.com/moby/go-archive at a version below v0.3.0 and extracting untrusted archives is also vulnerable.
Fixed Versionsmoby/go-archive v0.3.0 (0.3.0" target="_blank" rel="noopener">release) · Docker Engine/CLI 29.7.0 (29.7.2 recommended) · Docker Desktop 4.86.0 · Docker Compose 5.4.0 · Docker Sandboxes 0.38.0. Red Hat tracks backports via RHSA; Debian/Ubuntu via docker-ce packages. Go module consumers: go get github.com/moby/go-archive@v0.3.0.
Exposure SurfaceDocker Engine is not typically internet-exposed (daemon socket is local or TLS-secured). The attack vector is local (docker cp), so Shodan/Censys exposure data is not directly applicable. Primary risk surface: CI/CD workers (Jenkins, GitLab Runner, GitHub Actions self-hosted runners, CircleCI) that script docker cp to extract build artifacts, and developer workstations using Docker Desktop.
Disclosure Timeline2026-08-18: Advisory published, CVE assigned, patches released (Docker Engine 29.7.0, Desktop 4.86.0). 2026-08-20: Advisory updated. Fix PR: moby/go-archive#45, vendored into Docker Engine via moby/moby#53247.
Reporting ResearcherRon Masas, Imperva Red Team. Remediation by thaJeztah (Sebastiaan van Stijn, Docker/Moby maintainer). Reviewed by vvoland and mickael-docker. Vulnerability dubbed CopyEscape.

Sources.

  1. Imperva Blog — CopyEscape Technical Write-up (Ron Masas)
  2. GitHub Security Advisory GHSA-hfg8-hc9c-6c3h
  3. moby/go-archive v0.3.0 Release Notes
  4. HackSpeak/CVE-2026-17106 — Public PoC Repository
  5. GitLab Advisory Database — CVE-2026-17106
  6. SecureLayer7 Labs — CVE-2026-17106 Analysis
  7. NHS England Cyber Alert CC-4828 — Docker CopyEscape
  8. Docker Engine PR #53247 — Vendor go-archive v0.3.0
05 · The Call

Final Verdict
= UNCHANGED to HIGH (7.5/10)

Why this verdict

  • Reliable public PoC with host-takeover path: Imperva's CopyEscape PoC demonstrates deterministic exploitation — the inotify + sentinel-file technique converts the TOCTOU race from probabilistic to repeatable. On Linux with sudo, the runc overwrite delivers immediate root code execution with persistence across all future container operations. This is not a theoretical crash-only demonstration; it is a weaponized exploit with two platform-specific payloads.
  • CI/CD supply-chain vector elevates real-world impact: In CI/CD pipelines, docker cp is commonly scripted to extract build artifacts, test reports, and coverage data from builder containers. An attacker who contributes a malicious Dockerfile via a pull request triggers the exploit automatically when the CI worker executes the build-and-extract flow. The CVSS UI:A (active user interaction) is a *machine*, not a human making a judgment call. This is supply-chain-scale impact — a compromised CI worker can poison downstream artifacts, inject backdoors into release binaries, or pivot laterally into the build infrastructure.
  • Role multiplier: Docker Engine is a canonically high-value component (container orchestration per the role catalog). On CI/CD workers (~20–30% of Docker Engine deployments by industry surveys), the chain succeeds with supply-chain-scale blast radius — the docker cp invocation is a standard build-script step, and the CI worker typically runs as root. On production container hosts, docker cp is rarely used in normal operations; Kubernetes clusters use containerd/CRI-O with kubectl cp via the exec API, not moby/go-archive. On developer workstations (~40–50% of Docker Desktop installs), blast radius is host-level (shell profiles, SSH keys, source code). The CI/CD case floors this at HIGH; the production-host case does not escalate further because docker cp is not part of the standard container lifecycle.
  • User interaction requirement prevents CRITICAL: The CVSS v4.0 vector includes UI:A — docker cp must be explicitly invoked against the malicious container. Outside of CI/CD, this means the attacker must social-engineer or trick a human into running a specific command against their container. The container must also be in a running state. These two prerequisites narrow the exploitable population below the CRITICAL threshold for the general Docker install base.
  • Local attack vector, no remote chain: AV:L means there is no network-reachable path to triggering this vulnerability. The attacker needs either a malicious image on a registry that someone pulls and runs docker cp against, or existing code execution inside a container. Combined with the user-interaction gate, this bounds the realistic attack surface.

Why not higher?

CRITICAL would require either a remote attack vector or exploitation without active user interaction. CopyEscape has neither: the attack is local (AV:L), requires explicit invocation of docker cp against the malicious container (UI:A), and the container must be running. While Docker Engine is canonically high-value, docker cp is not part of the default container lifecycle — it is an operator-initiated or script-initiated action. In production Kubernetes environments (the largest segment of enterprise container infrastructure), docker cp is not used: kubectl cp routes through a completely separate code path. The fraction of Docker hosts where docker cp is a routine automated operation (primarily CI/CD) is substantial but not universal enough to warrant CRITICAL across the install base.

Why not lower?

MEDIUM would dangerously underweight the blast radius. The public PoC is reliable and weaponized, not crash-only or theoretical. On Linux with sudo, runc overwrite gives immediate root code execution on the host with persistence — every future container lifecycle event re-triggers the payload. In CI/CD environments, this is a supply-chain attack with zero human judgment in the loop, targeting infrastructure that builds and signs release artifacts. The library (moby/go-archive) is embedded in every Docker installation worldwide — the affected population is enormous. An EPSS of 0.44% reflects the local/UI-gated attack vector, not the severity of successful exploitation.

06 · Verification

Crowdsourced verification payload.

Run on each Docker host (Linux or macOS) as any user with permission to invoke docker version. No sudo required. Example: bash check_cve_2026_17106.sh. For Go projects importing moby/go-archive, the script also checks go.sum if present in the current directory.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# CVE-2026-17106 (CopyEscape) — Docker & moby/go-archive version checker
# Run on Docker hosts. No special privileges required.
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN

set -uo pipefail

PATCHED_ENGINE="29.7.0"
PATCHED_GOARCHIVE="0.3.0"
STATUS="UNKNOWN"

version_gte() {
  # Returns 0 if $1 >= $2 using version-aware sort
  printf '%s\n%s\n' "$2" "$1" | sort -V | head -n1 | grep -qx "$2"
}

echo "=== CVE-2026-17106 (CopyEscape) Check ==="
echo ""

# --- Check Docker Engine/CLI ---
if command -v docker &>/dev/null; then
  CLI_VER=$(docker version --format '{{.Client.Version}}' 2>/dev/null || true)
  if [ -z "$CLI_VER" ]; then
    CLI_VER=$(docker --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true)
  fi
  if [ -n "$CLI_VER" ]; then
    echo "[*] Docker CLI version: $CLI_VER"
    if version_gte "$CLI_VER" "$PATCHED_ENGINE"; then
      echo "[+] Docker CLI >= $PATCHED_ENGINE — PATCHED"
      STATUS="PATCHED"
    else
      echo "[-] Docker CLI < $PATCHED_ENGINE — VULNERABLE"
      STATUS="VULNERABLE"
    fi
  else
    echo "[?] Docker found but CLI version undetermined"
  fi

  SRV_VER=$(docker version --format '{{.Server.Version}}' 2>/dev/null || true)
  if [ -n "$SRV_VER" ]; then
    echo "[*] Docker Engine version: $SRV_VER"
    if version_gte "$SRV_VER" "$PATCHED_ENGINE"; then
      echo "[+] Docker Engine >= $PATCHED_ENGINE — PATCHED"
    else
      echo "[-] Docker Engine < $PATCHED_ENGINE — VULNERABLE"
      STATUS="VULNERABLE"
    fi
  fi
else
  echo "[?] docker CLI not found on this host"
fi

# --- Check Go module dependency (if in a Go project) ---
if [ -f go.sum ]; then
  GA_VER=$(grep 'github.com/moby/go-archive v' go.sum 2>/dev/null | head -1 | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' || true)
  if [ -n "$GA_VER" ]; then
    GA_CLEAN=${GA_VER#v}
    echo "[*] moby/go-archive in go.sum: $GA_VER"
    if version_gte "$GA_CLEAN" "$PATCHED_GOARCHIVE"; then
      echo "[+] moby/go-archive >= v$PATCHED_GOARCHIVE — PATCHED"
      [ "$STATUS" != "VULNERABLE" ] && STATUS="PATCHED"
    else
      echo "[-] moby/go-archive < v$PATCHED_GOARCHIVE — VULNERABLE"
      STATUS="VULNERABLE"
    fi
  fi
fi

echo ""
echo "=== RESULT: $STATUS ==="
case "$STATUS" in
  PATCHED)    exit 0 ;;
  VULNERABLE) echo "Action: update Docker to >= $PATCHED_ENGINE or moby/go-archive to >= v$PATCHED_GOARCHIVE"
              echo "Interim: stop containers before docker cp, or use bind mounts instead"
              exit 1 ;;
  *)          echo "Could not determine patch status — verify manually"
              exit 2 ;;
esac