Go Release Notes
Last updated: Apr 8, 2026
- Apr 7, 2026
- Date parsed from source:Apr 7, 2026
- First seen by Releasebot:Apr 8, 2026
Go 1.26.2 and Go 1.25.9 are released
Go releases 1.26.2 and 1.25.9 with 10 security fixes, strengthening compiler safety, TLS handling, certificate validation, archive parsing, and template escaping while addressing a Linux Root.Chmod symlink issue.
Hello gophers,
We have just released Go versions 1.26.2 and 1.25.9, minor point releases.
These releases include 10 security fixes following the security policy:
os: Root.Chmod can follow symlinks out of the root on Linux
On Linux, if the target of Root.Chmod is replaced with a symlink while the chmod operation is in progress, Chmod could operate on the target of the symlink, even when the target lies outside the root.
The Linux fchmodat syscall silently ignores the AT_SYMLINK_NOFOLLOW flag, which Root.Chmod uses to avoid symlink traversal. Root.Chmod checks its target before acting and returns an error if the target is a symlink lying outside the root, so the impact is limited to cases where the target is replaced with a symlink between the check and operation.
On Linux, Root.Chmod now uses the fchmodat2 syscall when available, and an workaround using /proc/self/fd otherwise.
Thanks to Uuganbayar Lkhamsuren for reporting this issue.
This is CVE-2026-32282 and Go issue https://go.dev/issue/78293.html/template: JS template literal context incorrectly tracked
Context was not properly tracked across template branches for JS template literals, leading to possibly incorrect escaping of content when branches were used.
Additionally template actions within JS template literals did not properly track the brace depth, leading to incorrect escaping being applied.
These issues could cause actions within JS template literals to be incorrectly or improperly escaped, leading to XSS vulnerabilities.
This only affects templates that use template actions within JS template literals.
This is CVE-2026-32289 and Go issue https://go.dev/issue/78331.crypto/x509: excluded DNS constraints not properly applied to wildcard domains
When verifying a certificate chain containing excluded DNS constraints, these constraints are not correctly applied to wildcard DNS SANs which use a different case than the constraint.
For example, if a certificate contains the DNS name "*.example.com" and the excluded DNS name "EXAMPLE.COM", the constraint will not be applied.
This only affects validation of otherwise trusted certificate chains, issued by a root CA in the VerifyOptions.Roots CertPool, or in the system certificate pool.
This issue only affects Go 1.26.
Thank you to Riyas from Saintgits College of Engineering, k1rnt, @1seal for reporting this issue.
This is CVE-2026-33810 and Go issue https://go.dev/issue/78332.cmd/compile: no-op interface conversion bypasses overlap checking
Previously, the compiler failed to unwrap pointers contained within a no-op interface conversion leading to an incorrect determination of a non-overlapping move.
To prevent unsafe move operations, the compiler will now unwrap all such conversions before considering a move non-overlapping.
Thank you to Jakub Ciolek - https://ciolek.dev/ for reporting this issue.
This is CVE-2026-27144 and Go issue https://go.dev/issue/78371.cmd/compile: possible memory corruption after bound check elimination
Previously, slices and arrays accessed using induction variables were sometimes incorrectly proved in-bound. If the induction variable used for indexing were to overflow or underflow, it could allow access to memory beyond the scope of the original slice or array.
To prevent this behavior, the compiler ensures that any mutated induction variable that overflows/underflows with respect to its loop condition is not used for bound check elimination.
Thank you to Jakub Ciolek - https://ciolek.dev/ for reporting this issue.
This is CVE-2026-27143 and Go issue https://go.dev/issue/78333.archive/tar: unbounded allocation when parsing old format GNU sparse map
tar.Reader could allocate an unbounded amount of memory when reading a maliciously-crafted archive containing a large number of sparse regions encoded in the "old GNU sparse map" format.
We now limit both the number of old GNU sparse map extension blocks, and the total number of sparse file entries, regardless of encoding.
Thanks to Colin Walters ([email protected]) who initially reported this issue.
Thanks also to Uuganbayar Lkhamsuren (https://github.com/uug4na) and Jakub Ciolek who additionally reported this issue.
This is CVE-2026-32288 and Go issue https://go.dev/issue/78301.crypto/tls: multiple key update handshake messages can cause connection to deadlock
If one side of the TLS connection sends multiple key update messages post-handshake in a single record, the connection can deadlock, causing uncontrolled consumption of resources. This can lead to a denial of service.
This only affects TLS 1.3.
Thank you to Jakub Ciolek - https://ciolek.dev/ for reporting this issue.
This is CVE-2026-32283 and Go issue https://go.dev/issue/78334.cmd/go: trust layer bypass when using cgo and SWIG
A well-crafted SWIG source file could take advantage of a file-naming convention used inside the trust boundary of the cgo compiler. Doing so could result in arbitrary code execution during build time.
SWIG files are disallowed from using this convention.
Thank you to Juho Forsén of Mattermost for reporting this issue.
This is CVE-2026-27140 and Go issue https://go.dev/issue/78335.crypto/x509: unexpected work during chain building
During chain building, the amount of work that is done is not correctly limited when a large number of intermediate certificates are passed in VerifyOptions.Intermediates, which can lead to a denial of service. This affects both direct users of crypto/x509 and users of crypto/tls.
Thank you to Jakub Ciolek - https://ciolek.dev/ for reporting this issue.
This is CVE-2026-32280 and Go issue https://go.dev/issue/78282.crypto/x509: inefficient policy validation
Validating certificate chains which use policies is unexpectedly inefficient when certificates in the chain contain a very large number of policy mappings, possibly causing denial of service.
This only affects validation of otherwise trusted certificate chains, issued by a root CA in the VerifyOptions.Roots CertPool, or in the system certificate pool.
Thank you to Jakub Ciolek - https://ciolek.dev/ for reporting this issue.
This is CVE-2026-32281 and Go issue https://go.dev/issue/78281.
View the release notes for more information:
https://go.dev/doc/devel/release#go1.26.2You can download binary and source distributions from the Go website:
https://go.dev/dl/To compile from source using a Git clone, update to the release with git checkout go1.26.2 and build as usual.
Thanks to everyone who contributed to the releases.
Cheers,
Original source Report a problem
David and Junyang for the Go team - Mar 10, 2026
- Date parsed from source:Mar 10, 2026
- First seen by Releasebot:Mar 11, 2026
//go:fix inline and the source-level inliner
Google presents Go 1.26 with an all-new source-level inliner powering the go fix tool for self-service migrations and safe code updates. The feature tour explains inlining, side effects, and examples like replacing ioutil.ReadFile, framing a tidier, more automated path to modern Go code.
Alan Donovan
10 March 2026
Source-level inlining
Go 1.26 contains an all-new implementation of the
go fix
subcommand, designed to help you keep your Go code up-to-date and modern. For an introduction, start by reading our
recent post
on the topic. In this post, we’ll look at one particular feature, the source-level inliner.While
go fix
has several bespoke modernizers for specific new language and library features, the source-level inliner is the first fruit of our efforts to provide “
self-service
” modernizers and analyzers. It enables any package author to express simple API migrations and updates in a straightforward and safe way. We’ll first explain what the source-level inliner is and how you can use it, then we’ll dive into some aspects of the problem and the technology behind it.Source-level inlining Go to source-level-inlining
Source-level inlining
In 2023, we built an
algorithm
for source-level inlining of function calls in Go. To “inline” a call means to replace the call by a copy of the body of the called function, substituting arguments for parameters. We call it “source-level” inlining because it durably modifies the source code. By contrast, the inlining algorithm found in a typical compiler, including Go’s, applies a similar transformation, but to the compiler’s ephemeral
intermediate representation
, to generate more efficient code.If you’ve ever invoked
gopls
’s “
Inline call
” interactive refactoring, you’ve used the source-level inliner. (In VS Code, this code action can be found on the “Source Action…” menu.) The before-and-after screenshots below show the effect of inlining the call to
sum
from the function named
six
.The inliner is a crucial building block for a number of source transformation tools. For example, gopls uses it for the “Change signature” and “Remove unused parameter” refactorings because, as we’ll see below, it takes care of many subtle correctness issues that arise when refactoring function calls.
This same inliner is also one of the analyzers in the all-new
go fix
command. In
go fix
, it enables self-service API migration and upgrades using a new
//go:fix inline
directive comment. Let’s take a look at a few examples of how this works and what it can be used for.Example: renaming ioutil.ReadFile Go to example-renaming-ioutilreadfile
Example: renaming
ioutil.ReadFileIn Go 1.16, the
ioutil.ReadFile
function, which reads the content of a file, was deprecated in favor of the new
os.ReadFile
function. In effect, the function was renamed, though of course Go’s
compatibility promise
prevents us from ever removing the old name.package ioutil
import "os"
// ReadFile reads the file named by filename…
// Deprecated: As of Go 1.16, this function simply calls [os.ReadFile].
func ReadFile(filename string) ([]byte, error) {
return os.ReadFile(filename)
}Ideally, we would like to change every Go program in the world to stop using
ioutil.ReadFile
and to call
os.ReadFile
instead. The inliner can help us do that. First we annotate the old function with
//go:fix inline
. This comment tells the tool that any time it sees a call to this function, it should inline the call.package ioutil
import "os"
// ReadFile reads the file named by filename…
// Deprecated: As of Go 1.16, this function simply calls [os.ReadFile].
//go:fix inline
func ReadFile(filename string) ([]byte, error) {
return os.ReadFile(filename)
}When we run
go fix
on a file containing a call to
ioutil.ReadFile
, it applies the replacement:$ go fix -diff ./...
-import "io/ioutil"
+import "os"- data, err := ioutil.ReadFile("hello.txt")
- data, err := os.ReadFile("hello.txt")
The call has been inlined, in effect replacing a call to one function by a call to another.
Because the inliner replaces a function call by a copy of the body of the called function, not by some arbitrary expression, in principle the transformation should not change the program’s behavior (barring code that inspects the call stack, of course). This differs from other tools that allow for arbitrary rewrites, such as
gofmt -r
, which are very powerful but need to be watched closely.For many years now, our Google colleagues on the teams supporting Java, Kotlin, and C++ have been using source-level inliner tools like this. To date, these tools have eliminated millions of calls to deprecated functions in Google’s code base. Users simply add the directives, and wait. During the night, robots quietly prepare, test, and submit batches of code changes across a monorepo of billions of lines of code. If all goes well, by the morning the old code is no longer in use and can be safely deleted. Go’s inliner is a relative newcomer, but it has already been used to prepare more than 18,000 changelists to Google’s monorepo.
Example: fixing API design flaws Go to example-fixing-api-design-flaws
Example: fixing API design flaws
With a little creativity, a variety of migrations can be expressed as inlinings. Consider this hypothetical
oldmath
package:// Package oldmath is the bad old math package.
package oldmath// Sub returns x - y.
func Sub(y, x int) int// Inf returns positive infinity.
func Inf() float64// Neg returns -x.
func Neg(x int) intIt has several design flaws: the
Sub
function declares its parameters in the wrong order; the
Inf
function implicitly prefers one of the two infinities; and the
Neg
function is redundant with
Sub
. Fortunately we have a
newmath
package that avoids these mistakes, and we’d like to get users to switch to it. The first step is to implement the old API in terms of the new package and to deprecate the old functions. Then we add inliner directives:// Package oldmath is the bad old math package.
package oldmathimport "newmath"
// Sub returns x - y.
// Deprecated: the parameter order is confusing.
//go:fix inline
func Sub(y, x int) int {
return newmath.Sub(x, y)
}// Inf returns positive infinity.
// Deprecated: there are two infinite values; be explicit.
//go:fix inline
func Inf() float64 {
return newmath.Inf(+1)
}// Neg returns -x.
// Deprecated: this function is unnecessary.
//go:fix inline
func Neg(x int) int {
return newmath.Sub(0, x)
}Now, when users of
oldmath
run the
go fix
command on their code, it will replace all calls to the old functions by their new counterparts. By the way, gopls has included
inline
in its analyzer suite for some time, so if your editor uses gopls, the moment you add the
//go:fix inline
directives you should start seeing a diagnostic at each call site, such as “call of
oldmath.Sub
should be inlined”, along with a suggested fix that inlines that particular call.For example, this old code:
import "oldmath"
var nine = oldmath.Sub(1, 10) // diagnostic: "call to oldmath.Sub should be inlined"
will be transformed to:
import "newmath"
var nine = newmath.Sub(10, 1)
Observe that after the fix, the arguments to
Sub
are in the logical order. This is progress! If you’re in luck, the inliner will succeed at removing every call to the functions in
oldmath
, perhaps allowing you to delete it as a dependency.The
inline
analyzer works on types and constants too. If our
oldmath
package had originally declared a data type for rational numbers and a constant for π, we could use the following forwarding declarations to migrate them to the
newmath
package while preserving the behavior of existing code:package oldmath
//go:fix inline
type Rational = newmath.Rational//go:fix inline
const Pi = newmath.PiEach time the
inline
analyzer encounters a reference to
oldmath.Rational
or
oldmath.Pi
, it will update them to refer instead to
newmath
.Under the hood of the inliner
Under the hood of the inliner
At a glance, source inlining seems straightforward: just replace the call with the body of the callee function, introduce variables for the function parameters, and bind the call arguments to those variables. But handling all of the complexities and corner cases correctly while producing acceptable results is no small technical challenge: the inliner is about 7,000 lines of dense, compiler-like logic. Let’s look at six aspects of the problem that make it so tricky.
1. Parameter elimination
- Parameter elimination
One of the inliner’s most important tasks is to attempt to replace each occurrence of a parameter in the callee by its corresponding argument from the call. In the simplest case, the argument is a trivial literal such as
0
or
""
, so the replacement is straightforward and the parameter can be eliminated.//go:fix inline
func show(prefix, item string) {
fmt.Println(prefix, item)
}show("", "hello")
fmt.Println("", "hello")
For less trivial literals such as
404
or
"go.dev"
, the replacement is equally straightforward, so long as the parameter appears in the callee at most once. But if it appears multiple times, it would be bad style to sprinkle copies of these magic values throughout the code as it would obscure the relationship between them; a later change to only one of them might create an inconsistency.In such cases the inliner must tread carefully and emit a more conservative result. Whenever one or more parameters cannot be completely substituted for any reason, the inliner inserts an explicit “parameter binding” declaration:
//go:fix inline
func printPair(before, x, y, after string) {
fmt.Println(before, x, after)
fmt.Println(before, y, after)
}printPair("[", "one", "two", "]")
// a “parameter binding” declaration
var before, after = "[", "]"
fmt.Println(before, "one", after)
fmt.Println(before, "two", after)2. Side effects
- Side effects
In Go, as in all imperative programming languages, calling a function may have the side effect of updating variables, which in turn may affect the behavior of other functions. Consider the call to
add
below:func add(x, y int) int { return y + x }
z = add(f(), g())
A trivial inlining of the call would replace
x
with
f()
and
y
with
g()
, with this result:z = g() + f()
But this result is incorrect because evaluation of
g()
now occurs before
f()
; if the two functions have side effects, those effects will now be observed in a different order and may affect the result of the expression. Of course, it is bad form to write code that relies on effect ordering among call arguments, but that doesn’t mean people don’t do it, and our tools have to get it right.So, the inliner must attempt to prove that
f()
and
g()
do not have side effects on each other. On success, it can safely proceed with the result above. Otherwise, it must fall back to an explicit parameter binding:var x = f()
z = g() + xWhen considering side effects, it’s not only the argument expressions that matter. Also significant is the order in which parameters are evaluated relative to other code in the callee. Consider this call to
add2
://go:fix inline
func add2(x, y int) int {
return x + other() + y
}add2(f(), g())
This time, parameters
x
and
y
are used in the same order they are declared, so the substitution
f() + other() + g()
won’t change the order of effects of
f()
and
g()
—but it will change the order of any effects of
other()
and
g()
. Furthermore, if the function body uses a parameter within a loop, substitution might change the cardinality of effects.The inliner uses a novel
hazard analysis
to model the order of effects in each callee function. Nonetheless, its ability to construct the necessary safety proofs is quite limited. For example, if the calls
f()
and
g()
are simple accessors, it would be perfectly safe to call them in either order. Indeed, an optimizing compiler might use its knowledge of the internals of
f
and
g
to safely reorder the two calls. But unlike a compiler, which generates object code that reflects the source at a specific moment, the purpose of the inliner is to make permanent changes to the source, so it can’t take advantage of ephemeral details. As an extreme example, consider this
start
function:func start() { /* TODO: implement */ }
An optimizing compiler is free to delete each call to
start()
because it has no effects today, but the inliner is not, because it may become important tomorrow.In short, the inliner may produce results that—to the informed eye of a project maintainer—are clearly too conservative. In such cases, the fixed code would benefit stylistically from a little manual cleanup.
3. “Fallible” constant expressions
- “Fallible” constant expressions
You might imagine (as I once did) that it would always be safe to replace a parameter variable by a constant argument of the same type. Surprisingly, this turns out not to be the case, because some checks previously done at run time would now happen—and fail—at compile time. Consider this call to the
index
function://go:fix inline
func index(s string, i int) byte {
return s[i]
}index("", 0)
A naive inliner might replace
s
with
""
and
i
with
0
, resulting in
""[0]
, but this is not actually a legal Go expression because this particular index is out of bounds for this particular string. Because the expression
""[0]
is composed of constants, it is evaluated at compile time, and a program that contains it will not even build. By contrast, the original program would fail only if execution reaches this call to
index
, which presumably in a working program it does not.Consequently, the inliner must keep track of all expressions and their operands that might become constant during parameter substitution, triggering additional compile-time checks. It builds a
constraint system
and attempts to solve it. Each unsatisfied constraint is resolved by adding an explicit binding for the constrained parameters.4. Shadowing
- Shadowing
Typical argument expressions contain one or more identifiers that refer to symbols (variables, functions, and so on) in the caller’s file. The inliner must make sure that each name in the argument expression would refer to the same symbol after parameter substitution; in other words, none of the caller’s names is
shadowed
in the callee. If this fails, the inliner must again insert parameter bindings, as in this example://go:fix inline
func f(val string) {
x := 123
fmt.Println(val, x)
}x := "hello"
f(x)x := "hello"
{
// another “parameter binding” declaration
// to read the caller's x before shadowing it
var val string = x
x := 123
fmt.Println(val, x)
}Conversely, the inliner must also check that each name in the
callee
function body would refer to the same thing when it is spliced into the call site. In other words, none of the callee’s names is shadowed or missing in the caller. For missing names, the inliner may need to insert additional imports.5. Unused variables
- Unused variables
When an argument expression has no effects and its corresponding parameter is never used, the expression may be eliminated. However, if the expression contains the last reference to a local variable at the caller, this may cause a compile error because the variable is now unused.
//go:fix inline
func f(_ int) { print("hello") }x := 42
f(x)x := 42 // error: unused variable: x
print("hello")So the inliner must account for references to local variables and avoid removing the last one. (Of course it is still possible that two different inliner fixes each remove the
second
-to-last reference to a variable, so the two fixes are valid in isolation but not together; see the discussion of
semantic conflicts
in the previous post. Unfortunately manual cleanup is inevitably required in this case.)6. Defer
- Defer
In some cases, it is simply impossible to inline away the call. Consider a call to a function that uses a
defer
statement: if we were to eliminate the call, the deferred function would execute when the
caller
function returns, which is too late. All we can safely do when the callee uses
defer
is to put the body of the callee in a function literal and immediately call it. This function literal,
func() { … }()
, delimits the lifetime of the
defer
statement, as in this example://go:fix inline
func callee() {
defer f()
…
}callee()
func() {
defer f()
…
}()If you invoke the inliner in gopls, you’ll see that it makes the change shown above and introduces the function literal. This result may be appropriate in an interactive setting, since you are likely to immediately tweak the code (or undo the fix) as you prefer, but it is rarely desirable in a batch tool, so as a matter of policy the analyzer in
go fix
refuses to inline such “literalized” calls.An optimizing compiler for tidiness
An optimizing compiler for “tidiness”
We’ve now seen half a dozen examples of how the inliner handles tricky semantic edge cases correctly. (Many thanks to Rob Findley, Jonathan Amsterdam, and Olena Synenka for insights, discussions, reviews, features, and fixes.) By putting all of the smarts into the inliner, users can simply apply an “Inline call” refactoring in their IDE or add a
//go:fix inline
directive to their own functions and be confident that the resulting code transformations can be applied with only the most cursory review.Although we have made good progress toward that goal, we have not yet fully attained it, and it is likely that we never will. Consider a compiler. A sound compiler produces correct output for any input and never miscompiles your code; this is the fundamental expectation that every user should have of their compiler. An
optimizing
compiler produces code carefully chosen for speed without compromising on safety. Similarly, an inliner is a bit like an optimizing compiler whose goal is not speed but
tidiness
: inlining a call must never change the behavior of your program, and ideally it produces code that is maximally neat and tidy. Unfortunately, an optimizing compiler is
provably
never done: showing that two different programs are equivalent is an undecidable problem, and there will always be improvements that an expert knows are safe but the compiler cannot prove. So too with the inliner: there will always be cases where the inliner’s output is too fussy or otherwise stylistically inferior to that of a human expert, and there will always be more “tidiness optimizations” to add.Try it out! Go to try-it-out
Try it out!
Try it out!
We hope this tour of the inliner gives you a sense of some of the challenges involved, and of our priorities and directions in providing sound, self-service code transformation tools. Please try out the inliner, either interactively in your IDE, or through
//go:fix inline
directives and the
go fix
command, and share with us your experiences and any ideas you have for further improvements or new tools.Previous article: Allocating on the Stack
Blog Index
Original source Report a problem All of your release notes in one feed
Join Releasebot and get updates from Google and hundreds of other software products.
- March 2026
- No date parsed from source.
- First seen by Releasebot:Mar 6, 2026
[security] Go 1.26.1 and Go 1.25.8 are released
Go releases 1.26.1 and 1.25.8 bring five security fixes across crypto/x509, html/template, net/url and os. Highlights address certificate verification constraints, panics on malformed certs, meta tag URL escaping, IPv6 literal validation, and directory FileInfo root escapes. Includes download links.
Go releases: Go 1.26.1 and 1.25.8
Hello gophers,
We have just released Go versions 1.26.1 and 1.25.8, minor point releases.
These releases include 5 security fixes following the security policy:
crypto/x509: incorrect enforcement of email constraints
When verifying a certificate chain which contains a certificate containing
multiple email address constraints (composed of the full email address) which
share common local portions (the portion of the address before the '@'
character) but different domain portions (the portion of the address after the
'@' character), these constraints will not be properly applied, and only the
last constraint will be considered.This can allow certificates in the chain containing email addresses which are
either not permitted or excluded by the relevant constraints to be returned by
calls to Certificate.Verify. Since the name constraint checks happen after chain
building is complete, this only applies to certificate chains which chain to
trusted roots (root certificates either in VerifyOptions.Roots or in the system
root certificate pool), requiring a trusted CA to issue certificates containing
either not permitted or excluded email addresses.This issue only affects Go 1.26.
Thanks to Jakub Ciolek for reporting this issue.
This is CVE-2026-27137 and Go issue https://go.dev/issue/77952.
crypto/x509: panic in name constraint checking for malformed certificates
Certificate verification can panic when a certificate in the chain has an empty
DNS name and another certificate in the chain has excluded name constraints.
This can crash programs that are either directly verifying X.509 certificate
chains, or those that use TLS.Since the name constraint checks happen after chain building is complete, this
only applies to certificate chains which chain to trusted roots (root
certificates either in VerifyOptions.Roots or in the system root certificate
pool), requiring a trusted CA to issue certificates containing malformed DNS
names.This issue only affects Go 1.26.
Thanks to Jakub Ciolek for reporting this issue.
This is CVE-2026-27138 and Go issue https://go.dev/issue/77953.
html/template: URLs in meta content attribute actions are not escaped
Actions which insert URLs into the content attribute of HTML meta tags are not
escaped. This can allow XSS if the meta tag also has an http-equiv attribute
with the value "refresh".A new GODEBUG setting has been added, htmlmetacontenturlescape, which can be
used to disable escaping URLs in actions in the meta content attribute which
follow "url=" by setting htmlmetacontenturlescape=0.This is CVE-2026-27142 and Go issue https://go.dev/issue/77954.
net/url: reject IPv6 literal not at start of host
The Go standard library function net/url.Parse insufficiently
validated the host/authority component and accepted some invalid URLs
by effectively treating garbage before an IP-literal as ignorable.
The function should have rejected this as invalid.To prevent this behavior, net/url.Parse now rejects IPv6 literals
that do not appear at the start of the host subcomponent of a URL.Thanks to Masaki Hara (https://github.com/qnighy) of Wantedly.
This is CVE-2026-25679 and Go issue https://go.dev/issue/77578.
os: FileInfo can escape from a Root
On Unix platforms, when listing the contents of a directory using
File.ReadDir or File.Readdir the returned FileInfo could reference
a file outside of the Root in which the File was opened.The contents of the FileInfo were populated using the lstat system
call, which takes the path to the file as a parameter. If a component
of the full path of the file described by the FileInfo is replaced with
a symbolic link, the target of the lstat can be directed to another
location on the filesystem.The impact of this escape is limited to reading metadata provided by
lstat from arbitrary locations on the filesystem. This could be used
to probe for the presence or absence of files as well as gleaning
metadata like file sizes, but does not permit reading or writing files
outside the root.The FileInfo is now populated using fstatat.
Thank you to Miloslav Trmač of Red Hat for reporting this issue.
This is CVE-2026-27139 and Go issue https://go.dev/issue/77827.
View the release notes for more information:
https://go.dev/doc/devel/release#go1.26.1You can download binary and source distributions from the Go website:
https://go.dev/dl/To compile from source using a Git clone, update to the release with
git checkout go1.26.1
and build as usual.Thanks to everyone who contributed to the releases.
Cheers,
Original source Report a problem
Cherry and David for the Go team - Feb 27, 2026
- Date parsed from source:Feb 27, 2026
- First seen by Releasebot:Feb 28, 2026
Allocating on the Stack
Go 1.26 brings automatic stack-allocated backing stores for small slices, dramatically reducing heap allocations and GC work. The article traces progress from 1.24/1.25 and shows the compiler now moves escaping slices to the heap only when needed, boosting speed and memory efficiency.
Keith Randall
27 February 2026We’re always looking for ways to make Go programs faster. In the last 2 releases, we have concentrated on mitigating a particular source of slowness, heap allocations. Each time a Go program allocates memory from the heap, there’s a fairly large chunk of code that needs to run to satisfy that allocation. In addition, heap allocations present additional load on the garbage collector. Even with recent enhancements like Green Tea, the garbage collector still incurs substantial overhead.
So we’ve been working on ways to do more allocations on the stack instead of the heap. Stack allocations are considerably cheaper to perform (sometimes completely free). Moreover, they present no load to the garbage collector, as stack allocations can be collected automatically together with the stack frame itself. Stack allocations also enable prompt reuse, which is very cache friendly.
Stack allocation of constant-sized slices Go to stack-allocation-of-constant-sized-slices
Stack allocation of constant-sized slices¶
Consider the task of building a slice of tasks to process:
func process(c chan task) { var tasks []task for t := range c { tasks = append(tasks, t) } processAll(tasks) }Let’s walk through what happens at runtime when pulling tasks from the channel
c
and adding them to the slice
tasks
.
On the first loop iteration, there is no backing store for
tasks
, so
append
has to allocate one. Because it doesn’t know how big the slice will eventually be, it can’t be too aggressive. Currently, it allocates a backing store of size 1.
On the second loop iteration, the backing store now exists, but it is full.
append
again has to allocate a new backing store, this time of size 2. The old backing store of size 1 is now garbage.
On the third loop iteration, the backing store of size 2 is full.
append
again has to allocate a new backing store, this time of size 4. The old backing store of size 2 is now garbage.
On the fourth loop iteration, the backing store of size 4 has only 3 items in it.
append
can just place the item in the existing backing store and bump up the slice length. Yay! No call to the allocator for this iteration.
On the fifth loop iteration, the backing store of size 4 is full, and
append
again has to allocate a new backing store, this time of size 8.
And so on. We generally double the size of the allocation each time it fills up, so we can eventually append most new tasks to the slice without allocation. But there is a fair amount of overhead in the “startup” phase when the slice is small. During this startup phase we spend a lot of time in the allocator, and produce a bunch of garbage, which seems pretty wasteful. And it may be that in your program, the slice never really gets large. This startup phase may be all you ever encounter.
If this code was a really hot part of your program, you might be tempted to start the slice out at a larger size, to avoid all of these allocations.func process2(c chan task) { tasks := make([]task, 0, 10) // probably at most 10 tasks for t := range c { tasks = append(tasks, t) } processAll(tasks) }This is a reasonable optimization to do. It is never incorrect; your program still runs correctly. If the guess is too small, you get allocations from
append
as before. If the guess is too large, you waste some memory.
If your guess for the number of tasks was a good one, then there’s only one allocation site in this program. The
make
call allocates a slice backing store of the correct size, and
append
never has to do any reallocation.
The surprising thing is that if you benchmark this code with 10 elements in the channel, you’ll see that you didn’t reduce the number of allocations to 1, you reduced the number of allocations to 0!
The reason is that the compiler decided to allocate the backing store on the stack. Because it knows what size it needs to be (10 times the size of a task) it can allocate storage for it in the stack frame of
process2
instead of on the heap1. Note that this depends on the fact that the backing store does not escape to the heap inside of
processAll
.### Stack allocation of variable-sized slices Go to stack-allocation-of-variable-sized-slices Stack allocation of variable-sized slices¶ But of course, hard coding a size guess is a bit rigid. Maybe we can pass in an estimated length? ```go func process3(c chan task, lengthGuess int) { tasks := make([]task, 0, lengthGuess) for t := range c { tasks = append(tasks, t) } processAll(tasks) }This lets the caller pick a good size for the
tasks
slice, which may vary depending on where this code is being called from.
Unfortunately, in Go 1.24 the non-constant size of the backing store means the compiler can no longer allocate the backing store on the stack. It will end up on the heap, converting our 0-allocation code to 1-allocation code. Still better than having
append
do all the intermediate allocations, but unfortunate.
But never fear, Go 1.25 is here!
Imagine you decide to do the following, to get the stack allocation only in cases where the guess is small:func process4(c chan task, lengthGuess int) { var tasks []task if lengthGuess <= 10 { tasks = make([]task, 0, 10) } else { tasks = make([]task, 0, lengthGuess) } for t := range c { tasks = append(tasks, t) } processAll(tasks) }Kind of ugly, but it would work. When the guess is small, you use a constant size
make
and thus a stack-allocated backing store, and when the guess is larger you use a variable size
make
and allocate the backing store from the heap.
But in Go 1.25, you don’t need to head down this ugly road. The Go 1.25 compiler does this transformation for you! For certain slice allocation locations, the compiler automatically allocates a small (currently 32-byte) slice backing store, and uses that backing store for the result of the
make
if the size requested is small enough. Otherwise, it uses a heap allocation as normal.
In Go 1.25,
process3
performs zero heap allocations, if
lengthGuess
is small enough that a slice of that length fits into 32 bytes. (And of course that
lengthGuess
is a correct guess for how many items are in
c
.)
We’re always improving the performance of Go, so upgrade to the latest Go release and be surprised by how much faster and memory efficient your program becomes!### Stack allocation of append-allocated slices Go to stack-allocation-of-append-allocated-slices Stack allocation of append-allocated slices¶ Ok, but you still don’t want to have to change your API to add this weird length guess. Anything else you could do? Upgrade to Go 1.26! ```go func process(c chan task) { var tasks []task for t := range c { tasks = append(tasks, t) } processAll(tasks) }In Go 1.26, we allocate the same kind of small, speculative backing store on the stack, but now we can use it directly at the
append
site.
On the first loop iteration, there is no backing store for
tasks
, so
append
uses a small, stack-allocated backing store as the first allocation. If, for instance, we can fit 4
task
s in that backing store, the first
append
allocates a backing store of length 4 from the stack.
The next 3 loop iterations append directly to the stack backing store, requiring no allocation.
On the 4th iteration, the stack backing store is finally full and we have to go to the heap for more backing store. But we have avoided almost all of the startup overhead described earlier in this article. No heap allocations of size, 1, 2, and 4, and none of the garbage that they eventually become. If your slices are small, maybe you will never have a heap allocation.### Stack allocation of append-allocated escaping slices Go to stack-allocation-of-append-allocated-escaping-slices Stack allocation of append-allocated escaping slices¶ Ok, this is all good when the tasks slice doesn’t escape. But what if I’m returning the slice? Then it can’t be allocated on the stack, right? Right! The backing store for the slice returned by extract below can’t be allocated on the stack, because the stack frame for extract disappears when extract returns. ```go func extract(c chan task) []task { var tasks []task for t := range c { tasks = append(tasks, t) } return tasks }But you might think, the
returned
slice can’t be allocated on the stack. But what about all those intermediate slices that just become garbage? Maybe we can allocate those on the stack?func extract2(c chan task) []task { var tasks []task for t := range c { tasks = append(tasks, t) } tasks2 := make([]task, len(tasks)) copy(tasks2, tasks) return tasks2 }Then the
tasks
slice never escapes
extract2
. It can benefit from all of the optimizations described above. Then at the very end of
extract2
, when we know the final size of the slice, we do one heap allocation of the required size, copy our
task
s into it, and return the copy.
But do you really want to write all that additional code? It seems error prone. Maybe the compiler can do this transformation for us?
In Go 1.26, it can!
For escaping slices, the compiler will transform the original
extract
code to something like this:func extract3(c chan task) []task { var tasks []task for t := range c { tasks = append(tasks, t) } tasks = runtime.move2heap(tasks) return tasks }runtime.move2heap
is a special compiler+runtime function that is the identity function for slices that are already allocated in the heap. For slices that are on the stack, it allocates a new slice on the heap, copies the stack-allocated slice to the heap copy, and returns the heap copy.
This ensures that for our original
extract
code, if the number of items fits in our small stack-allocated buffer, we perform exactly 1 allocation of exactly the right size. If the number of items exceeds the capacity our small stack-allocated buffer, we do our normal doubling-allocation once the stack-allocated buffer overflows.
The optimization that Go 1.26 does is actually better than the hand-optimized code, because it does not require the extra allocation+copy that the hand-optimized code always does at the end. It requires the allocation+copy only in the case that we’ve exclusively operated on a stack-backed slice up to the return point.
We do pay the cost for a copy, but that cost is almost completely offset by the copies in the startup phase that we no longer have to do. (In fact, the new scheme at worst has to copy one more element than the old scheme.)
Original source Report a problem### Wrapping up Wrapping up¶ Hand optimization can still be beneficial, especially if you have a good estimate of the slice size ahead of time. But hopefully the compiler will now catch a lot of the simple cases for you and allow you to focus on the remaining ones that really matter. There are a lot of details that the compiler needs to ensure to get all these optimizations right. If you think that one of these optimizations is causing correctness or (negative) performance issues for you, you can turn them off with -gcflags=all=-d=variablemakehash=n . If turning these optimizations off helps, please file an issue so we can investigate. ### Footnotes Go to footnotes Footnotes¶ 1 Go stacks do not have any alloca -style mechanism for dynamically-sized stack frames. All Go stack frames are constant sized. - Feb 17, 2026
- Date parsed from source:Feb 17, 2026
- First seen by Releasebot:Feb 18, 2026
Using go fix to modernize Go code
Go 1.26 ships a rewritten go fix with an integrated analysis framework, expanding modernizers and self-service tooling for code modernization. It unifies vet and fix, adds the newexpr fixer, and enables customizable, repo-wide improvements for maintainers and teams.
Alan Donovan
17 February 2026
The 1.26 release of Go this month includes a completely rewritten go fix subcommand. Go fix uses a suite of algorithms to identify opportunities to improve your code, often by taking advantage of more modern features of the language and library. In this post, we’ll first show you how to use go fix to modernize your Go codebase. Then in the second section we’ll dive into the infrastructure behind it and how it is evolving. Finally, we’ll present the theme of “self-service” analysis tools to help module maintainers and organizations encode their own guidelines and best practices.
Running go fix
Go to running-go-fix
Running go fix¶
The go fix command, like go build and go vet, accepts a set of patterns that denote packages. This command fixes all packages beneath the current directory:$ go fix ./...On success, it silently updates your source files. It discards any fix that touches generated files since the appropriate fix in that case is to the logic of the generator itself. We recommend running go fix over your project each time you update your build to a newer Go toolchain release. Since the command may fix hundreds of files, start from a clean git state so that the change consists only of edits from go fix; your code reviewers will thank you.
To preview the changes the above command would have made, use the -diff flag:
$ go fix -diff ./... --- dir/file.go (old) +++ dir/file.go (new) - eq := strings.IndexByte(pair, '=') - result[pair[:eq]] = pair[1+eq:] + before, after, _ := strings.Cut(pair, "=") + result[before] = after …You can list the available fixers by running this command:
$ go tool fix help …Registered analyzers:
- any replace interface{} with any
- buildtag check //go:build and // +build directives
- fmtappendf replace []byte(fmt.Sprintf) with fmt.Appendf
- forvar remove redundant re-declaration of loop variables
- hostport check format of addresses passed to net.Dial
- inline apply fixes based on 'go:fix inline' comment directives
- mapsloop replace explicit loops over maps with calls to maps package
- minmax replace if/else statements with calls to min or max
…
Adding the name of a particular analyzer shows its complete documentation:
$ go tool fix help forvarforvar: remove redundant re-declaration of loop variables
The forvar analyzer removes unnecessary shadowing of loop variables.
Before Go 1.22, it was common to writefor _, x := range s { x := x ... }
to create a fresh variable for each iteration. Go 1.22 changed the semantics
offorloops, making this pattern redundant. This analyzer removes the
unnecessaryx := xstatement.This fix only applies to
rangeloops.
By default, the go fix command runs all analyzers. When fixing a large project it may reduce the burden of code review if you apply fixes from the most prolific analyzers as separate code changes. To enable only specific analyzers, use the flags matching their names. For example, to run just the any fixer, specify the -any flag. Conversely, to run all the analyzers except selected ones, negate the flags, for instance -any=false.
As with go build and go vet, each run of the go fix command analyzes only a specific build configuration. If your project makes heavy use of files tagged for different CPUs or platforms, you may wish to run the command more than once with different values of GOARCH and GOOS for better coverage:$ GOOS=linux GOARCH=amd64 go fix ./... $ GOOS=darwin GOARCH=arm64 go fix ./... $ GOOS=windows GOARCH=amd64 go fix ./...Running the command more than once also provides opportunities for synergistic fixes, as we’ll see below.
Modernizers
Go to modernizers
Modernizers¶
The introduction of generics in Go 1.18 marked the end of an era of very few changes to the language spec and the start of a period of more rapid—though still careful—change, especially in the libraries. Many of the trivial loops that Go programmers routinely write, such as to gather the keys of a map into a slice, can now be conveniently expressed as a call to a generic function such as maps.Keys. Consequently these new features create many opportunities to simplify existing code.
In December 2024, during the frenzied adoption of LLM coding assistants, we became aware that such tools tended—unsurprisingly—to produce Go code in a style similar to the mass of Go code used during training, even when there were newer, better ways to express the same idea. Less obviously, the same tools often refused to use the newer ways even when directed to do so in general terms such as “always use the latest idioms of Go 1.25.” In some cases, even when explicitly told to use a feature, the model would deny that it existed. (See my 2025 GopherCon talk for more exasperating details.) To ensure that future models are trained on the latest idioms, we need to ensure that these idioms are reflected in the training data, which is to say the global corpus of open-source Go code.
Over the past year, we have built dozens of analyzers to identify opportunities for modernization. Here are three examples of the fixes they suggest:- minmax replaces an if statement by a use of Go 1.21’s min or max functions:
x := f() if x < 0 { x = 0 } if x > 100 { x = 100 } x := min(max(f(), 0), 100)- rangeint replaces a 3-clause for loop by a Go 1.22 range-over-int loop:
for i := 0; i < n; i++ { f() } for range n { f() }- stringscut (whose -diff output we saw earlier) replaces uses of strings.Index and slicing by Go 1.18’s strings.Cut:
i := strings.Index(s, ":") if i >= 0 { return s[:i] } before, _, ok := strings.Cut(s, ":") if ok { return before }These modernizers are included in gopls, to provide instant feedback as you type, and in go fix, so that you can modernize several entire packages at once in a single command. In addition to making code clearer, modernizers may help Go programmers learn about newer features. As part of the process of approving each new change to the language and standard library, the proposal review group now considers whether it should be accompanied by a modernizer. We expect to add more modernizers with each release.
Example: a modernizer for Go 1.26’s new(expr)
Go to example-a-modernizer-for-go-126s-newexprExample: a modernizer for Go 1.26’s new(expr)¶
Go 1.26 includes a small but widely useful change to the language specification. The built-in new function creates a new variable and returns its address. Historically, its sole argument was required to be a type, such as new(string), and the new variable was initialized to its “zero” value, such as "". In Go 1.26, the new function may be called with any value, causing it to create a variable initialized to that value, avoiding the need for an additional statement. For example:ptr := new(string) *ptr = "go1.25" ptr := new("go1.26")This feature filled a gap that had been discussed for over a decade and resolved one of the most popular proposals for a change to the language. It is especially convenient in code that uses a pointer type *T to indicate an optional value of type T, as is common when working with serialization packages such as json.Marshal or protocol buffers. This is such a common pattern that people often capture it in a helper, such as the newInt function below, saving the caller from the need to break out of an expression context to introduce additional statements:
type RequestJSON struct { URL string Attempts *int // (optional) } data, err := json.Marshal(&RequestJSON{ URL: url, Attempts: newInt(10), }) func newInt(x int) *int { return &x }Helpers such as newInt are so frequently needed with protocol buffers that the proto API itself provides them as proto.Int64, proto.String, and so on. But Go 1.26 makes all these helpers unnecessary:
data, err := json.Marshal(&RequestJSON{ URL: url, Attempts: new(10), })To help you take advantage of this feature, the go fix command now includes a fixer, newexpr, that recognizes “new-like” functions such as newInt and suggests fixes to replace the function body with return new(x) and to replace every call, whether in the same package or an importing package, with a direct use of new(expr).
To avoid introducing premature uses of new features, modernizers offer fixes only in files that require at least the minimum appropriate version of Go (1.26 in this instance), either through a go 1.26 directive in the enclosing go.mod file or a //go:build go1.26 build constraint in the file itself.
Run this command to update all calls of this form in your source tree:$ go fix -newexpr ./...At this point, with luck, all of your newInt-like helper functions will have become unused and may be safely deleted (assuming they aren’t part of a stable published API). A few calls may remain where it would be unsafe to suggest a fix, such as when the name new is locally shadowed by another declaration. You can also use the deadcode command to help identify unused functions.
Synergistic fixes
Go to synergistic-fixes
Synergistic fixes¶
Applying one modernization may create opportunities to apply another. For example, this snippet of code, which clamps x to the range 0–100, causes the minmax modernizer to suggest a fix to use max. Once that fix is applied it suggests a second fix, this time to use min.x := f() if x < 0 { x = 0 } if x > 100 { x = 100 } x := min(max(f(), 0), 100)Synergies may also occur between different analyzers. For example, a common mistake is to repeatedly concatenate strings within a loop, resulting in quadratic time complexity—a bug and a potential vector for a denial-of-service attack. The stringsbuilder modernizer recognizes the problem and suggests using Go 1.10’s strings.Builder:
s := "" for _, b := range bytes { s += fmt.Sprintf("%02x", b) } use(s)var s strings.Builder for _, b := range bytes { s.WriteString(fmt.Sprintf("%02x", b)) } use(s.String())Once this fix is applied, a second analyzer may recognize that the WriteString and Sprintf operations can be combined as fmt.Fprintf(&s, "%02x", b), which is both cleaner and more efficient, and offer a second fix. (This second analyzer is QF1012 from Dominik Honnef’s staticcheck, which is already enabled in gopls but not yet in go fix, though we plan to add staticcheck analyzers to the go command starting in Go 1.27.)
Consequently, it may be worth running go fix more than once until it reaches a fixed point; twice is usually enough.Merging fixes and conflicts
Go to merging-fixes-and-conflicts
Merging fixes and conflicts¶
A single run of go fix may apply dozens of fixes within the same source file. All fixes are conceptually independent, analogous to a set of git commits with the same parent. The go fix command uses a simple three-way merge algorithm to reconcile the fixes in sequence, analogous to the task of merging a set of git commits that edit the same file. If a fix conflicts with the list of edits accumulated so far, it is discarded, and the tool issues a warning that some fixes were skipped and that the tool should be run again.
This reliably detects syntactic conflicts arising from overlapping edits, but another class of conflict is possible: a semantic conflict occurs when two changes are textually independent but their meanings are incompatible. As an example consider two fixes that each remove the second-to-last use of a local variable: each fix is fine by itself, but when both are applied together the local variable becomes unused, and in Go that’s a compilation error. Neither fix is responsible for removing the variable declaration, but someone has to do it, and that someone is the user of go fix.
A similar semantic conflict arises when a set of fixes causes an import to become unused. Because this case is so common, the go fix command applies a final pass to detect unused imports and remove them automatically.
Semantic conflicts are relatively rare. Fortunately they usually reveal themselves as compilation errors, making them impossible to overlook. Unfortunately, when they happen, they do demand some manual work after running go fix.
Let’s now delve into the infrastructure beneath these tools.The Go analysis framework
Go to the-go-analysis-framework
The Go analysis framework¶
Since the earliest days of Go, the go command has had two subcommands for static analysis, go vet and go fix, each with its own suite of algorithms: “checkers” and “fixers”. A checker reports likely mistakes in your code, such as passing a string instead of an integer as the operand of a fmt.Printf("%d") conversion. A fixer safely edits your code to fix a bug or to express the same thing in a better way, perhaps more clearly, concisely, or efficiently. Sometimes the same algorithm appears in both suites when it can both report a mistake and safely fix it.
In 2017 we redesigned the then-monolithic go vet program to separate the checker algorithms (now called “analyzers”) from the “driver”, the program that runs them; the result was the Go analysis framework. This separation enables an analyzer to be written once then run in a diverse range of drivers for different environments, such as:- unitchecker, which turns a suite of analyzers into a subcommand that can be run by the go command’s scalable incremental build system, analogous to a compiler in go build. This is the basis of go fix and go vet.
- nogo, the analogous driver for alternative build systems such as Bazel and Blaze.
- singlechecker, which turns an analyzer into a standalone command that loads, parses, and type-checks a set of packages (perhaps a whole program) and then analyzes them. We often use it for ad hoc experiments and measurements over the module mirror (proxy.golang.org) corpus.
- multichecker, which does the same thing for a suite of analyzers with a ‘swiss-army knife’ CLI.
- gopls, the language server behind VS Code and other editors, which provides real-time diagnostics from analyzers after each editor keystroke.
- the highly configurable driver used by the staticcheck tool. (Staticcheck also provides a large suite of analyzers that can be run in other drivers.)
- Tricorder, the batch static analysis pipeline used by Google’s monorepo and integrated with its code review system.
- gopls’ MCP server, which makes diagnostics available to LLM-based coding agents, providing more robust “guardrails”.
- analysistest, the analysis framework’s test harness.
One benefit of the framework is its ability to express helper analyzers that don’t report diagnostics or suggest fixes of their own but instead compute some intermediate data structure that may be useful to many other analyzers, amortizing the costs of its construction. Examples include control-flow graphs, the SSA representation of function bodies, and data structures for optimized AST navigation.
Another benefit of the framework is its support for making deductions across packages. An analyzer can attach a “fact” to a function or other symbol so that information learned while analyzing the function’s body can be used when later analyzing a call to the function, even if the call appears in another package or the later analysis occurs in a different process. This makes it easy to define scalable interprocedural analyses. For example, the printf checker can tell when a function such as log.Printf is really just a wrapper around fmt.Printf, so it knows that calls to log.Printf should be checked in a similar manner. This process works by induction, so the tool will also check calls to further wrappers around log.Printf, and so on. An example of an analyzer that makes heavy use of facts is Uber’s nilaway, which reports potential mistakes resulting in nil pointer dereferences.
The process of “separate analysis” in go fix is analogous to the process of separate compilation in go build. Just as the compiler builds packages starting from the bottom of the dependency graph and passing type information up to importing packages, the analysis framework works from the bottom of the dependency graph up, passing facts (and types) up to importing packages.
In 2019, as we started developing gopls, the language server for Go, we added the ability for an analyzer to suggest a fix when reporting a diagnostic. The printf analyzer, for example, offers to replace fmt.Printf(msg) with fmt.Printf("%s", msg) to avoid misformatting should the dynamic msg value contain a % symbol. This mechanism has become the basis for many of the quick fixes and refactoring features of gopls.
While all these developments were happening to go vet, go fix remained stuck as it was back before the Go compatibility promise, when early adopters of Go used it to maintain their code during the rapid and sometimes incompatible evolution of the language and libraries.
The Go 1.26 release brings the Go analysis framework to go fix. The go vet and go fix commands have converged and are now almost identical in implementation. The only differences between them are the criteria for the suites of algorithms they use, and what they do with computed diagnostics. Go vet analyzers must detect likely mistakes with low false positives; their diagnostics are reported to the user. Go fix analyzers must generate fixes that are safe to apply without regression in correctness, performance, or style; their diagnostics may not be reported, but the fixes are directly applied. Aside from this difference of emphasis, the task of developing a fixer is no different from that of developing a checker.Improving analysis infrastructure
Go to improving-analysis-infrastructure
Improving analysis infrastructure¶
As the number of analyzers in go vet and go fix continues to grow, we have been investing in infrastructure both to improve the performance of each analyzer and to make it easier to write each new analyzer.
For example, most analyzers start by traversing the syntax trees of each file in the package looking for a particular kind of node such as a range statement or function literal. The existing inspector package makes this scan efficient by pre-computing a compact index of a complete traversal so that later traversals can quickly skip subtrees that don’t contain any nodes of interest. Recently we extended it with the Cursor datatype to allow flexible and efficient navigation between nodes in all four cardinal directions—up, down, left, and right, similar to navigating the elements of an HTML DOM—making it easy and efficient to express a query such as “find each go statement that is the first statement of a loop body”:var curFile inspector.Cursor = ... // Find each go statement that is the first statement of a loop body. for curGo := range curFile.Preorder((*ast.GoStmt)(nil)) { kind, index := curGo.ParentEdge() if kind == edge.BlockStmt_List && index == 0 { switch curGo.Parent().ParentEdgeKind() { case edge.ForStmt_Body, edge.RangeStmt_Body: ... } } }Many analyzers start by searching for calls to a specific function, such as fmt.Printf. Function calls are among the most numerous expressions in Go code, so rather than search every call expression and test whether it is a call to fmt.Printf, it is much more efficient to pre-compute an index of symbol references, which is done by typeindex and its helper analyzer. Then the calls to fmt.Printf can be enumerated directly, making the cost proportional to the number of calls instead of to the size of the package. For an analyzer such as hostport that seeks an infrequently used symbol (net.Dial), this can easily make it 1,000× faster.
Some other infrastructural improvements over the past year include:- a dependency graph of the standard library that analyzers can consult to avoid introducing import cycles. For example, we can’t introduce a call to strings.Cut in a package that is itself imported by strings.
- support for querying the effective Go version of a file as determined by the enclosing go.mod file and build tags, so that analyzers don’t insert uses of features that are “too new”.
- a richer library of refactoring primitives (e.g. “delete this statement”) that correctly handle adjacent comments and other tricky edge cases.
We have come a long way, but there remains much to do. Fixer logic can be tricky to get right. Since we expect users to apply hundreds of suggested fixes with only cursory review, it’s critical that fixers are correct even in obscure edge cases. As just one example (see my GopherCon talk for several more), we built a modernizer that replaces calls such as append([]string{}, slice...) by the clearer slices.Clone(slice) only to discover that, when slice is empty, the result of Clone is nil, a subtle behavior change that in rare cases can cause bugs; so we had to exclude that modernizer from the go fix suite.
Some of these difficulties for authors of analyzers can be ameliorated with better documentation (both for humans and LLMs), particularly checklists of surprising edge cases to consider and test. A pattern-matching engine for syntax trees, similar to those in staticcheck and Tree Sitter, could simplify the fiddly task of efficiently identifying the locations that need fixing. A richer library of operators for computing accurate fixes would help avoid common mistakes. A better test harness would let us check that fixes don’t break the build, and preserve dynamic properties of the target code. These are all on our roadmap.
The “self-service” paradigm
Go to the-self-service-paradigm
The “self-service” paradigm¶
Original source Report a problem
More fundamentally, we are turning our attention in 2026 to a “self-service” paradigm.
The newexpr analyzer we saw earlier is a typical modernizer: a bespoke algorithm tailored to a particular feature. The bespoke model works well for features of the language and standard library, but it doesn’t really help update uses of third-party packages. Although there’s nothing to stop you from writing a modernizer for your own public APIs and running it on your own project, there’s no automatic way to get users of your API to run it too. Your modernizer probably wouldn’t belong in gopls or the go vet suite unless your API is particularly widely used across the Go ecosystem. Even in that case you would have to obtain code reviews and approvals and then wait for the next release.
Under the self-service paradigm, Go programmers would be able to define modernizations for their own APIs that their users can apply without all the bottlenecks of the current centralized paradigm. This is especially important as the Go community and global Go corpus are growing much faster than the ability of our team to review analyzer contributions.
The go fix command in Go 1.26 includes a preview of the first fruits of this new paradigm: the annotation-driven source-level inliner, which we’ll describe in an upcoming companion blog post next week. In the coming year, we plan to investigate two more approaches within this paradigm.
First, we will be exploring the possibility of dynamically loading modernizers from the source tree and securely executing them, either in gopls or go fix. In this approach a package that provides an API for, say, a SQL database could additionally provide a checker for misuses of the API, such as SQL injection vulnerabilities or failure to handle critical errors. The same mechanism could be used by project maintainers to encode internal housekeeping rules, such as avoiding calls to certain problematic functions or enforcing stronger coding disciplines in critical parts of the code.
Second, many existing checkers can be informally described as “don’t forget to X after you Y!”, such as “close the file after you open it”, “cancel the context after you create it”, “unlock the mutex after you lock it”, “break out of the iterator loop after yield returns false”, and so on. What such checkers have in common is that they enforce certain invariants on all execution paths. We plan to explore generalizations and unifications of these control-flow checkers so that Go programmers can easily apply them to new domains, without complex analytical logic, simply by annotating their own code.
We hope that these new tools will save you effort during maintenance of your Go projects and help you learn about and benefit from newer features sooner. Please try out go fix on your projects and report any problems you find, and do share any ideas you have for new modernizers, fixers, checkers, or self-service approaches to static analysis. - February 2026
- No date parsed from source.
- First seen by Releasebot:Feb 10, 2026
Go 1.26.0 is released
Hello gophers,
We have just released Go 1.26.0.
To find out what has changed in Go 1.26, read the release notes:
https://go.dev/doc/go1.26You can download binary and source distributions from our download page:
https://go.dev/dl/#go1.26.0If you have Go installed already, an easy way to try go1.26.0
is by using the go command:$ go install golang.org/dl/go1.26.0@latest $ go1.26.0 downloadTo compile from source using a Git clone, update to the release with
git checkout go1.26.0and build as usual.
Thanks to everyone who contributed to the release!
Cheers,
Original source Report a problem
Junyang and Michael for the Go team - Feb 10, 2026
- Date parsed from source:Feb 10, 2026
- First seen by Releasebot:Feb 10, 2026
Go 1.26 is released
Go 1.26 ships with language refinements, performance boosts, and improved tooling. It adds an expression form for new, enables the Green Tea GC by default, and brings several compiler and library improvements along with experimental features to try.
Language changes
Go 1.26 introduces two significant refinements to the language syntax and type system.
First, the built-in new function, which creates a new variable, now allows its operand to be an expression, specifying the initial value of the variable.
A simple example of this change means that code such as this:
x := int64(300)
ptr := &x
Can be simplified to:ptr := new(int64(300))Second, generic types may now refer to themselves in their own type parameter list. This change simplifies the implementation of complex data structures and interfaces.
Performance improvements
The previously experimental Green Tea garbage collector is now enabled by default.
The baseline cgo overhead has been reduced by approximately 30%.
The compiler can now allocate the backing store for slices on the stack in more situations, which improves performance.
Tool improvements
The go fix command has been completely rewritten to use the Go analysis framework, and now includes a couple dozen “modernizers”, analyzers that suggest safe fixes to help your code take advantage of newer features of the language and standard library. It also includes the inline analyzer, which attempts to inline all calls to each function annotated with a //go:fix inline directive. Two upcoming blog posts will address these features in more detail.
More improvements and changes
Go 1.26 introduces many improvements over Go 1.25 across its tools, the runtime, compiler, linker, and the standard library. This includes the addition of three new packages: crypto/hpke, crypto/mlkem/mlkemtest, and testing/cryptotest. There are port-specific changes and GODEBUG settings updates.
Some of the additions in Go 1.26 are in an experimental stage and become exposed only when you explicitly opt in. Notably:
- An experimental simd/archsimd package provides access to “single instruction, multiple data” (SIMD) operations.
- An experimental runtime/secret package provides a facility for securely erasing temporaries used in code that manipulates secret information, typically cryptographic in nature.
- An experimental goroutineleak profile in the runtime/pprof package that reports leaked goroutines.
These experiments are all expected to be generally available in a future version of Go. We encourage you to try them out ahead of time. We really value your feedback!
Please refer to the Go 1.26 Release Notes for the complete list of additions, changes, and improvements in Go 1.26.
Over the next few weeks, follow-up blog posts will cover some of the topics relevant to Go 1.26 in more detail. Check back later to read those posts.
Thanks to everyone who contributed to this release by writing code, filing bugs, trying out experimental additions, sharing feedback, and testing the release candidates. Your efforts helped make Go 1.26 as stable as possible. As always, if you notice any problems, please file an issue.
We hope you enjoy using the new release!
Original source Report a problem - February 2026
- No date parsed from source.
- First seen by Releasebot:Feb 5, 2026
[security] Go 1.26 Release Candidate 3 is released
Go 1.26 RC3 is out with a TLS session resumption fix and notes on Config.GetConfigForClient. This release invites load testing and telemetry opt-in to validate the pre-release build and tighten security.
Go 1.26rc3 Release Notes
Hello gophers,
We have just released go1.26rc3, a release candidate version of Go 1.26.
It is cut from release-branch.go1.26 at the revision tagged go1.26rc3.
This release includes 1 security fix following the security policy:- crypto/tls: unexpected session resumption when using Config.GetConfigForClient
Config.GetConfigForClient is documented to use the original Config's session
ticket keys unless explicitly overridden. This can cause unexpected behavior if
the returned Config modifies authentication parameters, like ClientCAs: a
connection initially established with the parent (or a sibling) Config can be
resumed, bypassing the modified authentication requirements.
If ClientAuth is VerifyClientCertIfGiven or RequireAndVerifyClientCert (on the
server) or InsecureSkipVerify is false (on the client), crypto/tls now checks
that the root of the previously-verified chain is still in ClientCAs/RootCAs
when resuming a connection. - Go 1.26 Release Candidate 2, Go 1.25.6, and Go 1.24.12 had fixed a similar issue
related to session ticket keys being implicitly shared by Config.Clone. Since
this fix is broader, the Config.Clone behavior change has been reverted. - Note that VerifyPeerCertificate still behaves as documented: it does not apply
to resumed connections. Applications that use Config.GetConfigForClient or
Config.Clone and do not wish to blindly resume connections established with the
original Config must use VerifyConnection instead (or SetSessionTicketKeys or
SessionTicketsDisabled).
Thanks to Coia Prant (github.com/rbqvq) for reporting this issue.
This updates CVE-2025-68121 and Go issue https://go.dev/issue/77217 .
Please try your production load tests and unit tests with the new version.
Your help testing these pre-release versions is invaluable.
Report any problems using the issue tracker:
https://go.dev/issue/new
Please consider opting in to Go telemetry if you haven't already.
Go telemetry helps validate this release candidate and future Go releases.
You can opt in by running the following command:
$ go telemetry on
If you have Go installed already, an easy way to try go1.26rc3 is by using the go command:
$ go install
golang.org/dl/go1.26rc3@latest
$ go1.26rc3 download
You can download binary and source distributions from the usual place:
https://go.dev/dl/#go1.26rc3
To find out what has changed in Go 1.26, read the draft release notes:
https://tip.golang.org/doc/go1.26
Cheers,
Michael and Dmitri for the Go team
Telemetry and testing commands
Please try your production load tests and unit tests with the new version.
Your help testing these pre-release versions is invaluable.You can opt in by running the following command:
$ go telemetry onIf you have Go installed already, an easy way to try go1.26rc3 is by using the go command:
$ go install golang.org/dl/go1.26rc3@latest $ go1.26rc3 downloadYou can download binary and source distributions from the usual place:
https://go.dev/dl/#go1.26rc3To find out what has changed in Go 1.26, read the draft release notes:
https://tip.golang.org/doc/go1.26Cheers,
Original source Report a problem
Michael and Dmitri for the Go team - Feb 4, 2026
- Date parsed from source:Feb 4, 2026
- First seen by Releasebot:Feb 5, 2026
[security] Go 1.25.7 and Go 1.24.13 are released
Go releases 1.25.7 and 1.24.13 bring critical security fixes with safety improvements in cgo and TLS. They fix code smuggling in cgo doc strings and tighten session-resumption checks in crypto/tls to honor configured auth.
Go 1.25.7 and 1.24.13 Release Notes
Hello gophers,
We have just released Go versions 1.25.7 and 1.24.13, minor point releases.
These releases include 2 security fixes following the security policy :
cmd/cgo: remove user-content from doc strings in cgo ASTs. A discrepancy between how Go and C/C++ comments were parsed allowed for code smuggling into the resulting cgo binary. To prevent this behavior, the cgo compiler will no longer parse user-provided doc comments. Thank you to RyotaK (https://ryotak.net) of GMO Flatt Security Inc. for reporting this issue. This is CVE-2025-61732 and https://go.dev/issue/76697.
crypto/tls: unexpected session resumption when using Config.GetConfigForClient. Config.GetConfigForClient is documented to use the original Config's session ticket keys unless explicitly overridden. This can cause unexpected behavior if the returned Config modifies authentication parameters, like ClientCAs: a connection initially established with the parent (or a sibling) Config can be resumed, bypassing the modified authentication requirements. If ClientAuth is VerifyClientCertIfGiven or RequireAndVerifyClientCert (on the server) or InsecureSkipVerify is false (on the client), crypto/tls now checks that the root of the previously-verified chain is still in ClientCAs/RootCAs when resuming a connection. Go 1.26 Release Candidate 2, Go 1.25.6, and Go 1.24.12 had fixed a similar issue related to session ticket keys being implicitly shared by Config.Clone. Since this fix is broader, the Config.Clone behavior change has been reverted. Note that VerifyPeerCertificate still behaves as documented: it does not apply to resumed connections. Applications that use Config.GetConfigForClient or Config.Clone and do not wish to blindly resume connections established with the original Config must use VerifyConnection instead (or SetSessionTicketKeys or SessionTicketsDisabled). Thanks to Coia Prant (github.com/rbqvq) for reporting this issue. This updates CVE-2025-68121 and Go issue https://go.dev/issue/77217.
View the release notes for more information:
https://go.dev/doc/devel/release#go1.25.7You can download binary and source distributions from the Go website:
https://go.dev/dl/To compile from source using a Git clone, update to the release with
git checkout go1.25.7
and build as usual.Thanks to everyone who contributed to the releases.
Original source Report a problem
Cheers,
Michael and Dmitri for the Go team - January 2026
- No date parsed from source.
- First seen by Releasebot:Jan 16, 2026
Go 1.26 Release Candidate 2
Go 1.26 RC2 is released with six security fixes and TLS/session handling improvements, plus safer toolchain VCS behavior. It’s a clear release upgrade you should test in production load and unit tests. Get it via go1.26rc2 download and test now.
Go 1.26 Release Candidate go1.26rc2
Hello gophers,
We have just released go1.26rc2, a release candidate version of Go 1.26.
It is cut from release-branch.go1.26 at the revision tagged go1.26rc2.
This release includes 6 security fixes following the security policy :archive/zip: denial of service when parsing arbitrary ZIP archives
archive/zip used a super-linear file name indexing algorithm that is invoked the first time a file in an archive is opened. This can lead to a denial of service when consuming a maliciously constructed ZIP archive.
Thanks to Thanks to Jakub Ciolek for reporting this issue.
This is CVE-2025-61728 and Go issue https://go.dev/issue/77102 .net/http: memory exhaustion in Request.ParseForm
When parsing a URL-encoded form net/http may allocate an unexpected amount of
memory when provided a large number of key-value pairs. This can result in a
denial of service due to memory exhaustion.
Thanks to jub0bs for reporting this issue.
This is CVE-2025-61726 and Go issue https://go.dev/issue/77101 .crypto/tls: Config.Clone copies automatically generated session ticket keys, session resumption does not account for the expiration of full certificate chain
The Config.Clone methods allows cloning a Config which has already been passed
to a TLS function, allowing it to be mutated and reused.
If Config.SessionTicketKey has not been set, and Config.SetSessionTicketKeys has
not been called, crypto/tls will generate random session ticket keys and
automatically rotate them. Config.Clone would copy these automatically generated
keys into the returned Config, meaning that the two Configs would share session
ticket keys, allowing sessions created using one Config could be used to resume
sessions with the other Config. This can allow clients to resume sessions even
though the Config may be configured such that they should not be able to do so.
Config.Clone no longer copies the automatically generated session ticket keys.
Config.Clone still copies keys which are explicitly provided, either by setting
Config.SessionTicketKey or by calling Config.SetSessionTicketKeys.
This issue was discoverd by the Go Security team while investigating another
issue reported by Coia Prant (github.com/rbqvq).
Additionally, on the server side only the expiration of the leaf certificate, if
one was provided during the initial handshake, was checked when considering if a
session could be resumed. This allowed sessions to be resumed if an intermediate
or root certificate in the chain had expired.
Session resumption now takes into account of the full chain when determining if
the session can be resumed.
Thanks to Coia Prant (github.com/rbqvq) for reporting this issue.
This is CVE-2025-68121 and Go issue https://go.dev/issue/77113 .cmd/go: bypass of flag sanitization can lead to arbitrary code execution
Usage of 'CgoPkgConfig' allowed execution of the pkg-config
binary with flags that are not explicitly safe-listed.
To prevent this behavior, compiler flags resulting from usage
of 'CgoPkgConfig' are sanitized prior to invoking pkg-config.
Thank you to RyotaK (https://ryotak.net) of GMO Flatt Security Inc.
for reporting this issue.
This is CVE-2025-61731 and go.dev/issue/77100 .cmd/go: unexpected code execution when invoking toolchain
The Go toolchain supports multiple VCS which are used retrieving modules and
embedding build information into binaries.
On systems with Mercurial installed (hg) downloading modules (e.g. via go get or
go mod download) from non-standard sources (e.g. custom domains) can cause
unexpected code execution due to how external VCS commands are constructed.
On systems with Git installed, downloading and building modules with malicious
version strings could allow an attacker to write to arbitrary files on the
system the user has access to. This can only be triggered by explicitly
providing the malicious version strings to the toolchain, and does not affect
usage of @latest or bare module paths.
The toolchain now uses safer VCS options to prevent misinterpretation of
untrusted inputs. In addition, the toolchain now disallows module version
strings prefixed with a "-" or "/" character.
Thanks to splitline (@splitline) from DEVCORE Research Team for reporting this
issue.
This is CVE-2025-68119 and Go issue https://go.dev/issue/77099 .crypto/tls: handshake messages may be processed at the incorrect encryption level
During the TLS 1.3 handshake if multiple messages are sent in records that span
encryption level boundaries (for instance the Client Hello and Encrypted
Extensions messages), the subsequent messages may be processed before the
encryption level changes. This can cause some minor information disclosure if a
network-local attacker can inject messages during the handshake.
Thanks to Coia Prant (github.com/rbqvq) for reporting this issue.
This is CVE-2025-61730 and Go issue https://go.dev/issue/76443
Please try your production load tests and unit tests with the new version.
Your help testing these pre-release versions is invaluable.
Report any problems using the issue tracker:
https://go.dev/issue/new
If you have Go installed already, an easy way to try go1.26rc2
is by using the go command:$ go install golang.org/dl/go1.26rc2@latest $ go1.26rc2 downloadYou can download binary and source distributions from the usual place:
https://go.dev/dl/#go1.26rc2
To find out what has changed in Go 1.26, read the draft release notes:
https://tip.golang.org/doc/go1.26Cheers,
Original source Report a problem
Junyang and Michael for the Go team