Skip to content
Dan Riddell edited this page Sep 30, 2026 · 1 revision

Audit

letsgo audit [<tag>] [--repo owner/name] [--token]

A release is checked for vulnerabilities once, at the moment it is built. The vulnerability database moves afterwards. letsgo audit re-runs govulncheck against a release that already shipped, using that release's own source, and records the answer on the release itself.

This is the question nothing else answers: not "is my current branch safe" — which CI already tells you — but "is the thing people are running right now still safe".

What it audits

With no tag, audit checks the newest non-retracted stable release of each major version. A repository on v1 and v2 gets both, and nothing older: the releases people are told to use.

With a tag, it checks only that one.

letsgo audit            # the current release of each major
letsgo audit v1.4.0     # one release

What it does before scanning

The source archive's digest is checked against the manifest first. A mismatch fails the audit and writes nothing — scanning bytes that are not the release's bytes would produce an answer about something else.

The scan then runs with the manifest's own build tags and targets, so what it reports is reachable in the binaries that actually shipped, not in the module in the abstract.

The record

Results are appended to audit.json, an asset on that release:

{"schema": 1, "tag": "v1.2.0", "audits": [
  {"at": "2026-09-24T03:17:00Z", "vulndb": "2026-09-23", "govulncheck": "v1.1.4",
   "status": "affected",
   "findings": [{"id": "GO-2026-1234", "module": "golang.org/x/net", "fixed": "v0.31.0"}]}
]}

Entries are appended, never replaced. The history of what was known about a release, and when, is the useful part — a file that only ever shows today's answer cannot tell you whether a vulnerability was known before or after you deployed.

Running again with the same database date and the same findings appends nothing, so a nightly schedule does not grow the file for saying the same thing.

Nothing else on the release is touched.

Exit codes

Findings do not fail the command. The exit code is 0 whether a release is clean or affected, and non-zero only when the audit could not run — a missing govulncheck, an unreadable release, a tampered source archive.

That is deliberate. A scheduled audit that goes red the day an advisory lands gets muted within a week. The finding belongs in the record on the release, where anyone deciding whether to upgrade will see it.

How verify surfaces it

letsgo verify prints the latest entry as an informational line:

  · audit   affected by GO-2026-1234 (as of 2026-09-24)

or clean (as of ...). It never changes the verify result: a release that reproduces byte for byte is verified, and an advisory published afterwards does not make the build a lie. audit.json is also not counted as an unexpected asset.

Releases it skips

A release made before letsgo — no manifest, no source archive — is skipped with a reason rather than failed. So is an immutable release, which cannot take the extra asset.

On a schedule

on: { schedule: [{ cron: "17 3 * * *" }] }

jobs:
  audit:
    runs-on: ubuntu-latest
    permissions: { contents: write }
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-go@v7
        with: { go-version-file: go.mod }
      - uses: danielriddell21/letsgo-action@v1
        with: { command: audit }

contents: write is needed because the result is written back to the release.

Clone this wiki locally