Skip to content

sec(deps): shipped /app/cudly binary carries 2 dependency advisories (GO-2026-5158, GO-2026-5932) #1837

Closed as not planned

Description

@cristim

Surfaced while verifying #1835 (which closes #1833). Out of that issue's scope (#1833 is specifically about Go standard library CVEs from the toolchain) but found in the same scan, and it affects the shipped binary.

What

govulncheck -mode=binary against /app/cudly extracted from the image built on the #1835 branch, on both architectures:

Your code is affected by 2 vulnerabilities from 2 modules.

Standard-library findings: 0 (that is #1833, now fixed). The two remaining are dependency advisories:

  • GO-2026-5158
  • GO-2026-5932

At least one traces into golang.org/x/crypto.

Why it matters

These are in the binary we deploy, and they are reachable enough that govulncheck's symbol analysis surfaced them rather than filtering them out as unused code paths. /app/cudly is the API server.

Note also that CI's govulncheck runs in source mode over the six modules, so these should be visible there too. Check whether the CI job currently passes despite them, and if so, why. Either it is not failing on non-stdlib findings, or the source-mode analysis reaches a different conclusion from the binary-mode one. Both possibilities are worth understanding, because they determine whether anything would catch the next such advisory.

Fix direction

  1. Confirm both advisories against the live vulnerability database and identify the fixed versions.
  2. Bump the affected modules (go get -u, then go mod tidy) across every module in the workspace that pulls them in: this is a go.work monorepo with 6 modules (., pkg, providers/{aws,azure,gcp}, tests/e2e), and ./... only covers the current one.
  3. Re-scan the built binary, not just the source, and assert with a sound method.

Verification bar

Assert on govulncheck's summary line, or use -format json and count OSV entries. Do not use a bare grep -c 'Module: stdlib': govulncheck v1.1.4 does not emit that string, it prints a bare Standard library line, so that pattern returns 0 whether or not findings exist. That vacuous check was found on #1835 and is exactly how an artifact ships dirty behind a green result.

Related: #1836 (nothing in the pipeline scans the image we ship), #1833 (the stdlib half, fixed by #1835).

Activity

  1. cristim commented on Aug 18, 2026

    @cristim
    MemberAuthor

    Re-scoping this issue. PR #1840 clears one of the two advisories; the other cannot be fixed by any bump. #1840 deliberately says Refs rather than Closes so this stays open on its own merits.

    GO-2026-5158: fixed

    go.opentelemetry.io/otel, "baggage parsing no longer caps raw header length". Affected ranges 1.41.0-1.42.0 and 1.43.0-1.44.0; we were on v1.43.0, inside the second window. Fixed in v1.44.0.

    Arrives transitively via cloud.google.com/go/storage from providers/gcp/services/cloudstorage, so the fix was bumping the indirect require rather than adding a direct one.

    GO-2026-5932: no fix exists, and this is the decision

    golang.org/x/crypto, "golang.org/x/crypto/openpgp is unmaintained, unsafe by design". The advisory has introduced: "0" and no fixed event, so every version of the module matches, permanently. There is no version to bump to, now or later.

    It is not reachable. Two independent checks:

    • go list -deps ./... across all six modules finds no openpgp package anywhere. The repo uses x/crypto for bcrypt, chacha20poly1305, cryptobyte, hkdf and pkcs12.
    • The shipped binary's symbol table contains zero occurrences of openpgp. That method was validated against a known-called symbol before its absence was trusted: bcrypt.CompareHashAndPassword and bcrypt.GenerateFromPassword both resolve at function level, with 20 x/crypto/bcrypt references, so the check can demonstrably detect presence.

    So the options are to accept it with the reasoning recorded, or to stop depending on x/crypto entirely, which is not a proportionate response to an unmaintained subpackage we never import.

    Recommendation: accept and document, do not suppress. No ignore list, no -scan narrowing, no allowlist. If #1836 adds image scanning that fails on binary-mode findings, this advisory is the one case that will need an explicit, justified exception at that point, and it should be argued there rather than hidden here.

    Why CI is green today, and why that is not a bug

    CI runs govulncheck in source mode, which exits 0 when findings sit only at "packages you import" / "modules you require" level rather than "your code calls this". Every module returned exit 0 both before and after this work, while all three advisories appeared in the output. The gate is correctly scoped, not misconfigured.

    Binary mode on the same tree exits 3. That gap between what we scan and what we ship is #1836, and #1840 does not close it.

    Third advisory found, not in this issue's original scope

    GO-2026-5841, OOB read in compress/s2, github.com/klauspost/compress 1.16.0-1.18.7, we are on v1.18.5, fixed in v1.18.7. Arrives transitively through testcontainers-go -> moby/go-archive from internal/database/postgres/testhelpers, so it is test-only and not in the shipped binary. Not bumped in #1840 to keep that PR to one concern. Worth a separate decision.

  2. cristim commented on Aug 18, 2026

    @cristim
    MemberAuthor

    Reopened. This was auto-closed by the #1840 squash merge, and the cause was my wording rather than the intent.

    The squash body contained the sentence "This does not close #1837". GitHub's closing-keyword parser matches close #1837 and does not read the negation, so a sentence written specifically to say the issue stays open is what closed it.

    Nothing about the disposition changed: #1840 clears GO-2026-5158 only. GO-2026-5932 has introduced: "0" and no fixed event, so no bump can clear it, and it remains in the shipped binary while being unreachable. The re-scoping comment above stands and this issue is now scoped to that accept-or-remove decision.

    Worth noting for anyone writing PR text here: phrase such statements without the trigger word, for example "#1840 does not resolve this issue" or "Refs #1837, partial".

  3. cristim commented on Aug 25, 2026

    @cristim
    MemberAuthor

    Verified against origin/main @ 84a884e: no bump is left to make

    Both advisories were checked against the live vuln DB (govulncheck v1.1.4, DB updated 2026-08-25) and against a real image build. Result: one is already fixed on main, the other has no published fix and never will. The shipped image today carries zero advisories with an available fix.

    Per-advisory

    Advisory Module Affected range Fixed version Status
    GO-2026-5158 go.opentelemetry.io/otel [1.41.0, 1.42.0) and [1.43.0, 1.44.0) 1.42.0 / 1.44.0 Already fixed on main by 5108b13
    GO-2026-5932 golang.org/x/crypto (openpgp) introduced: 0, no fixed event none published Unfixable by bumping

    GO-2026-5932's OSV entry has a single range starting at 0 with no fixed event, and govulncheck prints Fixed in: N/A. The package is unmaintained by design; upstream's remedy is migrating off it, not a release. No version of golang.org/x/crypto clears it.

    All six modules enumerated

    Every module in the go.work workspace resolves go.opentelemetry.io/otel to v1.44.0, above both affected ranges:

    .                  go.opentelemetry.io/otel v1.44.0
    pkg                go.opentelemetry.io/otel v1.44.0
    providers/aws      go.opentelemetry.io/otel v1.44.0
    providers/azure    go.opentelemetry.io/otel v1.44.0
    providers/gcp      go.opentelemetry.io/otel v1.44.0
    tests/e2e          go.opentelemetry.io/otel v1.44.0
    

    No module needed a change.

    Shipped-image scan (real image, not source)

    $ docker build -t cudly:issue1837 .                        # exit 0
    $ bash scripts/scan-shipped-image.sh cudly:issue1837        # exit 0
    ==> 2 Go binary/binaries in cudly:issue1837
      -> /app/cudly (built with go1.26.6)
        no fix   GO-2026-5932  in golang.org/x/crypto  (tolerated: no published fix)
        => /app/cudly: clean (1 advisory/advisories without a published fix)
      -> /usr/local/bin/migrate (built with go1.26.6)
        => /usr/local/bin/migrate: clean (0 advisory/advisories without a published fix)
    

    The count on /app/cudly is 1, not 2. GO-2026-5158 is gone.

    GO-2026-5932 is a binary-mode artifact, not reachable code

    The issue reasoned that these surfaced "because they are reachable enough that govulncheck's symbol analysis surfaced them rather than filtering them out as unused code paths". That does not hold for 5932.

    go mod why golang.org/x/crypto/openpgp reports (main module does not need package golang.org/x/crypto/openpgp). Nothing in the import graph reaches it.

    Control experiment: a 1.7MB program whose entire body is one bcrypt.GenerateFromPassword call, requiring golang.org/x/crypto v0.53.0 and importing no openpgp package, produces the identical finding, down to the same "Vulnerable symbols found" list:

    Vulnerability #1: GO-2026-5932
      Module: golang.org/x/crypto
        Found in: golang.org/x/crypto@v0.53.0
        Fixed in: N/A
        Vulnerable symbols found:
          #1: armor.golang.org/x/crypto/openpgp/armor/*
          ...
    

    The reason: GO-2026-5932's OSV entry lists affected import paths with no symbols array. With no symbols to match, govulncheck treats each listed package as wholly vulnerable and matches on module presence at a vulnerable version. Since the range is introduced: 0 with no fix, every binary that links any part of golang.org/x/crypto reports it. The /* suffix on each reported "symbol" is that package-level wildcard, not a real function found in the binary.

    Why the source-mode CI job passes

    Not because it ignores non-stdlib findings. Source mode has the import graph, sees that openpgp is never imported, and correctly reports nothing at all for 5932. Binary mode has no source to do that with, so it falls back to module-presence matching and produces the finding. Source mode is right here and binary mode is the noisy one, which is the opposite of the direction this issue assumed.

    The separate point about exit codes does hold and is worth recording: govulncheck ./... exits 0 when findings are only at the "packages you import" or "modules you require" level, and non-zero only when a symbol is called. The current root-module scan ends with This scan also found 0 vulnerabilities in packages you import and 2 vulnerabilities in modules you require, but your code doesn't appear to call these vulnerabilities, and those two contribute nothing to the exit code.

    Assertion soundness, demonstrated in both directions

    Two binaries built from identical source, one against otel v1.43.0 and one against v1.44.0:

    input distinct OSV ids (-format json) summary line exit grep -c 'Module: stdlib'
    otel v1.43.0 1 (GO-2026-5158) Your code is affected by 1 vulnerability from 1 module. 3 0
    otel v1.44.0 0 No vulnerabilities found. 0 0

    The JSON OSV count and the summary line separate the two cases. The Module: stdlib pattern returns 0 against a genuinely vulnerable binary, confirming on real input that it certifies anything.

    The image gate's own teeth are covered by scripts/test-scan-shipped-image.sh, which passes 9/9 and asserts both directions, including PASS: stale-toolchain binary fails, and names its fixable advisories.

    Conclusion

    Nothing to change. scripts/scan-shipped-image.sh already names GO-2026-5932 in its "WHAT IT TOLERATES, AND WHY" header, correctly, including that it is unreachable in this binary. Suggest closing.

    Separately: a new advisory is about to turn the source-mode job red

    govulncheck ./... on the root module currently exits 3 on GO-2026-6253 (github.com/moby/go-archive, fixed in v0.3.0, published 2026-08-25). It is symbol-reachable via testcontainers from internal/database/postgres/testhelpers and from internal/config/store_postgres.go. Unrelated to this issue and left alone here; it needs its own.

  4. cristim commented on Aug 25, 2026

    @cristim
    MemberAuthor

    Closing on the evidence posted above. No code change is needed, and the issue's central premise turned out to be wrong in an instructive way.

    The two advisories

    GO-2026-5158 (go.opentelemetry.io/otel) was already fixed on main by commit 5108b13c8. All six workspace modules resolve otel to v1.44.0, above both affected ranges ([1.41.0,1.42.0) and [1.43.0,1.44.0)). Enumerated per module, not sampled.

    GO-2026-5932 (golang.org/x/crypto, openpgp) has no published fix. Its OSV entry carries a single range starting at introduced: 0 with no fixed event; govulncheck prints Fixed in: N/A. No bump clears it, so the fix direction in this issue was not achievable as written.

    So the count is 1, not 2, and the remaining one is unfixable by upgrading.

    The premise that did not hold

    This issue assumed both advisories were "reachable enough that govulncheck's symbol analysis surfaced them rather than filtering them out as unused code paths". For 5932 that is false.

    go mod why golang.org/x/crypto/openpgp reports (main module does not need package ...). A 1.7MB control binary whose entire body is one bcrypt call, importing no openpgp package, produces the identical finding including the same symbol list. The cause: 5932's OSV entry lists affected import paths with no symbols array, so govulncheck matches at module-presence granularity. The /* suffix on each apparent "symbol" is that wildcard, not a function located in the binary.

    Why source mode passes, corrected

    Not the exit-code mechanism, for this advisory. Source mode has the import graph, sees openpgp is never imported, and correctly reports nothing. Binary mode has no source to analyse and falls back to module presence. Source mode is right here; binary mode is the noisy one - the opposite of what this issue assumed.

    The exit-code behaviour is real but separate: the root scan ends with 0 vulnerabilities in packages you import and 2 vulnerabilities in modules you require, but your code doesn't appear to call these, and those contribute nothing to the exit code.

    The vacuous-check warning was worth stating, and was re-proven

    Built from identical source against otel v1.43.0 and v1.44.0:

    input distinct OSV ids summary line exit grep -c 'Module: stdlib'
    v1.43.0 1 (GO-2026-5158) Your code is affected by 1 vulnerability from 1 module. 3 0
    v1.44.0 0 No vulnerabilities found. 0 0

    The vacuous pattern returns 0 against a genuinely vulnerable binary. Assert on the summary line or the JSON OSV count.

    One methodology note worth keeping

    An initial go tool nm probe returned zero openpgp symbols, which looked like confirmation of unreachability. It had actually failed - no symbol section, because the binary is -s -w stripped. An empty result from a broken probe is not evidence. The control-binary experiment replaced it.

    Current state

    scripts/scan-shipped-image.sh (landed for #1836) already names GO-2026-5932 in its tolerance header, correctly, including that it is unreachable in this binary. CI builds the image and runs that scanner plus its self-test (9/9 passing). There is no bump, no doc and no guard left to add.

    The separate advisory found during this work - GO-2026-6253 in moby/go-archive, which is symbol-reachable and does have a fix - is filed as #1894 (p0, currently red on main) and is being fixed there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions