mirror of
https://github.com/anchore/syft.git
synced 2026-10-30 14:02:26 +01:00
fix: keep unread nested archive limits out of java capabilities
- java embeds the shared `cataloging.ArchiveSearchConfig` but only reads the two `Include*` booleans, and setting depth or byte limits there fails `CreateSBOM` - a `capabilities-fields:` annotation on an embedded struct now limits which of its fields are listed, so the docs stop advertising settings that error - building the java cataloger directly with those fields set now warns instead of debug logging Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/capabilities/internal"
|
||||
@@ -31,6 +32,10 @@ type ConfigInfo struct {
|
||||
|
||||
var appConfigAnnotationPattern = regexp.MustCompile(`^//\s*app-config:\s*(.+)$`)
|
||||
|
||||
// embeddedFieldsAnnotationPattern matches "// capabilities-fields: A, B" on an embedded struct, which limits
|
||||
// the embedded fields listed to the ones named (for a shared type the cataloger reads only part of).
|
||||
var embeddedFieldsAnnotationPattern = regexp.MustCompile(`^//\s*capabilities-fields:\s*(.+)$`)
|
||||
|
||||
// DiscoverConfigs walks the cataloger directory and discovers all configuration structs
|
||||
// Returns map where key is "packageName.StructName" (e.g., "golang.CatalogerConfig")
|
||||
func DiscoverConfigs(repoRoot string) (map[string]ConfigInfo, error) {
|
||||
@@ -160,6 +165,9 @@ func extractCatalogerConfigFieldsRecursive(structType *ast.StructType, visitedTy
|
||||
if len(field.Names) == 0 {
|
||||
// this is an embedded field - resolve and extract its fields
|
||||
embeddedFields := resolveEmbeddedStructFields(field.Type, visitedTypes)
|
||||
if only := embeddedFieldAllowlist(field.Doc); only != nil {
|
||||
embeddedFields = slices.DeleteFunc(embeddedFields, func(f ConfigField) bool { return !only[f.Name] })
|
||||
}
|
||||
fields = append(fields, embeddedFields...)
|
||||
continue
|
||||
}
|
||||
@@ -195,6 +203,26 @@ func extractCatalogerConfigFieldsRecursive(structType *ast.StructType, visitedTy
|
||||
return fields
|
||||
}
|
||||
|
||||
// embeddedFieldAllowlist returns the field names from a "capabilities-fields:" annotation, or nil when there
|
||||
// is none (every exported embedded field is listed).
|
||||
func embeddedFieldAllowlist(commentGroup *ast.CommentGroup) map[string]bool {
|
||||
if commentGroup == nil {
|
||||
return nil
|
||||
}
|
||||
for _, comment := range commentGroup.List {
|
||||
matches := embeddedFieldsAnnotationPattern.FindStringSubmatch(comment.Text)
|
||||
if len(matches) < 2 {
|
||||
continue
|
||||
}
|
||||
only := make(map[string]bool)
|
||||
for _, name := range strings.Split(matches[1], ",") {
|
||||
only[strings.TrimSpace(name)] = true
|
||||
}
|
||||
return only
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveEmbeddedStructFields resolves an embedded struct type and extracts its fields recursively
|
||||
func resolveEmbeddedStructFields(fieldType ast.Expr, visitedTypes map[string]bool) []ConfigField {
|
||||
// extract the type name from the expression
|
||||
|
||||
@@ -457,3 +457,32 @@ func TestDiscoverAllowedConfigStructs(t *testing.T) {
|
||||
t.Logf(" - %s", configType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbeddedFieldAllowlist(t *testing.T) {
|
||||
comments := func(lines ...string) *ast.CommentGroup {
|
||||
g := &ast.CommentGroup{}
|
||||
for _, l := range lines {
|
||||
g.List = append(g.List, &ast.Comment{Text: l})
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
doc *ast.CommentGroup
|
||||
want map[string]bool
|
||||
}{
|
||||
{name: "no doc lists every field", doc: nil, want: nil},
|
||||
{name: "no annotation lists every field", doc: comments("// Deprecated: use something else."), want: nil},
|
||||
{
|
||||
name: "annotation limits to the named fields",
|
||||
doc: comments("// Deprecated: use something else.", "// capabilities-fields: IncludeIndexedArchives, IncludeUnindexedArchives"),
|
||||
want: map[string]bool{"IncludeIndexedArchives": true, "IncludeUnindexedArchives": true},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, embeddedFieldAllowlist(tt.doc))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,6 @@ configs: # AUTO-GENERATED - config structs and their fields
|
||||
description: IncludeIndexedArchives indicates whether to search within indexed archive files (e.g., .zip).
|
||||
- key: IncludeUnindexedArchives
|
||||
description: IncludeUnindexedArchives indicates whether to search within unindexed archive files (e.g., .tar*).
|
||||
- key: MaxDepth
|
||||
description: 'MaxDepth is how many levels of nested archives to catalog: 0 disables it, negative goes as deep as 16 levels.'
|
||||
- key: MaxMemoryBytes
|
||||
description: MaxMemoryBytes bounds the archive content held in memory at once; content that does not fit is written to disk. Zero holds nothing in memory, negative is unbounded.
|
||||
- key: MaxDiskBytes
|
||||
description: MaxDiskBytes bounds the archive content written to disk at once; an archive whose content does not fit is skipped or truncated. Zero writes nothing to disk, negative is unbounded. MaxMemoryBytes and MaxDiskBytes cannot both be zero while MaxDepth is not.
|
||||
- key: UseNetwork
|
||||
description: UseNetwork enables network operations for java package metadata enrichment, such as fetching parent POMs and license information.
|
||||
app_key: java.use-network
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
// NewArchiveCataloger returns a new Java archive cataloger object for detecting packages with archives (jar, war, ear, par, sar, jpi, hpi, and native-image formats)
|
||||
func NewArchiveCataloger(cfg ArchiveCatalogerConfig) pkg.Cataloger {
|
||||
if a := cfg.ArchiveSearchConfig; a.MaxDepth != 0 || a.MaxMemoryBytes != 0 || a.MaxDiskBytes != 0 {
|
||||
log.Debug("java archive cataloger ignores nested archive depth and limits, set them with syft.CreateSBOMConfig.Archive")
|
||||
log.Warn("java archive cataloger ignores nested archive depth and limits, set them with syft.CreateSBOMConfig.Archive")
|
||||
}
|
||||
gap := newGenericArchiveParserAdapter(cfg)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ type ArchiveCatalogerConfig struct {
|
||||
// Deprecated: use syft.CreateSBOMConfig.Archive. Only the two Include* booleans are read here, and
|
||||
// setting MaxDepth, MaxMemoryBytes or MaxDiskBytes is a configuration error. This will be removed in a
|
||||
// future release.
|
||||
// capabilities-fields: IncludeIndexedArchives, IncludeUnindexedArchives
|
||||
cataloging.ArchiveSearchConfig `yaml:",inline" json:"" mapstructure:",squash"`
|
||||
|
||||
// UseNetwork enables network operations for java package metadata enrichment, such as fetching parent POMs and license information.
|
||||
|
||||
Reference in New Issue
Block a user