Black Lives Matter. Support the Equal Justice Initiative.

The Go Blog

Command PATH security in Go

Russ Cox
19 January 2021

Today’s Go security release fixes an issue involving PATH lookups in untrusted directories that can lead to remote execution during the go get command. We expect people to have questions about what exactly this means and whether they might have issues in their own programs. This post details the bug, the fixes we have applied, how to decide whether your own programs are vulnerable to similar problems, and what you can do if they are.

Go command & remote execution

One of the design goals for the go command is that most commands – including go build, go doc, go get, go install, and go list – do not run arbitrary code downloaded from the internet. There are a few obvious exceptions: clearly go run, go test, and go generate do run arbitrary code – that's their job. But the others must not, for a variety of reasons including reproducible builds and security. So when go get can be tricked into executing arbitrary code, we consider that a security bug.

If go get must not run arbitrary code, then unfortunately that means all the programs it invokes, such as compilers and version control systems, are also inside the security perimeter. For example, we've had issues in the past in which clever use of obscure compiler features or remote execution bugs in version control systems became remote execution bugs in Go. (On that note, Go 1.16 aims to improve the situation by introducing a GOVCS setting that allows configuration of exactly which version control systems are allowed and when.)

Today's bug, however, was entirely our fault, not a bug or obscure feature of gcc or git. The bug involves how Go and other programs find other executables, so we need to spend a little time looking at that before we can get to the details.

Commands and PATHs and Go

All operating systems have a concept of an executable path ($PATH on Unix, %PATH% on Windows; for simplicity, we'll just use the term PATH), which is a list of directories. When you type a command into a shell prompt, the shell looks in each of the listed directories, in turn, for an executable with the name you typed. It runs the first one it finds, or it prints a message like “command not found.”

On Unix, this idea first appeared in Seventh Edition Unix's Bourne shell (1979). The manual explained:

The shell parameter $PATH defines the search path for the directory containing the command. Each alternative directory name is separated by a colon (:). The default path is :/bin:/usr/bin. If the command name contains a / then the search path is not used. Otherwise, each directory in the path is searched for an executable file.

Note the default: the current directory (denoted here by an empty string, but let's call it “dot”) is listed ahead of /bin and /usr/bin. MS-DOS and then Windows chose to hard-code that behavior: on those systems, dot is always searched first, automatically, before considering any directories listed in %PATH%.

As Grampp and Morris pointed out in their classic paper “UNIX Operating System Security” (1984), placing dot ahead of system directories in the PATH means that if you cd into a directory and run ls, you might get a malicious copy from that directory instead of the system utility. And if you can trick a system administrator to run ls in your home directory while logged in as root, then you can run any code you want. Because of this problem and others like it, essentially all modern Unix distributions set a new user's default PATH to exclude dot. But Windows systems continue to search dot first, no matter what PATH says.

For example, when you type the command

go version

on a typically-configured Unix, the shell runs a go executable from a system directory in your PATH. But when you type that command on Windows, cmd.exe checks dot first. If .\go.exe (or .\go.bat or many other choices) exists, cmd.exe runs that executable, not one from your PATH.

For Go, PATH searches are handled by exec.LookPath, called automatically by exec.Command. And to fit well into the host system, Go's exec.LookPath implements the Unix rules on Unix and the Windows rules on Windows. For example, this command

out, err := exec.Command("go", "version").CombinedOutput()

behaves the same as typing go version into the operating system shell. On Windows, it runs .\go.exe when that exists.

(It is worth noting that Windows PowerShell changed this behavior, dropping the implicit search of dot, but cmd.exe and the Windows C library SearchPath function continue to behave as they always have. Go continues to match cmd.exe.)

The Bug

When go get downloads and builds a package that contains import "C", it runs a program called cgo to prepare the Go equivalent of the relevant C code. The go command runs cgo in the directory containing the package sources. Once cgo has generated its Go output files, the go command itself invokes the Go compiler on the generated Go files and the host C compiler (gcc or clang) to build any C sources included with the package. All this works well. But where does the go command find the host C compiler? It looks in the PATH, of course. Luckily, while it runs the C compiler in the package source directory, it does the PATH lookup from the original directory where the go command was invoked:

cmd := exec.Command("gcc", "file.c")
cmd.Dir = "badpkg"

So even if badpkg\gcc.exe exists on a Windows system, this code snippet will not find it. The lookup that happens in exec.Command does not know about the badpkg directory.

The go command uses similar code to invoke cgo, and in that case there's not even a path lookup, because cgo always comes from GOROOT:

cmd := exec.Command(GOROOT+"/pkg/tool/"+GOOS_GOARCH+"/cgo", "file.go")
cmd.Dir = "badpkg"

This is even safer than the previous snippet: there's no chance of running any bad cgo.exe that may exist.

But it turns out that cgo itself also invokes the host C compiler, on some temporary files it creates, meaning it executes this code itself:

// running in cgo in badpkg dir
cmd := exec.Command("gcc", "tmpfile.c")

Now, because cgo itself is running in badpkg, not in the directory where the go command was run, it will run badpkg\gcc.exe if that file exists, instead of finding the system gcc.

So an attacker can create a malicious package that uses cgo and includes a gcc.exe, and then any Windows user that runs go get to download and build the attacker's package will run the attacker-supplied gcc.exe in preference to any gcc in the system path.

Unix systems avoid the problem first because dot is typically not in the PATH and second because module unpacking does not set execute bits on the files it writes. But Unix users who have dot ahead of system directories in their PATH and are using GOPATH mode would be as susceptible as Windows users. (If that describes you, today is a good day to remove dot from your path and to start using Go modules.)

(Thanks to RyotaK for reporting this issue to us.)

The Fixes

It's obviously unacceptable for the go get command to download and run a malicious gcc.exe. But what's the actual mistake that allows that? And then what's the fix?

One possible answer is that the mistake is that cgo does the search for the host C compiler in the untrusted source directory instead of in the directory where the go command was invoked. If that's the mistake, then the fix is to change the go command to pass cgo the full path to the host C compiler, so that cgo need not do a PATH lookup in to the untrusted directory.

Another possible answer is that the mistake is to look in dot during PATH lookups, whether happens automatically on Windows or because of an explicit PATH entry on a Unix system. A user may want to look in dot to find a command they typed in a console or shell window, but it's unlikely they also want to look there to find a subprocess of a subprocess of a typed command. If that's the mistake, then the fix is to change the cgo command not to look in dot during a PATH lookup.

We decided both were mistakes, so we applied both fixes. The go command now passes the full host C compiler path to cgo. On top of that, cgo, go, and every other command in the Go distribution now use a variant of the os/exec package that reports an error if it would have previously used an executable from dot. The packages go/build and go/import use the same policy for their invocation of the go command and other tools. This should shut the door on any similar security problems that may be lurking.

Out of an abundance of caution, we also made a similar fix in commands like goimports and gopls, as well as the libraries and, which invoke the go command as a subprocess. If you run these programs in untrusted directories – for example, if you git checkout untrusted repositories and cd into them and then run programs like these, and you use Windows or use Unix with dot in your PATH – then you should update your copies of these commands too. If the only untrusted directories on your computer are the ones in the module cache managed by go get, then you only need the new Go release.

After updating to the new Go release, you can update to the latest gopls by using:

GO111MODULE=on \
go get

and you can update to the latest goimports or other tools by using:

GO111MODULE=on \
go get

You can update programs that depend on, even before their authors do, by adding an explicit upgrade of the dependency during go get:

GO111MODULE=on \
go get

For programs that use go/build, it is sufficient for you to recompile them using the updated Go release.

Again, you only need to update these other programs if you are a Windows user or a Unix user with dot in the PATH and you run these programs in source directories you do not trust that may contain malicious programs.

Are your own programs affected?

If you use exec.LookPath or exec.Command in your own programs, you only need to be concerned if you (or your users) run your program in a directory with untrusted contents. If so, then a subprocess could be started using an executable from dot instead of from a system directory. (Again, using an executable from dot happens always on Windows and only with uncommon PATH settings on Unix.)

If you are concerned, then we've published the more restricted variant of os/exec as You can use it in your program by simply replacing

import "os/exec"


import exec ""

and recompiling.

Securing os/exec by default

We have been discussing on whether the Windows behavior of always preferring the current directory in PATH lookups (during exec.Command and exec.LookPath) should be changed. The argument in favor of the change is that it closes the kinds of security problems discussed in this blog post. A supporting argument is that although the Windows SearchPath API and cmd.exe still always search the current directory, PowerShell, the successor to cmd.exe, does not, an apparent recognition that the original behavior was a mistake. The argument against the change is that it could break existing Windows programs that intend to find programs in the current directory. We don’t know how many such programs exist, but they would get unexplained failures if the PATH lookups started skipping the current directory entirely.

The approach we have taken in may be a reasonable middle ground. It finds the result of the old PATH lookup and then returns a clear error rather than use a result from the current directory. The error returned from exec.Command("prog") when prog.exe exists looks like:

prog resolves to executable in current directory (.\prog.exe)

For programs that do change behavior, this error should make very clear what has happened. Programs that intend to run a program from the current directory can use exec.Command("./prog") instead (that syntax works on all systems, even Windows).

We have filed this idea as a new proposal,

A Proposal for Adding Generics to Go

Ian Lance Taylor
12 January 2021

Generics proposal

We’ve filed a Go language change proposal to add support for type parameters for types and functions, permitting a form of generic programming.

Why generics?

Generics can give us powerful building blocks that let us share code and build programs more easily. Generic programming means writing functions and data structures where some types are left to be specified later. For example, you can write a function that operates on a slice of some arbitrary data type, where the actual data type is only specified when the function is called. Or, you can define a data structure that stores values of any type, where the actual type to be stored is specified when you create an instance of the data structure.

Since Go was first released in 2009, support for generics has been one of the most commonly requested language features. You can read more about why generics are useful in an earlier blog post.

Although generics have clear use cases, fitting them cleanly into a language like Go is a difficult task. One of the first (flawed) attempts to add generics to Go dates back all the way to 2010. There have been several others over the last decade.

For the last couple of years we’ve been working on a series of design drafts that have culminated in a design based on type parameters. This design draft has had a lot of input from the Go programming community, and many people have experimented with it using the generics playground described in an earlier blog post. Ian Lance Taylor gave a talk at GopherCon 2019 about why to add generics and the strategy we are now following. Robert Griesemer gave a follow-up talk about changes in the design, and the implementation, at GopherCon 2020. The language changes are fully backward compatible, so existing Go programs will continue to work exactly as they do today. We have reached the point where we think that the design draft is good enough, and simple enough, to propose adding it to Go.

What happens now?

The language change proposal process is how we make changes to the Go language. We have now started this process to add generics to a future version of Go. We invite substantive criticisms and comments, but please try to avoid repeating earlier comments, and please try to avoid simple plus-one and minus-one comments. Instead, add thumbs-up/thumbs-down emoji reactions to comments with which you agree or disagree, or to the proposal as a whole.

As with all language change proposals, our goal is to drive toward a consensus to either add generics to the language or let the proposal drop. We understand that for a change of this magnitude it will be impossible to make everybody in the Go community happy, but we intend to get to a decision that everybody is willing to accept.

If the proposal is accepted, our goal will be to have a complete, though perhaps not fully optimized, implementation for people to try by the end of the year, perhaps as part of the Go 1.18 betas.

Go on ARM and Beyond

Russ Cox
17 December 2020

The industry is abuzz about non-x86 processors recently, so we thought it would be worth a brief post about Go’s support for them.

It has always been important to us for Go to be portable, not overfitting to any particular operating system or architecture. The initial open source release of Go included support for two operating systems (Linux and Mac OS X) and three architectures (64-bit x86, 32-bit x86, and 32-bit ARM).

Over the years, we’ve added support for many more operating systems and architecture combinations:

  • Go 1 (March 2012) supported the original systems as well as FreeBSD, NetBSD, and OpenBSD on 64-bit and 32-bit x86, and Plan 9 on 32-bit x86.
  • Go 1.3 (June 2014) added support for Solaris on 64-bit x86.
  • Go 1.4 (December 2014) added support for Android on 32-bit ARM and Plan 9 on 64-bit x86.
  • Go 1.5 (August 2015) added support for Linux on 64-bit ARM and 64-bit PowerPC, as well as iOS on 32-bit and 64-bit ARM.
  • Go 1.6 (February 2016) added support for Linux on 64-bit MIPS, as well as Android on 32-bit x86. It also added an official binary download for Linux on 32-bit ARM, primarily for Raspberry Pi systems.
  • Go 1.7 (August 2016) added support for Linux on z Systems (S390x) and Plan 9 on 32-bit ARM.
  • Go 1.8 (February 2017) added support for Linux on 32-bit MIPS, and it added official binary downloads for Linux on 64-bit PowerPC and z Systems.
  • Go 1.9 (August 2017) added official binary downloads for Linux on 64-bit ARM.
  • Go 1.12 (February 2018) added support for Windows 10 IoT Core on 32-bit ARM, such as the Raspberry Pi 3. It also added support for AIX on 64-bit PowerPC.
  • Go 1.14 (February 2019) added support for Linux on 64-bit RISC-V.

Although the x86-64 port got most of the attention in the early days of Go, today all our target architectures are well supported by our SSA-based compiler back end and produce excellent code. We’ve been helped along the way by many contributors, including engineers from Amazon, ARM, Atos, IBM, Intel, and MIPS.

Go supports cross-compiling for all these systems out of the box with minimal effort. For example, to build an app for 32-bit x86-based Windows from a 64-bit Linux system:

GOARCH=386 GOOS=windows go build myapp  # writes myapp.exe

In the past year, several major vendors have made announcements of new ARM64 hardware for servers, laptops and developer machines. Go was well-positioned for this. For years now, Go has been powering Docker, Kubernetes, and the rest of the Go ecosystem on ARM64 Linux servers, as well as mobile apps on ARM64 Android and iOS devices.

Since Apple’s announcement of the Mac transitioning to Apple silicon this summer, Apple and Google have been working together to ensure that Go and the broader Go ecosystem work well on them, both running Go x86 binaries under Rosetta 2 and running native Go ARM64 binaries. Earlier this week, we released the first Go 1.16 beta, which includes native support for Macs using the M1 chip. You can download and try the Go 1.16 beta for M1 Macs and all your other systems on the Go download page. (Of course, this is a beta release and, like all betas, it certainly has bugs we don’t know about. If you run into any problems, please report them at

It’s always nice to use the same CPU architecture for local development as in production, to remove one variation between the two environments. If you deploy to ARM64 production servers, Go makes it easy to develop on ARM64 Linux and Mac systems too. But of course, it’s still as easy as ever to work on one system and cross-compile for deployment to another, whether you’re working on an x86 system and deploying to ARM, working on Windows and deploying to Linux, or some other combination.

The next target we’d like to add support for is ARM64 Windows 10 systems. If you have expertise and would like to help, we’re coordinating work on

Redirecting requests to

Julie Qiu
15 December 2020

With the introduction of Go modules and the growth of the Go ecosystem, was launched in 2019 to provide a central place where developers can discover and evaluate Go packages and modules. Like, serves Go documentation, but it also supports modules, better search functionality, and signals to help Go users to find the right packages.

As we shared in January 2020, our goal is to eventually redirect traffic from to the corresponding page on We’ve also made it possible for users to opt in to redirecting their own requests from to

We’ve received a lot of great feedback this year, which has been tracked and resolved through the pkgsite/ and pkgsite/design-2020 milestones on the Go issue tracker. Your feedback resulted in support for popular feature requests on, open sourcing pkgsite, and most recently, a redesign of

Next Steps

The next step in this migration is to redirect all requests from to the corresponding page on

This will happen in early 2021, once the work tracked at the pkgsite/ milestone is complete.

During this migration, updates will be posted to Go issue 43178.

We encourage everyone to begin using today. You can do so by visiting, or clicking “Always use” in the top right corner of any page.


Will URLs continue to work?

Yes! We will redirect all requests arriving at to the equivalent page on, so all your bookmarks and links will continue to take you to the documentation you need.

What will happen to the golang/gddo repository?

The gddo repository will remain available for anyone who wants to keep running it themselves, or even fork and improve it. We will mark it archived to make clear that we will no longer accept contributions. However, you will be able to continue forking the repository.

Will continue to work?

This transition will have no impact on Until an API is available for, will continue to serve traffic. See Go issue 36785 for updates on an API for

Will my badges keep working?

Yes! Badge URLs will redirect to the equivalent URL on too. Your page will automatically get a new badge. You can also generate a new badge at if you would like to update your badge link.


As always, feel free to file an issue on the Go issue tracker for any feedback.

Contributing is an open source project. If you’re interested in contributing to the pkgsite project, check out the contribution guidelines or join the #pkgsite channel on Gophers Slack to learn more.

Eleven Years of Go

Russ Cox, for the Go team
10 November 2020

Today we celebrate the eleventh birthday of the Go open source release. The parties we had for Go turning 10 seem like a distant memory. It’s been a tough year, but we’ve kept Go development moving forward and accumulated quite a few highlights.

In November, we launched and shortly after Go’s 10th birthday.

In February, the Go 1.14 release delivered the first officially “production-ready” implementation of Go modules, along with many performance improvements, including faster defers and non-cooperative goroutine preemption to reduce scheduling and garbage collection latency.

In early March, we launched a new API for protocol buffers,, with much-improved support for protocol buffer reflection and custom messages.

When the pandemic hit, we decided to pause any public announcements or launches in the spring, recognizing that everyone’s attention rightly belonged elsewhere. But we kept working, and one of our team members joined the Apple/Google collaboration on privacy-preserving exposure notifications to support contact tracing efforts all over the world. In May, that group launched the reference backend server, written in Go.

We continued to improve gopls, which enables advanced Go-aware support in many editors. In June, the VSCode Go extension officially joined the Go project and is now maintained by the same developers who work on gopls.

Also in June, thanks to your feedback, we open-sourced the code behind as part of the Go project as well.

Later in June, we released the latest design draft for generics, along with a prototype tool and generics playground.

In July, we published and discussed three new design drafts for future changes: new //go:build lines for file selection, file system interfaces, and build-time file embedding. (We’ll see all of those in 2021, as noted below.)

In August, the Go 1.15 release delivered mainly optimizations and bug fixes rather than new features. The most significant was the start of a rewrite of the linker, making it run 20% faster and use 30% less memory on average for large builds.

Last month, we ran our annual Go user survey. We will post results on the blog once we’ve analyzed them.

The Go community has adapted to “virtual-first” along with everyone else, and we saw many virtual meetups and over a dozen virtual Go conferences this year. Last week, the Go team hosted Go day at Google Open Source Live (videos at the link).

Going Forward

We’re also incredibly excited about what’s in store for Go’s 12th year. Most immediately, this week Go team members will be presenting eight events at GopherCon 2020. Mark your calendars!

Go Releases

In February, the Go 1.16 release will include the new file system interfaces and build-time file embedding. It will complete the linker rewrite, bringing additional performance improvements. And it will include support for the new Apple Silicon (GOARCH=arm64) Macs.

In August, the Go 1.17 release will no doubt bring more features and improvements, although it’s far enough out that the exact details remain up in the air. It will include a new register-based calling convention for x86-64 (without breaking existing assembly!), which will make programs faster across the board. (Other architectures will follow in later releases.) One nice feature that will definitely be included is the new //go:build lines, which are far less error-prone than the current // +build lines. Another highly anticipated feature we hope will be ready for beta testing next year is support for fuzzing in the go test command.

Go Modules

Over the next year, we will continue to work on developing support for Go modules and integrating them well into the entire Go ecosystem. Go 1.16 will include our smoothest Go modules experience yet. One preliminary result from our recent survey is that 96% of users have now adopted Go modules (up from 90% a year ago).

We will also finally wind down support for GOPATH-based development: any programs using dependencies other than the standard library will need a go.mod. (If you haven’t switched to modules yet, see the GOPATH wiki page for details about this final step in the journey from GOPATH to modules.)

From the start, the goal for Go modules has been “to add the concept of package versions to the working vocabulary of both Go developers and our tools,” to enable deep support for modules and versions throughout the Go ecosystem. The Go module mirror, checksum database, and index were made possible by this ecosystem-wide understanding of what a package version is. Over the next year, we will see rich module support added to more tools and systems. For example, we plan to investigate new tooling to help module authors publish new versions (go release) as well as to help module consumers update their code to migrate away from deprecated APIs (a new go fix).

As a larger example, we created gopls to reduce many tools used by editors for Go support, none of which supported modules, down to a single one that did. Over the next year, we’ll be ready to make the VSCode Go extension use gopls by default, for an excellent module experience out of the box, and we’ll release gopls 1.0. Of course, one of the best things about gopls is that it is editor-neutral: any editor that understands the language server protocol can use it.

Another important use of version information is tracking whether any package in a build has a known vulnerability. Over the next year, we plan to develop a database of known vulnerabilities as well as tools to check your programs against that database.

The Go package discovery site is another example of a version-aware system enabled by Go modules. We’ve been focused on getting the core functionality and user experience right, including a redesign launching today. Over the next year, we will be unifying into We will also be expanding the version timeline for each package, showing important changes in each version, known vulnerabilities, and more, following the overall goal of surfacing what you need to make informed decisions about adding dependencies.

We’re excited to see this journey from GOPATH to Go modules nearing completion and all the excellent dependency-aware tools that Go modules are enabling.


The next feature on everyone’s minds is of course generics. As we mentioned above, we published the latest design draft for generics back in June. Since then, we’ve continued to refine rough edges and have turned our attention to the details of implementing a production-ready version. We will be working on that throughout 2021, with a goal of having something for people to try out by the end of the year, perhaps a part of the Go 1.18 betas.

Thank You!

Go is far more than just us on the Go team at Google. We are indebted to the contributors who work with us with the Go releases and tools. Beyond that, Go only succeeds because of all of you who work in and contribute to Go’s thriving ecosystem. It has been a difficult year in the world outside Go. More than ever, we appreciate you taking the time to join us and help make Go such a success. Thank you. We hope you are all staying safe and wish you all the best.

See the index for more articles.