July 7, 2026·4 min read·engineeringgosecurityfilesystemtls

Go 1.26.5 closes a trailing-slash root escape

July's fixes address a Unix symlink escape and leaked TLS pre-shared key identities. Both concern information or authority that crossed a boundary despite the protective API being in use.

The July 7 Go 1.26.5 release repairs security defects in os and crypto/tls. The announcement describes a root escape through a symlink followed by a trailing slash, and a privacy leak in Encrypted Client Hello. Go 1.25.12 receives the corresponding security update.

These fixes matter to me because they concern code that has already selected a protective interface. An application can use os.Root and still encounter an implementation defect in path handling. A TLS client can enable ECH while leaking information through another handshake field. I want the review of such code to state the intended protection precisely enough that a regression test can examine the boundary.

The root escape report, CVE-2026-39822, identifies a Unix path whose final component is a symlink and whose final character is /. Opening that path through os.Root could follow the symlink outside the root. The report gives root.Open("symlink/") as the relevant call when the link points outside the intended directory.

The mechanism lies in openat semantics. According to the report, openat(fd, path, O_NOFOLLOW) follows symlinks when the path ends in a slash. Go failed to account for that behavior in its implementation of root confinement. The fix sanitizes the path supplied to the syscall. A flag with a protective name was insufficient without handling the path form to which the kernel applied it.

For regression review, I would retain the trailing slash in the input fixture and explain why it is there. A reviewer might otherwise simplify the string and unintentionally remove the reported condition. I would also verify that the fixture's link points beyond the intended root, since a link that stays inside it does not demonstrate the same boundary violation.

Root confinement has a specific contract

The original os.Root explanation, published in March 2025, describes the API as traversal-resistant access relative to a directory. It permits relative components and symlinks when they remain inside that root. It also explains why resolving symlinks first and opening a pathname later can race with filesystem changes. The API exists partly to avoid making every caller implement that protection.

On Unix, the explanation describes a directory file descriptor and operations through the openat family. It also states that root confinement does not restrict mount-point traversal, including Linux bind mounts. That is a threat-model limit, separate from the newly fixed symlink escape. I would preserve the distinction when documenting what an application expects from its filesystem boundary.

I prefer this kind of explicit contract to an application helper whose security behavior depends on several distant path checks. That preference does not remove the need to update the implementation. It gives a maintainer a clear place to apply the correction and a named guarantee against which to write tests. I would continue to express the confinement requirement through the API while ensuring the deployed toolchain contains the fix.

In an application review, I would start by identifying which paths are untrusted and which directory contains the permitted objects. Then I would check that the actual operation uses the intended root. A test should make the outside object distinguishable from the inside object so that an accidental escape cannot look like a successful ordinary open. That is a proposed application test, not a claim that I have reproduced this defect in a particular system.

The TLS report, CVE-2026-42505, says the ECH implementation leaked pre-shared key identities during handshakes. A passive observer collecting handshakes could use them to de-anonymize the server hostname even when ECH was enabled. The fix is described in the issue as omitting PSK from the outer ClientHello.

The distinction between inner and outer messages explains why the field matters. The ECH specification's PSK section prohibits advertising actual PSK identities in ClientHelloOuter. The client may offer them in ClientHelloInner. It recommends a GREASE pre-shared-key extension in the outer message with random identities and binders, so that the visible extension exchange remains plausible without disclosing the real identity.

That specification separates protocol appearance from the information the client intends to reveal. A real resumption identity and a random placeholder may occupy related protocol positions while carrying different privacy consequences. I would not assess the protection by checking only that a server name is absent from one visible field. The PSK report identifies another field through which an observer can correlate a connection.

The report describes hostname privacy loss, rather than a general claim that the observer can decrypt application traffic. I would keep those consequences separate in an upgrade notice. An operator needs to know what protection failed in order to identify the relevant clients and review the promises made about their network behavior.

Test the property the application relies on

For ECH, I would want a protocol test to inspect the outer message in a case that offers a real PSK internally. A connection-success assertion would establish interoperability but leave the privacy property unexamined. The expected output needs to state which real identity must remain absent from the visible handshake.

For filesystem access, I would want the test to inspect which object the open operation reaches. A successful call alone cannot establish confinement if the wrong object is also readable. Both test designs begin with the application's requirement and then make the forbidden observation explicit. They do not assume that enabling an API is evidence that every behavior behind it is correct.

My upgrade record would identify the affected application artifacts and confirm their replacement with builds containing this release. I would retain the trailing-slash symlink case and the ECH outer-message case as separate regressions. Their fixtures should explain the meaning of the path suffix and the PSK identity, because those details are the reason the otherwise ordinary operations require special coverage.