Files
syft/syft/pkg/golang.go
T
Alex Goodman da745b13e8 feat(golang): add extended-stdlib scope and module patterns for symbol capture (#5154)
* feat(golang): add extended-stdlib scope and include patterns for symbol capture

`golang.capture-symbols` decides how much symbol data lands in the SBOM for grype's reachability analysis. It's `none`, `stdlib`, or `all` today, and the useful middle is missing: `stdlib` stops at the standard library, `all` multiplies SBOM size.

A new `extended-stdlib` configurable covers stdlib plus everything under `golang.org/x/`:

```yaml
golang:
  capture-symbols: extended-stdlib
```

Also, a new `capture-symbols-include` configurable for modules that are noisy in your binaries but not everyone's. It's unioned with whatever the scope selects, so it only ever widens:

```yaml
golang:
  capture-symbols: extended-stdlib
  capture-symbols-include:
    - github.com/klauspost/**
```

Patterns are standard doublestar globs, which matters because module paths carry `/v2`-style suffixes:

```yaml
golang:
  capture-symbols-include:
    - github.com/klauspost/*     # compress, but not compress/v2
    - github.com/klauspost/**    # both
    - k8s.io/client-go           # exact match only
```

Ordering is `none` < `stdlib` < `extended-stdlib` < `all`. The existing three values
and the `none` default are unchanged, and the include list is inert under `none`.
Presets compile into glob lists internally, so a single matcher answers "does this
module get symbols" instead of a preset branch sitting next to a separate glob branch.

An unrecognized `capture-symbols` value still falls back to `none`, but warns now
instead of doing it silently. A malformed include pattern warns and gets skipped.

One thing worth a look beyond the feature: the `Symbols` field description in the JSON
schema was wrong after this (it claimed only `all` and `stdlib` populate anything), and
that description lives in the already-published `16.1.10`. Rather than bump a version for
a sentence, `16.1.10` is amended in place and `schema/json/README.md` grows an explicit
exception for description-only changes: descriptions only, no shape change of any kind,
`$id` unchanged. Anything else still needs a bump. Happy to split that into its own PR if
you'd rather review the policy separately.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* refactor(golang): rename capture-symbols-include to capture-symbols-modules

The key's entries are go module paths, and `-include` sitting next to `capture-symbols` reads as plausibly taking symbol or package names instead. Those spellings parse and match nothing, which is quieter than the confusion `-include` was picked to avoid, so the name now says what the list holds.

`golang.CatalogerConfig.CaptureSymbolsModules` and `WithCaptureSymbolsModules` rename with it. Nothing behavioral changes; the key is new in this PR so there is no compatibility surface.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* feat(golang): match capture-symbols-modules across major version suffixes

`github.com/anchore/*` covered `github.com/anchore/syft` and silently stopped covering it the day it became `github.com/anchore/syft/v2`. The config keeps parsing, nothing warns, and symbols quietly go missing from the SBOM. Exact paths had the same hole: `github.com/klauspost/compress` did not cover `compress/v2` either, so no spelling short of `**` survived a major bump.

A major version suffix is part of a module's path but not part of its identity, so patterns are now matched against the module path both with and without it, using `module.SplitPathVersion` from `golang.org/x/mod` (already a direct dep, already used in this package for `PseudoVersion`).

```yaml
golang:
  capture-symbols-modules:
    - github.com/klauspost/*          # compress and compress/v2
    - github.com/klauspost/compress   # same module at every major version
    - github.com/klauspost/compress/v2  # v2 alone
```

Only a trailing suffix is a version, which is Go's own rule. In `github.com/anchore/syft/v2/thing` the `v2` is an ordinary path element naming a major subdirectory a nested module lives in, so it stays literal and `github.com/anchore/**/thing` is how you reach it. `/v0` and `/v1` are not valid suffixes and are left alone.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-08-07 12:44:42 +00:00

63 lines
3.6 KiB
Go

package pkg
// GolangBinaryBuildinfoEntry represents all captured data for a Golang binary
type GolangBinaryBuildinfoEntry struct {
// BuildSettings contains the Go build settings and flags used to compile the binary (e.g., GOARCH, GOOS, CGO_ENABLED).
BuildSettings KeyValues `json:"goBuildSettings,omitempty" cyclonedx:"goBuildSettings"`
// GoCompiledVersion is the version of Go used to compile the binary.
GoCompiledVersion string `json:"goCompiledVersion" cyclonedx:"goCompiledVersion"`
// Architecture is the target CPU architecture for the binary (extracted from GOARCH build setting).
Architecture string `json:"architecture" cyclonedx:"architecture"`
// H1Digest is the Go module hash in h1: format for the main module from go.sum.
H1Digest string `json:"h1Digest,omitempty" cyclonedx:"h1Digest"`
// MainModule is the main module path for the binary (e.g., "github.com/anchore/syft").
MainModule string `json:"mainModule,omitempty" cyclonedx:"mainModule"`
// GoCryptoSettings contains FIPS and cryptographic configuration settings if present.
GoCryptoSettings []string `json:"goCryptoSettings,omitempty" cyclonedx:"goCryptoSettings"`
// GoExperiments lists experimental Go features enabled during compilation (e.g., "arenas", "cgocheck2").
GoExperiments []string `json:"goExperiments,omitempty" cyclonedx:"goExperiments"`
// Symbols are the function symbols from this module that are compiled into the binary, extracted from
// the binary symbol table (pclntab) and grouped by the import path of the package that owns them. Each
// value is the sorted, deduplicated list of symbol names local to that package, i.e. with the import
// path prefix stripped (e.g. import path "github.com/foo/bar" -> "(*Type).Method"). The fully qualified
// name is the import path, a ".", and the local name. One exception: the binary's main package appears
// under the key "main" (the name the linker assigns), not its original source import path, which is not
// recoverable from the binary. Populated only when the golang cataloger's capture-symbols scope covers
// this package: the "all" scope populates every module package plus the synthetic stdlib package, the
// "extended-stdlib" scope populates the stdlib package plus every module under golang.org/x/, and the
// "stdlib" scope populates only the stdlib package. The capture-symbols-modules glob patterns populate
// any additional modules they match.
Symbols map[string][]string `json:"symbols,omitempty"`
}
// GolangModuleEntry represents all captured data for a Golang source scan with go.mod/go.sum
type GolangModuleEntry struct {
// H1Digest is the Go module hash in h1: format from go.sum for verifying module contents.
H1Digest string `json:"h1Digest,omitempty" cyclonedx:"h1Digest"`
}
// GolangSourceEntry represents all captured data for a Golang package found through source analysis
type GolangSourceEntry struct {
// H1Digest is the Go module hash in h1: format from go.sum for verifying module contents.
H1Digest string `json:"h1Digest,omitempty" cyclonedx:"h1Digest"`
// OperatingSystem is the target OS for build constraints (e.g., "linux", "darwin", "windows").
OperatingSystem string `json:"os,omitempty" cyclonedx:"os"`
// Architecture is the target CPU architecture for build constraints (e.g., "amd64", "arm64").
Architecture string `json:"architecture,omitempty" cyclonedx:"architecture"`
// BuildTags are the build tags used to conditionally compile code (e.g., "integration,debug").
BuildTags string `json:"buildTags,omitempty" cyclonedx:"buildTags"`
// CgoEnabled indicates whether CGO was enabled for this package.
CgoEnabled bool `json:"cgoEnabled" cyclonedx:"cgoEnabled"`
}