Skip to content

fix(matcher/golang): surface advisories pinned at import-path granularity (#3398)#3424

Open
ChrisJr404 wants to merge 1 commit into
anchore:mainfrom
ChrisJr404:fix/go-matcher-import-path-granularity-3398
Open

fix(matcher/golang): surface advisories pinned at import-path granularity (#3398)#3424
ChrisJr404 wants to merge 1 commit into
anchore:mainfrom
ChrisJr404:fix/go-matcher-import-path-granularity-3398

Conversation

@ChrisJr404

Copy link
Copy Markdown

Closes #3398.

What

The Go matcher silently drops advisories pinned at an import path inside a larger module. Go binaries only retain module-granularity build info via `debug/buildinfo`, so syft emits the module (`golang.org/x/crypto`) but the advisory's package name (`golang.org/x/crypto/ssh`) is what GitHub Advisories / OSV record. The matcher's case-insensitive exact-name lookup never lands.

The issue calls out GHSA-mh2q-q3fh-2475 / CVE-2026-29181 as a recent example: pinned at `go.opentelemetry.io/otel/baggage` and `/propagation`, missed by grype against binaries linking `go.opentelemetry.io/otel` v1.40.0 until the advisory was manually amended to also list the parent module. Every future advisory filed at import-path granularity hits the same wall.

Fix

The Go matcher now supplements the existing exact-name lookup with a prefix-search keyed on the SBOM package name plus a `/` segment break. So when the SBOM has `golang.org/x/crypto`, advisories pinned at `golang.org/x/crypto/ssh` (or any deeper sub-path) surface alongside ones pinned at the module name itself.

Path-segment safety: the prefix is `p.Name + "/"`, not just `p.Name`. `golang.org/x/crypto` matches `golang.org/x/crypto/ssh` but does NOT match `golang.org/x/cryptographer`. The `/` is enforced in two places: the SQL `LIKE` pattern, and the criterion's `MatchesVulnerability` (defense-in-depth). LIKE wildcards in the prefix are neutralized via SQLite `ESCAPE` clause.

The prefix path runs only when:

  • the package type is `syftPkg.GoModulePkg`, and
  • the name contains at least one `/`.

Both conditions together prevent the synthetic `stdlib` entry or other single-segment names from fanning out across the entire Go advisory corpus.

The trade-off (over-reporting when a binary doesn't import a vulnerable sub-package of a module it depends on) is called out in the issue and is the desired direction for a vulnerability scanner; downstream reachability triage belongs to tools like govulncheck.

Implementation notes

  • New `search.ByPackageNamePrefix` criterion. `MatchesVulnerability` enforces the `prefix + "/"` boundary case-insensitively.
  • `PackageSpecifier` gains a `NamePrefix` field; the v6 store translates it into `packages.name LIKE ? ESCAPE '\\' collate nocase`. The escape clause + replacer handle `%`, `_`, and `\\` in user-supplied prefixes safely.
  • Search query builder dispatches on the new criterion type.
  • A shared internal helper (`matchPackageByEcosystem`) factors out the disclosure / unaffected / version / qualified-package pipeline so the prefix path reuses it unchanged. Prefix-matched advisories participate in version filtering and ignore-rule construction the same way exact-matched ones do — including unaffected naks.
  • CPEs are not re-queried under the prefix path; the exact-name path above already covered them for `p.Name`.

Test plan

  • `Test_ByPackageNamePrefix` covers the criterion: matches under boundary, deep sub-paths, case-insensitive, sibling-substring-without-segment-break does not match, exact name does not match, empty prefix returns false.
  • `TestMatcher_ImportPathGranularityAdvisories` exercises the matcher end-to-end via the mock vulnerability provider:
    • `golang.org/x/crypto` SBOM surfaces both an exact `golang.org/x/crypto` advisory AND an import-path `golang.org/x/crypto/ssh` advisory, while a sibling `golang.org/x/cryptographer` advisory does NOT match (boundary trap).
    • `go.opentelemetry.io/otel` SBOM surfaces sibling import-path advisories under it (the original issue's example).
    • SBOM directly matching an import-path advisory still hits via the exact path (no regression).
    • Single-segment package name (`stdlib`) does NOT fan out to the corpus.
    • Non-Go package types are unaffected.
  • `TestNewSearchCriteria` gains a case for the new prefix criterion / specifier wiring.
  • Existing `grype/matcher/golang` and `grype/matcher/internal` tests continue to pass.
  • `go build ./...`, `go vet ./grype/...` (no new warnings), `task lint` (`golangci-lint` clean), `task format` (gofmt + gosimports + go mod tidy clean).
  • Full `go test ./grype/matcher/... ./grype/search/... ./grype/db/v6/...` suite green.

…rity

Go advisories are routinely filed against an import path inside a larger
module (e.g. "golang.org/x/crypto/ssh"), while Go binaries only carry
module-granularity build info via debug/buildinfo. Syft therefore emits
the module ("golang.org/x/crypto") and the matcher's case-insensitive
exact-name lookup never lands on advisories pinned to subpaths.

Recent in-the-wild instance: GHSA-mh2q-q3fh-2475 / CVE-2026-29181 was
originally pinned at "go.opentelemetry.io/otel/baggage" and
"go.opentelemetry.io/otel/propagation". Binaries linking
"go.opentelemetry.io/otel" v1.40.0 produced no findings until the
advisory was manually amended to also list the parent module. Same
pattern resurfaces every time an advisory uses an import-path name.

This change supplements the existing exact-name match with a
prefix search keyed on p.Name + "/", so an SBOM module also
surfaces advisories whose package name is a path-segment-bounded sub-path
of it. The "/" boundary prevents an unrelated module that shares only a
name prefix substring (e.g. "golang.org/x/cryptographer" vs
"golang.org/x/crypto") from matching.

Implementation:

- New ByPackageNamePrefix criterion enforces the path-segment boundary
  in MatchesVulnerability.
- PackageSpecifier gains a NamePrefix field; v6 store translates it into
  a parameterized "packages.name LIKE ? ESCAPE '\\'" query with wildcard
  metacharacters in the prefix neutralized.
- Go matcher invokes the prefix path only when p.Type is GoModulePkg and
  the name has at least one "/", to avoid fanning out the synthetic
  "stdlib" entry or single-segment names across the corpus.
- The version, qualified-package, and unaffected-nak pipeline is reused
  unchanged via a shared internal helper, so prefix-matched advisories
  participate in version filtering and ignore-rule construction the same
  way exact-matched ones do.

The trade-off (over-reporting for binaries that don't import a vulnerable
sub-package of a module they depend on) is the desired direction for a
vulnerability scanner; downstream reachability triage is left to tools
like govulncheck.

Closes anchore#3398

Signed-off-by: ChrisJr404 <chris@hacknow.com>

@kzantow kzantow left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example in the issue indicates this was a data problem that has now been fixed. Are there other examples that show we should actually make a change here?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Go matcher silently drops advisories pinned at import-path granularity

2 participants