Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/go: allow package authors to mark older package versions as insecure, incompatible, or broken #24031

Open
michael-schaller opened this issue Feb 22, 2018 · 71 comments
Assignees
Milestone

Comments

@michael-schaller
Copy link
Contributor

@michael-schaller michael-schaller commented Feb 22, 2018

In order to achieve reproducible builds vgo keeps using specific package versions until an explicit upgrade is done. IMHO this is an excellent default but I'm worried about insecure package versions as currently vgo can't detect if the build contains an insecure package version.

Can vgo be changed so that a package author is able to specify that every version below X is deemed insecure and if an insecure package version is used during a build that the build will fail (with a flag to override)?

@gopherbot gopherbot added this to the vgo milestone Feb 22, 2018
@kardianos

This comment has been minimized.

Copy link
Contributor

@kardianos kardianos commented Feb 22, 2018

@michael-schaller I'm not sure what new functionality you are asking for.

Right now vgo will not choose a version "below" a version you specify. So if there is an insecure package version, put the minimum version selector in your package or another package and it will not choose it. Maybe For modules that build main packages, you can also specify version ranges to exclude. Maybe I'm missing something?

@ALTree

This comment has been minimized.

Copy link
Member

@ALTree ALTree commented Feb 22, 2018

So if there is an insecure package version, put the minimum version selector in your package or another package and it will not choose it.

This only works if you know that a certain version is insecure. I think what he's asking for is a mechanism for package authors to broadcast to the world that a certain version is insecure; so that every time a user pulls it, they'll be warned that that version is deprecated and they'll know to update their mod file.

@kardianos kardianos changed the title x/vgo: Reproducible build vs. insecure package version x/vgo: allow package authors to mark packages as insecure Feb 22, 2018
@kardianos

This comment has been minimized.

Copy link
Contributor

@kardianos kardianos commented Feb 22, 2018

@ALTree That makes sense. Thanks for clarifying for me. I think that is a fine question to ask and may go hand-in-hand with the question of how to distribute the content / function of go.modverify.

@michael-schaller

This comment has been minimized.

Copy link
Contributor Author

@michael-schaller michael-schaller commented Feb 22, 2018

@ALTree correct. :-)

One naive idea would be that 'vgo build' could check the 'go.mod' (or another machine readable file) of the latest package versions for security information. This would also be great for Continuous Integration as then a package author could notify of security issues via CI build failures that are (hopefully) monitored.

@michael-schaller michael-schaller changed the title x/vgo: allow package authors to mark packages as insecure x/vgo: allow package authors to mark older package versions as insecure Feb 22, 2018
@michael-schaller

This comment has been minimized.

Copy link
Contributor Author

@michael-schaller michael-schaller commented Feb 24, 2018

@rsc mentioned Deprecated Versions (as part of the Defining Go Modules article) which is similar to this issue. He proposed to append +deprecated to a version tag which would also be a viable solution for this issue if +insecure would be honored by vgo.

IMHO that would be a pretty bare bones solution though as I presume that people would soon want to extend that further. For an instance I could see that someone would also want +buggy for a version with a serious bug (for an example a serious memory leak) or +broken for a version that is broken under certain circumstances (for an example the Windows build is broken). Furthermore this solution lacks a way to add more context as for deprecated versions one might be interested in the deprecation announcement or timeline and for insecure versions one might be interested in CVE, severity, ... and so on.

That said I think signaling via tags if a version is deprecated, insecure, ... is not adequate. Maybe even the proposal from my previous comment isn't. Maybe the discussion should rather go into the direction of a machine readable changelog which could be managed via vgo release.

@uluyol

This comment has been minimized.

Copy link
Contributor

@uluyol uluyol commented Feb 24, 2018

I'm not sure about the utility of this feature. If I release version 1.2.3, surely it must be fixing some bugs over 1.2.2 and I would probably mark 1.2.2. In other words, on almost every (point) release, I would mark all older versions as insecure. You might say this is only for bugs with a CVE, but I think the point still stands.

I don't see this providing much over just reporting that newer versions have been published.

@michael-schaller

This comment has been minimized.

Copy link
Contributor Author

@michael-schaller michael-schaller commented Feb 25, 2018

@uluyol there are actually multiple points for this feature (which I sadly failed to point out so far):

  1. It enables machines to automatically react:
    It's a chore to check for new versions and humans are sadly error prone, especially in burdensome cases of chore like this one. Having a machine readable form to notify of insecure, deprecated, buggy, ... versions enables machines to react so that humans can spend their time on better things. In this case vgo would be able to detect an unhealthy build and could let the build fail.

  2. Cut down reaction time:
    Once machines are able to react on such issues you can cut down reaction time drastically as for an instance Continuous Integration would fail and ideally you would automatically get a bug report for the build failure with the details why the build failed. This is especially valuable for projects that aren't any longer in the active development phase but rather receive infrequent updates on demand.

  3. Indicator for healthiness of new dependencies:
    If you add a new dependency to your project you get immediately a sense of the healthiness of that new dependency. If the new dependency is unhealthy vgo should fail to build and with that you know that you either need to change your project, need to file a bug report against the new dependency or you need to fork and fix the new dependency (if you still want to use it). For an instance this would catch the error that a human tries to add the new dependency with the major version v1 albeit that version is deprecated and a newer major version should be used.

  4. Indicator for healthiness of indirect dependencies:
    In the end you should have a cascading effect in case a package version is marked insecure as every other package that directly and most importantly indirectly depends on it should fail to build.

@uluyol

This comment has been minimized.

Copy link
Contributor

@uluyol uluyol commented Feb 25, 2018

I see. I agree that tags lack content, and that it would be useful to also have a reason (or changeling) listed for why a version is insecure. Then on build you could get messages like

build failed: insecure packages
oauth2: CVE-X-Y vulnerable to DoS
zstd: crash on invalid input

I can see why this would be useful. I do think that we'd want to let people override this behavior though.

@michael-schaller

This comment has been minimized.

Copy link
Contributor Author

@michael-schaller michael-schaller commented Feb 25, 2018

A definite +1 on the letting people override this behavior part. :-)

@jimmyfrasche

This comment has been minimized.

Copy link
Member

@jimmyfrasche jimmyfrasche commented Feb 25, 2018

I agree that additional in-band signalling would be useful way to let the maintainer know that action may need to be taken—but I disagree that every situation requires automatic, mechanical action.

Security fixes are important but automatically applying them can be as fraught as always ignoring them.

If the module in question is for a non-opensourced essential service using foo v1.0.5 and there's a foo v1.1.4+security that needs to be immediately investigated by a person. However, if its fixing a vulnerability introduced in foo v1.1.0 it may not necessarily be worth the effort and risk to drop everything and upgrade right now.

@4ad

This comment has been minimized.

Copy link
Member

@4ad 4ad commented Feb 27, 2018

I would prefer if vgo would continue to work the way it does today, with another tool, perhaps vgo audit that could check online if the versions used have problems.

The idea is that this tool is run on-demand by the programmer, rather than automatically. If this tool is easy to use, vgo audit could become as natural as running go fmt.

@rsc

This comment has been minimized.

Copy link
Contributor

@rsc rsc commented Apr 2, 2018

The purpose of vgo is to add versions to the vocabulary of the toolchain, so that users and tools can talk to each other sensibly about versions. As I mentioned in the https://research.swtch.com/vgo-module article, I think it would make sense to have a v1.2.3+deprecated tag, using an annotated tag so that there's a commit message. The commit message can say anything it wants about why the release is deprecated, and we can show that to users. We could easily add a notation in the text for identifying security problems. What happens next is up to tools. Probably vgo list -m -u (tell me about pending module updates) would do well to show information about currently-used modules that have deprecation notices.

@rsc rsc modified the milestones: vgo, vgo2 Jun 6, 2018
@rsc rsc modified the milestones: vgo2, Go1.12 Jul 12, 2018
@rsc rsc changed the title x/vgo: allow package authors to mark older package versions as insecure cmd/go: allow package authors to mark older package versions as insecure Jul 12, 2018
@rsc rsc added the modules label Jul 12, 2018
@rsc

This comment has been minimized.

Copy link
Contributor

@rsc rsc commented Jul 25, 2018

I've been thinking a bit about where to write down this information. The magic extra tag is clearly too limited in what it can record. I looked briefly into finding a way to write more information, such as using an annotated tag's commit message in Git, using svn propset to record a special per-revision property in Subversion, and so on. But something that must be reinvented for every different version control system is a bad idea.

Of course, we can't write the information in the original module version's go.mod, since we didn't know it was insecure when we tagged it, and the file tree is by convention (and enforcement via go.sum) immutable after tagging.

But maybe we can record it in a go.mod in a later release of the same module. Specifically, we could say that to look for updated post-release metadata about a particular module we grab the latest version's go.mod and look there. So for example, suppose v1.1.2 has a security problem, it was fixed in a rewrite for v1.2.0, and we're up to v1.2.4 when we discover the problem. Then we'd issue a v1.2.5 that is just v1.2.4 with an updated go.mod that adds something like:

bug example.com/mymodule/rpc v1.1.2-v1.2.0 https://example.com/issue1234 "RPC client bug - can get stuck if too many servers restart"

The fields are "bug", the affected package (if you don't use this package you don't have the bug), the half-open version range when the bug existed, a URL with more information, and a short description. Maybe a security bug would conventionally begin with a "security: " prefix in the description.

Then any future "go get", even one not asked about that module, would look up the latest version, find v1.2.5, learn about the bug in v1.1.2, and print a warning. Also, we could make this information available to running programs, which could inspect their own binaries for the package and version and then self-diagnose on a server status page, automatically report to local monitoring systems, and so on. (We've done something like this inside Google since early 2013 and it works really well.)

If we later decided to issue a v1.1.3 with that fix, we could issue a v1.2.6 that only updates go.mod:

bug example.com/mymodule/rpc v1.1.2-v1.1.3 https://example.com/issue1234 "RPC client bug - can get stuck if too many servers restart"

If we wanted to warn people about the bug but didn't have time to fix it yet, or the bug has been there from the beginning, the half-open interval can drop either side:

bug example.com/mymodule/rpc v1.1.2- https://example.com/issue1234 "RPC client bug - can get stuck if too many servers restart"
bug example.com/mymodule/rpc -v1.1.3 https://example.com/issue1234 "RPC client bug - can get stuck if too many servers restart"
bug example.com/mymodule/rpc - https://example.com/issue1234 "RPC client bug - can get stuck if too many servers restart"

The same general idea could apply to marking earlier versions deprecated or to reporting known conflicts with other dependencies.

It's slightly awkward to be issuing go.mod-update-only patch releases, but doing so creates a history of the annotations and makes them available via module proxies without special arrangement.

All of this is still sketchy but the above seems like it's on a better path than just the +deprecated tags.

@mattfarina

This comment has been minimized.

Copy link

@mattfarina mattfarina commented Aug 6, 2018

@rsc for some clarification, when should the bug directive be used? Only when there are security issues? Security issues plus deprecation? If deprecation, is there a common test people should us for marking something as such (e.g., every patch release)? Every bug?

Note, for every bug I poked at a couple repos I've had to deal with:

  • the go-grpc repo has 76 closed bugs
  • kubernetes/kubernetes has 4,109 closed bugs
  • gin-gonic/gin as 28 closed bugs

I tried to look at a sampling of typical and worse case scenarios.

@thepudds

This comment has been minimized.

Copy link

@thepudds thepudds commented Aug 6, 2018

@mattfarina If I have followed, I believe @rsc has referred (e.g., here and elsewhere) to this particular issue #24031 as potentially also being part of the solution for recording pair-wise incompatibility post publishing:

as I noted in #24301 on 5/25, I do want to find a place to record incompatibilities, but one that allows recording them after the fact instead of requiring accurate prediction of the future.

and:

More generally we need a place to record other known problems with already-released versions, like security bugs. That's #24031. Maybe the answer there applies to incompatibilities too.

And in #24031 (comment), towards the end of that more recent comment (which was mostly using security or a general bug as an example), Russ also added:

The same general idea could apply to marking earlier versions deprecated or to reporting known conflicts with other dependencies.

That said, the one-liner here is currently:

"cmd/go: allow package authors to mark older package versions as insecure"

If the intent of this particular issue #24031 is broader than security, it might make sense to update the one-liner to help people know where to discuss which topic (vs. maybe #26829 is the better place to discuss recording incompatibilities, or ___).

@thepudds

This comment has been minimized.

Copy link

@thepudds thepudds commented Aug 6, 2018

@mattfarina Said another way, using the start of your example from #26829:

Consider the following case where you have an app with a dependency on a module (modA) that has a dependency on another module (modB).

App --> modA --> modB

modB is released with a bug.

My understanding of the discussion of #24031 (both in the issue here and outside of github) is that #24031 might be the answer for:

  • the author of modA declares certain versions of modA do not want to use the buggy v.1.2.3 of modB
  • the author of "App" becoming aware of the actions of the author of modA
  • all of that happening without requiring any action on the part of the author of modB

Mechanism still TBD, but perhaps the mechanism (if I've followed the discussion) might be something like:

  • the author of modA produces a new go.mod in a later release of modA that declares that certain versions of modA are incompatible with the buggy v1.2.3 of modB
  • the author of 'App' issues a 'go get' (where perhaps that 'go get' is not directly related to modA or modB, or perhaps a different go command), and the author of 'App' is warned about the pair-wise incompatibility between the versions of modA and modB now in use in 'App' (or perhaps it is something other than a warning).
  • and all of that is done without needing to predict the future (by declaring future incompatibilities that don't exist yet at the moment of publishing a release) and also without needing to update an immutable published release.

But sorry if this is off base or just noise... and I agree some clarity on what this particular issue is intended to cover would be helpful, because it is an important set of topics...

@Zemnmez

This comment has been minimized.

Copy link

@Zemnmez Zemnmez commented Jan 3, 2019

@myitcv mentioned this issue. I put forward #29537 which i think would go some way to solving this issue

@michael-schaller

This comment has been minimized.

Copy link
Contributor Author

@michael-schaller michael-schaller commented Mar 10, 2020

A few comments:

General

  • I very much like the overall approach, the name retract and the location in go.mod. Well done! :-D
  • I miss a section on how to ensure extensibility. How could this be extended without breaking old Go versions?

Abstract

  • Can we add 'dead/abandoned/end-of-life' as possible reason as well?

Mechanism for retracting versions

  • Can we add a range of versions, including open start or open end? It can be bothersome for a frequently released package to list all the affected versions and the resulting retractions could be hard to read by humans.
  • Can we have something slightly better than comments to denote the retraction reason? It would be particularly good for vulnerability scanners to be able to determine how urgent a vulnerability is. Furthermore a grave bug, like a rarely occurring data corruption issue, could be urgent and should be reflected. So I'd like to see at least some machine readable reason enum and urgency/severity enum as part of the retraction definition.

go vet

  • I would like to see that go vet checks for retracted versions. Many people run go vet before commit and so this would IMHO be a good place to check for retracted versions.
@carnott-snap

This comment has been minimized.

Copy link

@carnott-snap carnott-snap commented Mar 10, 2020

No change would be made to build commands that don't resolve new module versions. A core promise of modules is that builds are reproducible: if a go.mod file lists all the required modules to build a set of packages, the go command continued to build those packages with the same set of module versions, even if versions are retracted or new versions are published.

This proposal places build reproducibility over safety. Even the go1 compatibility guarantee allows for breaking APIs in the event of security issues. This is laudable, but I think there exist cases where you want to break builds, due to the severity of an issue, especially within a private organisation. This would lead to another keywords (e.g. salt, burn, blacklist) and extra complexity, but I think it is worth calling out that this proposal does not provide such a mechanism.

@jayconrod

This comment has been minimized.

Copy link
Contributor

@jayconrod jayconrod commented Mar 10, 2020

Can you speak to why you went with a comment for explaining the cause of the retraction? Specifically as opposed to some retraction-specific syntax.

@alex I went through a few different iterations of this, but I couldn't come up with a structure that works for all cases. For the purpose of the go command, a flat string satisfies all the requirements, and the simplest solution is usually the best.

That said, I think it makes sense to have some conventional format for security issues, the way we have ^// Code generated .* DO NOT EDIT\.$ for generated source files. I'll wait until we have a firmer plan on how to report and track security issues before proposing anything though.

One thing to point out: I don't think retracting module versions completely solves the problem of reporting security vulnerabilities. You can't retract individual packages or functions (perhaps a job for a separate static analysis tool later on). And only authors are allowed to retract versions, so there's no way for users of an abandoned or uncooperative module to be notified of a problem.

I know @FiloSottile and @katiehockman have been thinking about this. Any early thoughts or open issues?

@jayconrod

This comment has been minimized.

Copy link
Contributor

@jayconrod jayconrod commented Mar 10, 2020

Loving the self-retract mechanism of publishing a retraction of an accidentally pushed new major version.

@lizthegrey I meant to apologize that this wasn't in place earlier. Having to rename your module sucks.

For new major versions beyond v2.0.0, since Go 1.14, I believe you can now add a go.mod file to the latest version (say v2.0.1), and that will no longer be resolved as the +incompatible latest version for the base module. Buuut that doesn't help for v1.0.0 though.

@jayconrod

This comment has been minimized.

Copy link
Contributor

@jayconrod jayconrod commented Mar 10, 2020

Though the retraction mechanism doesn't discuss it (as far as I have read) it seems like some more thought needs to be put into the specifics of how a package vulnerable via a critically vulnerable dependency has its dependencies upgraded. If no changes are made to build, how will the developer building the package know there is a critical dependency vulnerability? It is not so common for Go developers in my experience to unreservedly issue a command to upgrade all dependencies.

@Zemnmez I think checking for retracted versions during a build would be too costly. We have to load the version list for each module that provides packages imported by the thing being built. If there are uncached versions, we have to fetch the go.mod file for those. That's why I'm limiting the check to go list -m -u and go get: those commands almost always have to do that work anyway.

I agree that it would still be easy to miss a notification about this though. I just opened #37781, suggesting that gorelease report an error if the module being tested requires a retracted module version.

I think there's an opportunity here for another tool as well: given a list of module versions (perhaps produced by running go version -m on a binary running in production), report whether any version is retracted.

@jayconrod

This comment has been minimized.

Copy link
Contributor

@jayconrod jayconrod commented Mar 10, 2020

@michael-schaller

I very much like the overall approach, the name retract and the location in go.mod. Well done! :-D

Thanks! I went through a lot of words; it feels a little bit like jargon, but it precisely describes what we're trying to accomplish.

I miss a section on how to ensure extensibility. How could this be extended without breaking old Go versions?

I got into this a bit in the section Compatibility with old versions. In short, if you use retract in your own module (the main module), you must use Go 1.15 or later. retract may be used in modules you depend on with any Go version that supports modules. Old Go versions will ignore retract outside the main module.

Can we add 'dead/abandoned/end-of-life' as possible reason as well?

I'd generally advise against using retract for that reason: if you want to encourage users to upgrade to a new version, it's better to do it with a carrot (new features, better performance) instead of a stick. Upgrades (especially to incompatible versions) are costly, and not always worth it from the user's perspective.

That said, if you have a module that provides an API for a service that's being turned down, the module will no longer work, and users should be told. retract would make sense in that situation.

Can we have something slightly better than comments to denote the retraction reason? It would be particularly good for vulnerability scanners to be able to determine how urgent a vulnerability is. Furthermore a grave bug, like a rarely occurring data corruption issue, could be urgent and should be reflected. So I'd like to see at least some machine readable reason enum and urgency/severity enum as part of the retraction definition.

Maybe. It's not clear at all to me what the format should be. We'd need something extensible that works for all use cases. Before committing to anything, I think we should have a firmer plan for vulernability tracking in general.

I would like to see that go vet checks for retracted versions. Many people run go vet before commit and so this would IMHO be a good place to check for retracted versions.

This feels a bit out of go vet's wheelhouse to me. You could probably build an analyzer (using golang.org/x/tools/go/analysis, the framework go vet is built on) that does this. But analyzers typically just do static analysis on packages, and they don't usually go out to the network.

I've just opened #37781 to do something like this in gorelease though. That seems like a better fit to me.

@jayconrod

This comment has been minimized.

Copy link
Contributor

@jayconrod jayconrod commented Mar 10, 2020

This proposal places build reproducibility over safety. Even the go1 compatibility guarantee allows for breaking APIs in the event of security issues. This is laudable, but I think there exist cases where you want to break builds, due to the severity of an issue, especially within a private organisation. This would lead to another keywords (e.g. salt, burn, blacklist) and extra complexity, but I think it is worth calling out that this proposal does not provide such a mechanism.

@carnott-snap I could see this being the case within an organization where you have control of the code and all its uses. There a number of ways to accomplish this outside of go.mod already: removing a version from the corporate proxy; running a test in CI that fails if a bad version is used; checking versions used by binaries running in production and alerting operators.

I'd be extremely cautious about adding a mechanism like this for the whole ecosystem though. We'd open ourselves up to a "leftpad" scenario, where an author (perhaps someone who's taken over an abandoned project) could break builds for everyone by retracting all versions. That attack still exists here, but it's annoying rather than fatal.

@carnott-snap

This comment has been minimized.

Copy link

@carnott-snap carnott-snap commented Mar 10, 2020

I'd be extremely cautious about adding a mechanism like this for the whole ecosystem though.

I completely agree, but wanted to call out the limitation, since it was not discussed in the proposal text.

@jayconrod

This comment has been minimized.

Copy link
Contributor

@jayconrod jayconrod commented Mar 10, 2020

I completely agree, but wanted to call out the limitation, since it was not discussed in the proposal text.

Thanks, and good point. I've edited in a note to the proposal for visibility.

@Zemnmez

This comment has been minimized.

Copy link

@Zemnmez Zemnmez commented Mar 10, 2020

@michael-schaller

Can we have something slightly better than comments to denote the retraction reason? It would be particularly good for vulnerability scanners to be able to determine how urgent a vulnerability is. Furthermore a grave bug, like a rarely occurring data corruption issue, could be urgent and should be reflected. So I'd like to see at least some machine readable reason enum and urgency/severity enum as part of the retraction definition.

Maybe. It's not clear at all to me what the format should be. We'd need something extensible that works for all use cases. Before committing to anything, I think we should have a firmer plan for vulernability tracking in general.

I took a shot at this in my previous proposal. It's very minimal but might serve as some inspiration? #24031 (comment)

@Zemnmez I think checking for retracted versions during a build would be too costly. We have to load the version list for each module that provides packages imported by the thing being built. If there are uncached versions, we have to fetch the go.mod file for those. That's why I'm limiting the check to go list -m -u and go get: those commands almost always have to do that work anyway.

I agree that it would still be easy to miss a notification about this though. I just opened #37781, suggesting that gorelease report an error if the module being tested requires a retracted module version.

I think there's an opportunity here for another tool as well: given a list of module versions (perhaps produced by running go version -m on a binary running in production), report whether any version is retracted.

I feel quite strongly that a proposal for marking versions as 'no longer good' would benefit greatly from some basic story about how either it should be extended to support security notifications, or how it intends to support them. Marking packages in the Go ecosystem as vulnerable or not (especially in a way that is widely recognized) is currently a big issue when it comes to Go security overall in my experience.

I think that's the biggest factor into where and how 'retracted' revisions should be surfaced. If all retractions are considered equally critical because there is no story around expressing metadata, then all retractions will have to be treated like a critical security issue for many of the security-critical systems at scale that Go supports -- a noteworthy selection of which, as mentioned before by @michael-schaller should justifiably break the build for some people (perhaps behind a flag?).

This might be a misunderstanding, but this is, I feel a possible advantage of a tag based approach. I assume the logic there can go faster than with files in the revision tree.

edit: I will add that I think, as with the proposal so far that approaching this not from a security issue perspective 'security issue in revision x' and instead a 'critical bug' perspective is probably the better way to go here. I think there are benefits to equating the severities of 'this version will break your build' vs 'this version will render this system useless as per its security requirements'

@jimmyfrasche

This comment has been minimized.

Copy link
Member

@jimmyfrasche jimmyfrasche commented Mar 10, 2020

If you parse the comments out godoc-style and could include them in the go list -json as something like .RetractReason so that given

retract v1.2.3 // security vulnerability: see issue #1009

instead of tools just saying v1.2.3 is retracted they could say v1.2.3 is retracted: security vulnerability: see issue #1009

What happens if I release a go.mod with a retraction then delete that line and do another release? Does that retract the retraction?

@michael-schaller

This comment has been minimized.

Copy link
Contributor Author

@michael-schaller michael-schaller commented Mar 11, 2020

@jayconrod

@michael-schaller

I miss a section on how to ensure extensibility. How could this be extended without breaking old Go versions?

I got into this a bit in the section Compatibility with old versions. In short, if you use retract in your own module (the main module), you must use Go 1.15 or later. retract may be used in modules you depend on with any Go version that supports modules. Old Go versions will ignore retract outside the main module.

Having a section about backward compatibility is great. What is missing is a section about forward compatibility though. ;-)

For instance if in X years we would want to add an URL field (e.g. bug, ticket, announcement, ...) to the retractions how would that be added? If that would need to be added by extending the comment then IMHO that would reflect poorly on the current design proposal.

Furthermore it might be worth considering to have a standard set of fields (versions and commentfor the beginning) and to allow users to add custom fields on demand (similarly to HTTP X- headers). Then the Go team can standardize the widely used custom fields over time (and also hard deprecate these custom fields to force adoption of the new standard fields)...

Also the current design proposal makes it hard to parse the comment from go.mod as the comment could be a trailing comment but it could also be a leading comment (possibly multi-line). Having a dedicated comment field would IMHO improve this a lot for tools that will process this information (vulnerability scanners, build checkers, ...).

So some kind of machine readable key/value setup for the retractions would IMHO be great.

Can we add 'dead/abandoned/end-of-life' as possible reason as well?

I'd generally advise against using retract for that reason: if you want to encourage users to upgrade to a new version, it's better to do it with a carrot (new features, better performance) instead of a stick. Upgrades (especially to incompatible versions) are costly, and not always worth it from the user's perspective.

My intention wasn't for versions but rather for the whole project/package. You see many abandoned packages where the author/maintainer mentioned this in the README. So it would be great if all versions of a package could be marked as abandoned in the retractions.

Can we have something slightly better than comments to denote the retraction reason? It would be particularly good for vulnerability scanners to be able to determine how urgent a vulnerability is. Furthermore a grave bug, like a rarely occurring data corruption issue, could be urgent and should be reflected. So I'd like to see at least some machine readable reason enum and urgency/severity enum as part of the retraction definition.

Maybe. It's not clear at all to me what the format should be. We'd need something extensible that works for all use cases. Before committing to anything, I think we should have a firmer plan for vulernability tracking in general.

See my other comment about extensibility.

I would like to see that go vet checks for retracted versions. Many people run go vet before commit and so this would IMHO be a good place to check for retracted versions.

This feels a bit out of go vet's wheelhouse to me. You could probably build an analyzer (using golang.org/x/tools/go/analysis, the framework go vet is built on) that does this. But analyzers typically just do static analysis on packages, and they don't usually go out to the network.

I've just opened #37781 to do something like this in gorelease though. That seems like a better fit to me.

Sounds good to me. Thanks.

@michael-schaller

This comment has been minimized.

Copy link
Contributor Author

@michael-schaller michael-schaller commented Mar 11, 2020

@Zemnmez
Maybe it's best to delay the discussion about vulnerability details. IMHO having a solid, simple and extensible foundation as a first step would simplify all further extensions, like adding vulnerability information. I would also like to see vulnerability details asap but IMHO they aren't required for the very first iteration. Just being able to retract versions will be IMHO already a huge win. All further extensions are icing on the cake to give more and better context.

Also I think @jayconrod made some really good points why versions shouldn't be retracted via tags and so maybe, just maybe, it is time to consider giving up on the tags approach.

@michael-schaller

This comment has been minimized.

Copy link
Contributor Author

@michael-schaller michael-schaller commented Mar 11, 2020

@jayconrod
Could you add that with the go.mod approach it is also possible for anyone to suggest a retraction via a pull request? This might seem obvious but with the tag approach it would only be possible for repo owners to add a retraction and contributors could only suggest retractions via an issue report.

@jayconrod

This comment has been minimized.

Copy link
Contributor

@jayconrod jayconrod commented Mar 11, 2020

@Zemnmez

I feel quite strongly that a proposal for marking versions as 'no longer good' would benefit greatly from some basic story about how either it should be extended to support security notifications, or how it intends to support them. Marking packages in the Go ecosystem as vulnerable or not (especially in a way that is widely recognized) is currently a big issue when it comes to Go security overall in my experience.

I think that's the biggest factor into where and how 'retracted' revisions should be surfaced. If all retractions are considered equally critical because there is no story around expressing metadata, then all retractions will have to be treated like a critical security issue for many of the security-critical systems at scale that Go supports -- a noteworthy selection of which, as mentioned before by @michael-schaller should justifiably break the build for some people (perhaps behind a flag?).

I agree there should be a better story here, but I'll defer to the Go security team (@katiehockman, @FiloSottile) as to exactly what's needed. So far, it sounds like it would be useful to have a severity and some identifier (URL? CVE number?) in addition to a description.

At the moment, I don't think the go command should behave differently depending on the annotation: that should be up to other tools. I don't think regular builds should fair for example.

Consequently, a comment seems like the right way to express this. For example:

// Vulnerability: CVE-2020-1234
// Severity: HIGH
// Description: Uses ROT13 encryption
retract v1.5.0

Just to throw out a strawman alternative:

retract v1.5.2 (
    vulnerability CVE-2020-1234
    severity HIGH
    description "Uses ROT13 encryption"
)

This would just be a list of key-value pairs, all strings. They would be expressed as a JSON object in go list -m -u -json output.

This might be a misunderstanding, but this is, I feel a possible advantage of a tag based approach. I assume the logic there can go faster than with files in the revision tree.

Not sure I understand. Could you elaborate?

I sketched out a design for this a while back (never published), and I thought the logical place for metadata would be an annotated tag commit message (git only) or in the commit message for the revision the tag points to. Both are free-form text, so they have the same advantages and disadvantages as doc comments for metadata representation.

@jayconrod

This comment has been minimized.

Copy link
Contributor

@jayconrod jayconrod commented Mar 11, 2020

@jimmyfrasche

If you parse the comments out godoc-style and could include them in the go list -json as something like .RetractReason ...

I'm suggesting we just have the field Retracted in the go list -m -u -json output. It would contain the comment, and tools could do what they need with that.

Just added example output to the proposal.

@jayconrod

This comment has been minimized.

Copy link
Contributor

@jayconrod jayconrod commented Mar 11, 2020

@michael-schaller

Having a section about backward compatibility is great. What is missing is a section about forward compatibility though. ;-)

For instance if in X years we would want to add an URL field (e.g. bug, ticket, announcement, ...) to the retractions how would that be added? If that would need to be added by extending the comment then IMHO that would reflect poorly on the current design proposal.

This hinges on whether we can express everything in a comment or if we really need syntax. We should decide that based on whether the go command needs to actively do anything with the comment, besides presenting it in go list -m -u -json output.

If we need syntax, we should allow unrecognized fields for future compatibility.

@jimmyfrasche

This comment has been minimized.

Copy link
Member

@jimmyfrasche jimmyfrasche commented Mar 11, 2020

@jayconrod

If the Retracted field is a string how do you disambiguate between no retraction and an uncommented retraction? (Keeping in mind that build infrastructure is often written in looser languages).

Should gorelease warn about an uncommented retraction?

I also asked:

What happens if I release a go.mod with a retraction then delete that line and do another release? Does that retract the retraction?

If it's expected to be append-only, gorelease should probably warn if a version is accidentally removed.

@michael-schaller

This comment has been minimized.

Copy link
Contributor Author

@michael-schaller michael-schaller commented Mar 11, 2020

@jayconrod

This hinges on whether we can express everything in a comment or if we really need syntax. We should decide that based on whether the go command needs to actively do anything with the comment, besides presenting it in go list -m -u -json output.

If we need syntax, we should allow unrecognized fields for future compatibility.

The comment part is actually my only major concern about the current design proposal as it can be easily misused if there aren't any other options available. There are already people who voice their preference for machine readable retraction data (including me) but that becomes very hard as soon as people start to pack all kinds of information in a comment. That is really a future I'd like to avoid and hence I urge you to consider a more elaborate syntax. Just a basic key/value store would IMHO improve this design proposal a lot.

Furthermore I disagree that this is only about the go command as the retraction data should really be accessible to everyone, particularly scanners. For instance pkg.go.dev could show retracted versions and additional data (if available). There could also be a Security tab next to Versions tab with just the security retractions...

@tv42

This comment has been minimized.

Copy link

@tv42 tv42 commented Mar 11, 2020

There are some drawbacks:

  • There's no history about when versions were retracted (or un-retracted) and who made the change.
    [...]
  • The set of version control tags, the version list (the $module/@v/list endpoint in the GOPROXY protocol), and the version info ($module/@v/$version.info) are not authenticated by go.sum files or the checksum database. Their contents may change over time, and they can be manipulated by a malicious proxy.

Aren't these drawbacks true for release tags in the first place, in exactly the same way, and implicitly considered acceptable? (More likely, out of scope; it's up the version control to retain history of who does what with it.)

@bcmills

This comment has been minimized.

Copy link
Member

@bcmills bcmills commented Mar 11, 2020

@tv42, the contents of tagged releases are covered by the checksum database. Their metadata is not, but that metadata is not used for any kind of security auditing today.

@jayconrod

This comment has been minimized.

Copy link
Contributor

@jayconrod jayconrod commented Mar 12, 2020

@jimmyfrasche

If the Retracted field is a string how do you disambiguate between no retraction and an uncommented retraction? (Keeping in mind that build infrastructure is often written in looser languages).

Should gorelease warn about an uncommented retraction?

An uncommented retraction would have a Retracted field with the empty string as the value. The Retracted field would not be present if there was no retraction.

What happens if I release a go.mod with a retraction then delete that line and do another release? Does that retract the retraction?

Sorry for missing this question earlier

That would un-retract the version. That's okay. Just as there are cases where a version is published accidentally, there will be cases where a version is retracted accidentally, so the ability to undo a retraction is important.

@jayconrod

This comment has been minimized.

Copy link
Contributor

@jayconrod jayconrod commented Mar 12, 2020

@michael-schaller

The comment part is actually my only major concern about the current design proposal as it can be easily misused if there aren't any other options available. There are already people who voice their preference for machine readable retraction data (including me) but that becomes very hard as soon as people start to pack all kinds of information in a comment. That is really a future I'd like to avoid and hence I urge you to consider a more elaborate syntax. Just a basic key/value store would IMHO improve this design proposal a lot.

Furthermore I disagree that this is only about the go command as the retraction data should really be accessible to everyone, particularly scanners. For instance pkg.go.dev could show retracted versions and additional data (if available). There could also be a Security tab next to Versions tab with just the security retractions...

Point taken. I'll experiment with the syntax and chat with @rsc about this.

I definitely like the idea of pkg.go.dev surfacing this, too.

@michael-schaller

This comment has been minimized.

Copy link
Contributor Author

@michael-schaller michael-schaller commented Mar 12, 2020

@jayconrod

Point taken. I'll experiment with the syntax and chat with @rsc about this.

Sorry if this endangers your goal to implement this for Go 1.15.

@jimmyfrasche

This comment has been minimized.

Copy link
Member

@jimmyfrasche jimmyfrasche commented Mar 12, 2020

My concern is code that treats both the absence of the Retract field and a Retract field with a falsey value as "not retracted".

@rsc

This comment has been minimized.

Copy link
Contributor

@rsc rsc commented Mar 25, 2020

I'm skeptical about using retract statements as a database of security vulnerabilities.
There are many reasons a particular version might be retracted, not all of them security.
Note that this very issue title says "insecure, incompatible, or broken".
We shouldn't overfit to security. That's biting off too much.

Remember, go.mod is for directives to the go command related to version selection.
Security information should be elsewhere.
@FiloSottile is working on that problem; it's out of scope for go.mod.

What we want to avoid is go.mod becoming a complex, general-purpose
dumping ground for all possible information about the code.
That's the easy answer, but not the right one.

Also, note that this syntax:

retract v1.5.2 (
    vulnerability CVE-2020-1234
    severity HIGH
    description "Uses ROT13 encryption"
)

does not make sense since the go command defines ( ) as factoring, like Go itself.
This would need to be semantically equivalent to:

retract v1.5.2 vulnerability CVE-2020-1234
retract v1.5.2 severity HIGH
retract v.1.5.2 description "Uses ROT13 encryption"

That would in turn disallow us from flagging the same version appearing in multiple retractions.

@rsc

This comment has been minimized.

Copy link
Contributor

@rsc rsc commented Mar 25, 2020

My suggestion is to scale back, not attempt to be a security database, and have just a
plain-text comment, like we do for doc comments in Go code (like in struct fields).
Tooling could easily recognize a CVE number or a URL by syntax and pull them out.

The syntax I propose is

retract version... // optional comment

as in

retract v1.5.0 v1.5.1 v1.5.2 // buggy marshaling (https://golang.org/issue/12345)
retract v1.6.0 // accidental incompatible API change

We should also allow closed intervals like:

retract [v1.5.0, v1.5.2] // buggy marshaling (https://golang.org/issue/12345)

And we should prep the go command now so that we can allow open or half-open intervals.
(Today the ( ) parsing gets in the way, but we can limit that to ( at end of line
and ) at beginning of line.)

@michael-schaller

This comment has been minimized.

Copy link
Contributor Author

@michael-schaller michael-schaller commented Mar 29, 2020

@jayconrod
Could you add a documentation section to your design that outlines how this feature will be documented? What I'm particularly looking for is how the retraction feature is intended to be used and how it shouldn't be used. For instance that a retraction is merely a hint by the package maintainer(s) that a specific version (range) should be avoided because of the commented reason but that package users are free to ignore this hint. Also that retractions should not be used to deprecate old (but perfectly working) versions to force users to update to newer versions as that is against the spirit of the whole Go modules minimal version selection principle.

@jayconrod @rsc
I'm a bit unhappy with having the comment optional. The package maintainer(s) are sending a message with a retraction notice and without a comment/reason the message is just "avoid" which isn't all that helpful to package users, particularly as then they can't decide for themselves if they should follow the retraction recommendation or not. Also having a mandatory reason sends a bit of a stronger message to the package maintainer(s) that a retraction shouldn't be made lightly. So please consider having a mandatory reason instead of an optional comment.

@jayconrod @rsc @FiloSottile
Could you open a follow up bug for the security vulnerability tracking? It would also be great if the new bug wouldn't just reference this bug but would contain a summary of what has been discussed in this bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.