aquasecurity/trivy
Patterns and conventions
This page is a quick tour of the conventions you will see when reading and writing code in pkg/. None of them are unusual on their own, but they are pervasive enough to be worth calling out.
Error handling
- Wrap with
xerrors.Errorf. The codebase usesgolang.org/x/xerrorsfor error wrapping rather than the standard libraryfmt.Errorf. The pattern isreturn xerrors.Errorf("failed to do X: %w", err). Migration tofmt.Errorfhas been considered but not done — match the surrounding code. - Sentinel errors live next to the type they describe. Examples:
analyzer.ErrUnknownOS,analyzer.ErrPkgAnalysis,analyzer.ErrNoPkgsDetected(pkg/fanal/analyzer/analyzer.go). Compare witherrors.Is. types.UserErrorandtypes.ExitErrorinpkg/types/error.gocarry user-facing messages and exit codes. The CLI incmd/trivy/main.gochecks for these viaerrors.As.errcheckis strict. The lint config (.golangci.yaml) setscheck-blank: true— you cannot drop an error with_, _ = f(). Either handle it or use adeferhelper that logs.- Don't shadow
erracross goroutines. The codebase usesgolang.org/x/sync/errgroupfor parallel work; collect errors through the group, not via shared variables.
Registration patterns
A common pattern in fanal and IaC is "register from init()":
func init() {
analyzer.RegisterAnalyzer(&apkAnalyzer{})
}This means importing a package can change global state. The convention is to expose an aggregator package (e.g., pkg/fanal/analyzer/all/all.go) that imports every analyzer for its side effects. Top-level binaries import the aggregator instead of every individual analyzer.
The same shape is used for:
- IaC scanners (
pkg/iac/scanners/<provider>/) - Cloud providers (
pkg/iac/providers/) - Report writers (
pkg/report/) - Secret rules (
pkg/fanal/secret/builtin-rules.go)
Flag handling
Trivy's flag system is custom (pkg/flag/):
- Each domain owns a
*FlagGroup(e.g.,ImageFlagGroup,ScanFlagGroup). - Each command in
pkg/commands/app.gocomposes flag groups viaflag.Flags{...}.AddFlags(cmd). - After Cobra parses,
flag.Flags{...}.ToOptions(args)materializes a typedflag.Optionsstruct. - Downstream code consumes
flag.Options(and never rawviper/pflagreferences).
When you add a new flag:
- Define it on the appropriate group in
pkg/flag/<area>_flags.go. - Add a unit test in the matching
<area>_flags_test.go. - Bind it to viper if it should be configurable via env var or config file (the
Flag.ConfigNamefield controls this). - Document it in the user-facing docs (
docs/).
Report and Result shapes
pkg/types/report.go is the single source of truth for the report shape. Adding a new finding kind means:
- Add it to
types.Result(e.g., a new slice). - Update the renderers in
pkg/report/(table, JSON, SARIF, CycloneDX, SPDX, template, GitHub). - Update
pkg/rpc/convert.goto round-trip the new field over RPC. - Update fixtures in
integration/testdata/.
Logging
Use the structured logger via pkg/log:
log.Info("Detected OS", log.String("family", string(family)), log.String("name", name))
log.Warn("EOSL OS detected", log.Err(err))
log.Debug("Walked file", log.String("path", path))Avoid fmt.Printf and the standard library logger in production code paths. The progress bar in pkg/cheggaaa/... (re-exported in helpers) is the only sanctioned non-log output.
Concurrency
golang.org/x/sync/errgroupfor parallel work with cancellation.golang.org/x/sync/semaphoreto bound concurrency (used heavily by the analyzer registry to cap parallel file analysis with--parallel).pkg/parallel/andpkg/semaphore/wrap these for common cases.- Avoid raw goroutines without a waitgroup or errgroup.
Filesystem abstractions
- The walker uses
fs.FS(io/fs) interfaces wherever possible. Image layers are exposed as amapfs.FS(pkg/mapfs/) so layered overlays Just Work. - For testing,
github.com/spf13/aferois used. New code should preferio/fsif it can.
Time
Don't use time.Now() directly in code paths that produce timestamps in reports. Use clock.Now(ctx) from pkg/clock. Tests can override the clock for deterministic output.
Cobra command shape
Each subcommand is built by a New<Name>Command(globalFlags *flag.GlobalFlagGroup) *cobra.Command function. The body:
- Builds a flags struct (
<area>Flags := flag.Flags{...}). - Returns a
&cobra.Command{...}withRunEthat calls intoartifact.Run(...)or another runner. - Calls
flags.AddFlags(cmd)andflags.Bind(cmd)for viper.
Match this shape when adding a new command.
Naming
- Filenames are
snake_case.go. Test files end in_test.go. - Type names are
CamelCase; package-private helpers arecamelCase. - Constants are typed strings (e.g.,
analyzer.Type) rather thaniotaenums.
Imports
Goimports is implicit in golangci-lint. The convention is three groups: standard library, third-party, then github.com/aquasecurity/trivy/.... Most files already follow this.
Generated code
Generated files are committed:
rpc/**/*.pb.goandrpc/**/*.twirp.go— generated from.protofiles viabuf(buf.gen.yaml).pkg/iac/rego/embed.go— embedded Rego policies (built into the binary).pkg/k8s/scanner/builtin/...— generated Kubernetes resource handlers.
Regenerate with mage build:rpc or by running buf generate. Check the magefiles/ for the latest invocation.
Documentation
User-facing markdown lives under docs/ and is built with MkDocs. Per-package Go doc comments are useful for reviewers but are not the primary user-facing surface.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.