Försikt: shouldn't we vet libraries in Go?

Thoughts about auditing

A couple of years ago, I stumbled over Cargo Vet.

That project introduces itself like this:

The cargo vet subcommand is a tool to help projects ensure that third-party Rust dependencies have been audited by a trusted entity. It strives to be lightweight and easy to integrate.

When run, cargo vet matches all of a project’s third-party dependencies against a set of audits performed by the project authors or entities they trust. If there are any gaps, the tool provides mechanical assistance in performing and documenting the audit.

It inspired me to spend a little bit of time on Försikt which is sort of the draft-space for my ideas on how to implement a similar system in Go.

I’ve long been a reluctant user of dependencies, it’s wonderful to have access to millions of lines of code at your fingertips but the state of security around most of the modern software stacks is so abyssmal that we can have seemingly benign dependencies decide to delete the content on people’s computers if they happen to be in the wrong place.

The thing is, I’ve read through quite a few dependencies sufficiently well that I would say that at least at the version they’re at, I’d trust them. I know of project’s which has good enough routines that I’d implicitly trust them for at least my own projects (Google, Uber, and Prometheus comes to mind).

I could share with others which libraries I’ve looked at, and what versions and you could add your list of libraries you’ve looked at and what versions. If I trust your judgment, I can inherit your trust in libraries you’ve audited and suddenly we could have an open source market marrying trust with code.

Enterprises could buy their audit lists from a verified auditor, thus providing a space for security researchers to contribute to the state of open source by providing varying levels of auditing of software.

Försikt tries to provide a sandbox for me to explore these questions, to move the principle of requiring at least two eyes on all code going out into production into the realm of dependencies as well.

My hope is that one day, this will be a natural part of the official toolchain with sufficient popularity that ensuring an unbroken audit-chain for software is as common as linters are. I think that requires the audit tooling to be easy to use and sufficiently popular to achieve transparency to the user: Most libraries should just work provided you import some well-known audit source.

Försikt, what I’ve built

Försikt defines a go.audit in the style of go.mod and go.sum that complements the declarations in go.mod, the TOFU validation done by go.sum with a log of manual auditing done in go.audit.

audit 1

trust (
        // this is a note
        // notes can have multiple lines
        golang.org/x/crypto v1.0.0 Alice
        // Diff audits are only trusted if the oldest version is also trusted
        golang.org/x/crypto [v1.0.0, v1.0.1] @charlie
        golang.org/x/net v1.2.3 "Bob <bob@example.org>" // inline comment
)

violation example.org/malicious * "Nils Holgersson"

The auditors in this example sign off with their name, this is project dependent, but Bob’s signature is likely what we’d want in a portable go.audit.

We have an example of Alice fully auditing golang.org/x/crypto for its 1.0.0 release. @charlie’s [v1.0.0, v1.0.1] means that Charlie has looked at the delta between those two versions and asserts that nothing untoward has been introduced, but they’re not willing to sign-off on the whole module. If we’d lose our trust in Alice and removed her lines from the go.audit-file, the crypto library would now start flagging that it’s not properly audited.

Nils in this example has added a known bad package as a violation, which as the name implies is the opposite of a trust-line: We know we should avoid this.

And… this is about it. I’ve taken the go.mod-parsing code and wrestled it just enough to run my audit over the go mod graph. Beyond that lies dragons.

What I think should be done

Försikt is quite honestly a hack. I haven’t thought enough about the merits and drawbacks of including the hashes from go.sum in addition to the versions. It doesn’t support importing trusted lists from elsewhere, the features that are there are implemented quickly and with disregard for the user experience.

I’m pretty sure the syntax is over-simplistic and doesn’t cater to nearly a big enough audience to ever achieve any significant adoption: I lack signatures and trust-levels that security-minded users would want and it’s still too obnoxious for casual users.

Before you mistake this critique as a false sense of modesty, no, I’m sure that I could develop Försikt into something that has the functionality that I’d want out of it, but I equally well know that since I started working on it, I’ve barely touched it. I mostly lost interest in it after the initial implementation and for more than a year just let it rest in a git repository with no love.

I think the ultimate goal has to be reaching a level of acceptance and ubiquiti to enter the Go project’s main toolchain. As such, what I truly believe should be done is a design document outlining the benefits and challenges to introducing go.audit. Försikt is a playground with that as its ultimate goal.