Retracting a Go Module

grayscale macbook Photo by Sergey Zolkin on Unsplash

Introduction

Did you happen to catch my last blog post? If you did, you will know that I made a rather large mistake. Long story short, I released a new major version of my pagination library, but I might have forgotten to read the docs and I didn’t update the go module name to match the new major version. This left me with a broken version of my library out in the world. So let’s look at what I did to try and resolve it.


Golang Module Proxy

The first thing I did was delete the tag on GitHub and try to create a new v2.0.0 tag after I had fixed the module name. Unfortunately for me, the go tool chain pulls modules through the go module proxy and caches them in an immutable storage indefinitely. So even though I had deleted the tag, the broken version was still available for installation through the go get and go install commands.

Though it was unfortunate for me, it is of course a critical thing that any modules you depend on are stored immutably. Otherwise, a bad actor could create a library, get people to use it, and then overwrite an existing version of a module with malicious code. Any future builds would bring the new version with its vulnerabilities, and users would be none the wiser.

Or, in a slightly less malicious example, what if a user on GitHub were to change their username or decides to delete a package because they no longer feel like maintaining it? That would immediately cause broken build processes for anyone depending on that package.

So basically, the Go proxy is great! We love it. It did, however, leave me wondering: What if I make a genuine mistake or unintentionally release something with a vulnerability? How do I prevent my users from using broken or potentially harmful libraries?


Retract - The magic pill

Luckily, I learned my lesson. I made this error due to a lack of documentation reading, so the very first thing I did was scour the go documentation for a solution.

Apparently, we have the ability to mark a go module as retracted using the retract directive in a go.mod file. There are two ways to do this: The first is using the go mod CLI (this was added in go1.16):

go mod edit -retract v1.0.0

The other way, which I prefer, is by adding it to the go.mod file manually.

module github.com/webstradev/retract-me

go 1.23

retract (
	v1.0.0 // Broken version, accidentally published.
	v1.0.1 // Major Security Vulnerability, DO NOT USE!
)

Make sure to add a comment explaining why the module is retracted (this cannot be done through the CLI, so you will need to use the go.mod file either way). The comments are optional, but they really should not be, as they are also shown to the user when they try to upgrade to a retracted version.

go: warning: retract-me@v1.0.1: retracted by module author: Major Security Vulnerability, DO NOT USE!

Additionally, if you were to list the module versions, you would not see this retracted version in the list of available versions.

go list -m -versions github.com/webstradev/retract-me
github.com/webstradev/gin-pagination/v2 v0.9.0 v0.9.1 v1.0.2

Retracting the latest version

So it’s important to note that for a retraction to take effect, you need to create a new release with a retract directive for one or multiple older versions. Then You need the go module proxy to know about your new version, which contains the retractions.

A new go module won’t be available on the go module proxy until the first time someone runs go get and it pulls it through the cache. So to make sure your retract directive is even read by the proxy, your best bet is to go get your new version yourself, thus making it known to the proxy. Otherwise your retraction won’t take effect untill someone else updates to the latest version.

If you have already fixed the vulnerability, or the broken part of your module, then you can leave it at that. Your new module will be the latest, and if anyone tries to pull the broken one (by explicitly referencing that version), they will get a retract warning.

But, what if you haven’t found a fix yet and still want to immediately retract this mistake? Or what if you prematurely release a new major version and need to revert to a previous major version? The solution is relatively simple: Let’s say you have version v1.9.0, and you accidentally release v2.0.0. If you were to retract v2.0.0, you would need to release v2.0.1 with that retraction, and thus you are still in the next major version. So instead, you need to retract both the broken version and the one you are about to release.

module github.com/webstradev/retract-me

go 1.23

retract (
	v2.0.0 // Released major version v2 prematurely 
	v2.0.1 // Released major version v2 prematurely 
)

Then you just release this as v2.0.1 and your latest will drop back down to v1.9.0 and all is well in the universe.


Conclusion

It’s a good thing that the go module proxy stores your dependencies and own modules immutably, but sometimes you need to be able to hit undo. Using retract is the closest thing to CTRL + Z and it can be a resaonably straightforward way to reduce the impact of a broken version and keep your users safe.