TL;DR: docker cp can overwrite files on the host. Copying a file out of a container is enough, and the write uses whatever privileges we ran the command with. Under sudo on a build runner, a container replaces /usr/bin/runc and gets root the next time Docker uses it. That's CVE-2026-17106. Patch to Engine and CLI 29.7.2 or later, Desktop 4.86.0 or later, Sandboxes 0.38.0. Also inside: how deleting one config line blinds a log pipeline, and what our coding agents leave on disk.

Docker's CLI trusts a symlink a container slipped into an archive. Elastic's log pipeline never sees a safety setting get deleted. And the encrypted blobs our coding agents write down turn out to be readable.

🎯 Attack of the Week: a copy that lands in /usr/bin

docker cp looks like a file copy. Two programs are doing the work. The daemon walks the container's filesystem and packs a tar archive. Our machine then unpacks it, under our account, and everything inside that archive came from the container.

The tar stream mixes two states of the filesystem. The daemon's walk sees escape as a directory. The container swaps it for a symlink pointing at /usr/bin. The walk records escape as a symlink. Then it keeps walking the directory it saw a moment earlier and writes a child entry under it. As Imperva puts it, the two entries "describe two different moments in the source filesystem."

Then the CLI reads the wrong field. A tar entry keeps two things: its own name, and, for a symlink, the path it points at. The safety check only ever looks at the name. When the link gets built, the target comes straight out of the archive. Imperva: the CLI "validates a constructed target but creates the original absolute symlink," because "the subsequent os.Symlink call receives the original hdr.Linkname from the archive."

So the symlink gets created inside the folder we picked, pointing out of it. The check looks at /safe/output/file.txt/escape, sees a path that still sits under /safe/output, and says yes. Then the child entry arrives, the kernel follows escape to /usr/bin, and the bytes land on /usr/bin/runc.

What it costs. The write uses our permissions, not the container's: "the final write occurs on the machine running the CLI, with the authority of that user." Run it under sudo and those are root's. Imperva replaced /usr/bin/runc with a shell script. Docker executed it later and created a marker file as root. The copy is enough to plant a root-owned file. Docker runs it the next time it needs runc.

The trigger is one ordinary command:

docker cp container:/path/to/file.txt ./file.txt

Pulling logs out of a container someone else built is enough.

This command has failed this way before. CVE-2018-15664, disclosed by Aleksa Sarai in 2019, was "a symlink-exchange attack with Directory Traversal, giving attackers arbitrary read-write access to the host filesystem with root privileges, because daemon/archive.go does not do archive operations on a frozen filesystem (or from within a chroot)." That one was a bug in how the archive got built. This one adds a second bug in how it gets unpacked.

Ship This Week. Upgrade to Docker Engine and CLI 29.7.2 or later, Desktop 4.86.0 or later, Sandboxes 0.38.0. The fix landed in 29.7.0 on July 30; 29.7.1 and 29.7.2 clean up regressions it caused, including docker cp and image pulls failing on older kernels.

Check by version, because a CVE feed won't tell us. The score sits in GHSA-hfg8-hc9c-6c3h at CVSS v4.0 7.1, CWE-22 and CWE-59, and that advisory is attached to the moby/go-archive repo. As of this writing NVD returns no entry, MITRE returns CVE_RECORD_DNE, OSV has nothing, GitHub's global advisory database comes back empty. If our scanner looks up CVE numbers, it can't see this one.

Until we patch, don't run the copy as root. Imperva's guidance is to pull files from suspicious containers "using a disposable account, VM, or similarly isolated environment." Picking a scratch folder won't help, because the symlink decides where the bytes go, anywhere we can write.

Copying from a stopped container does block the race, since a walk over a frozen filesystem never steps into that symlink. Keep Imperva's caveat with it: that "does not replace safe extraction," and the advisory covers Unpack and ApplyLayer too, which run when there's no container at all. Note the cost, because incident response is the case they call out. Stopping a container to collect evidence safely throws away the running state we wanted.

🚨 Rule of the Week

Delete the setting and no alert fires. npm's min-release-age, available since npm 11.10, keeps versions published in the last N days out of resolution. That wait blocks a freshly compromised release. Delete the line and the protection is gone.

Log shippers watch for new lines. In van der Meulen's words, filestream "is designed for log tailing: when bytes arrive at the end of a file, an event is emitted." When npm rewrites .npmrc without the cooldown line, filestream sees the file change. Then the allowlist drops every line, and nothing reaches Elasticsearch.

Log tailing only fires when bytes get added, and taking a line out adds nothing.

His fix reads the file directly. About 40 lines of CEL check every .npmrc every 6 hours and set cooldown.absent = true when the setting disappears. He also ships an emit-on-change version, for when 6 hours is too slow.

Do this week. Take one setting we depend on and ask whether our telemetry would notice it being deleted. An .npmrc cooldown, a Dependabot config, an AWS Service Control Policy, a NetworkPolicy. When log tailing is the only source, the answer is no. Alert on the control going missing, and on a host that quietly stops reporting it at all.

🔧 Defender's Corner

Turn on read auditing for our agent session directories. Gavin Kramer at SpecterOps, "Blacklight: Illuminating AI Agent Artifacts for Attackers and Defenders" [2026-08-12].

The token files already get attention. Kramer's toolkit maps the ones that don't: .codex/sessions/, .claude/sessions/, .cursor/chats/**/store.db, .gemini/antigravity-cli/conversations/. Chat transcripts and conversation data, sitting in the clear beside .codex/auth.json and .claude/.credentials.json.

Put together, Kramer says, they reveal "who the user is, what they are working on, which systems they can access, how their agent is configured, and whether reusable authentication material is present."

His main point is simple: "Modification events should not be treated as proof of access." Most file monitoring fires when something changes. A read leaves no write event behind, so copying them off a laptop may generate no alert at all.

His POSIX osquery config queries file_events over those directories every 60 seconds. On supported macOS, es_process_file_events adds process-linked activity, though it wants a signed osquery build, Endpoint Security support and Full Disk Access. Windows read confirmation runs on Event 4663 with Audit File System enabled and SACLs on the paths.

Pick the paths, then switch on read auditing for them. A change event on these directories says almost nothing about who read them.

🤖 Agent Bench: ask what our checks are reading

This pattern repeats across a codebase, and an agent can scan for it.

Point a coding agent at the places we check a path or a URL before acting on it, and ask for four lists:

  1. Where a value gets normalized, resolved or rebuilt for validation, and the original is what the operation then uses. Require both variable names and the file:line.

  2. Archive and upload handling. Where we extract tar or zip, whether entry names are checked after resolution, and whether symlink entries are handled at all.

  3. Conditionals guarding privileged work that key on a field which is null or absent on some code paths. A guard reading a missing field passes.

  4. Checks on a string that a later step re-decodes, re-encodes or Unicode-normalizes before use.

What the run produces: every check/use mismatch with a file:line, ranked by how much of the value an attacker controls.

The failure mode to plan for: an agent will call a path reachable without ever proving it. Trace the input by hand before a finding counts as one.

Try it this week: run list 3 against one service's authorization checks. A guard keyed on a field that's null for some event types is the cheapest of these to find and the easiest thing to wave through in review.

📡 Also on the Radar

The encrypted blobs in agent session files can be read back. Johann Rehberger, "Recovering Encrypted LLM Reasoning Traces" [2026-08-16].

Panfilov et al., "Stealing Reasoning Traces from Proprietary LLM APIs" (arXiv:2608.09867, 2026-08-10), decoded 315,320 reasoning blocks scraped from public repositories. Out came 367 pieces of personal data and 182 credentials. The blobs work across sessions, users and models from the same provider, so handing one to another model from that provider gets the reasoning back in plain text. The paper covers Anthropic, OpenAI and Google, and credits Matthew Green with spotting the replay trick back in May.

Rehberger reproduced it, and he's honest about the edges: some recoveries came back reworded, and it got less reliable as he went. The probe.py he built works on OpenAI's format, where the blobs sit in ~/.codex/sessions/ under an encrypted_content field. On the cause: "providers likely use shared encryption keys across users, sessions, and models."

Which brings it back to those session directories. People commit and share these files, and the encrypted parts carry things no reviewer can see. As Rehberger puts it: "If you share session files containing them, you may be sharing significantly more information than you realize."

Docker's CLI trusted a symlink the container wrote. Elastic only emitted on new lines, so a deleted one went unseen. So: what is our safety check looking at?

How was this issue?

Login or Subscribe to participate

R.K.

100 real-world tasks. 8 tool categories. 1 complete DevOps stack.

Master Git, Docker, Kubernetes, Linux, CI/CD, Terraform, and monitoring through structured tasks across real-job scenarios, and earn a shareable credential that proves you can apply what you’ve learned.

No lectures, no theory. Build a public portfolio as you go. Earn a verified badge. Free.