From fe4fdc786c5a117be2b33568dd55b58d9aeb0ecc Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Thu, 24 Sep 2026 13:24:29 -0400 Subject: [PATCH] fix: report archives left unexplored at the nested depth limit with nested cataloging on, java stops recursing into jars itself (the task owns recursion) and its own "nested archives not cataloged" unknown is suppressed. The task then just returned at the depth bound. So `MaxDepth: 1` on a war silently found fewer jars than leaving the feature off, and nothing in the SBOM said so. at the bound, every archive that sits there (by content sniff) is now recorded as a "nested archive not cataloged: the nested archive depth limit was reached" unknown. The CLI description for `nested-archive-max-depth` notes that it replaces java's unbounded jar-in-jar recursion. Signed-off-by: Alex Goodman --- cmd/syft/internal/options/pkg.go | 3 ++- internal/task/archive_nesting_test.go | 5 +++++ internal/task/archive_tasks.go | 7 +++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/cmd/syft/internal/options/pkg.go b/cmd/syft/internal/options/pkg.go index 3a3cc4f66..5308e2308 100644 --- a/cmd/syft/internal/options/pkg.go +++ b/cmd/syft/internal/options/pkg.go @@ -26,7 +26,8 @@ note: enabling this may result in a performance impact since all discovered comp note: this only applies to the java package cataloger, and is ignored when nested-archive-max-depth is not 0 (every archive is searched)`) descriptions.Add(&o.ExcludeBinaryOverlapByOwnership, `allows users to exclude synthetic binary packages from the sbom these packages are removed if an overlap with a non-synthetic package is found`) - descriptions.Add(&o.NestedArchiveMaxDepth, `maximum depth to recursively catalog nested archives (0 = disabled, -1 = as deep as 16 levels)`) + descriptions.Add(&o.NestedArchiveMaxDepth, `maximum depth to recursively catalog nested archives (0 = disabled, -1 = as deep as 16 levels) +note: when enabled this replaces the java cataloger's own unbounded jar-in-jar recursion, so archives deeper than this are reported as unknowns rather than cataloged`) descriptions.Add(&o.NestedArchiveMaxMemoryBytes, `maximum bytes of nested archive content held in memory at once (0 = default, -1 = unbounded)`) descriptions.Add(&o.NestedArchiveMaxDiskBytes, `maximum bytes of nested archive content on disk at once (0 = default, -1 = unbounded)`) } diff --git a/internal/task/archive_nesting_test.go b/internal/task/archive_nesting_test.go index fc0736045..b93584633 100644 --- a/internal/task/archive_nesting_test.go +++ b/internal/task/archive_nesting_test.go @@ -270,6 +270,11 @@ func Test_mixedFamilyNesting_depthCountsLevelsNotFamilies(t *testing.T) { } _, ok := s.Artifacts.FileMetadata[innermost] assert.True(t, ok, "the undescended archive must still be recorded as a file at %s", coordKey(innermost)) + + // and what was left unexplored is said out loud, not silently dropped + reasons := s.Artifacts.Unknowns[innermost] + require.Len(t, reasons, 1, "unknowns: %v", s.Artifacts.Unknowns) + assert.Contains(t, reasons[0], "depth limit was reached") }) } } diff --git a/internal/task/archive_tasks.go b/internal/task/archive_tasks.go index bbe552acd..8dfe58476 100644 --- a/internal/task/archive_tasks.go +++ b/internal/task/archive_tasks.go @@ -145,6 +145,13 @@ func (c *archiveCataloger) catalog(ctx context.Context, resolver file.Resolver, maxDepth = unboundedArchiveDepth } if depth >= maxDepth { + // java's own recursion is off while this task runs, so an archive left here is otherwise lost + // without a trace + for _, candidate := range c.discoverArchives(resolver) { + if candidate.sniffedAsArchive { + recordArchiveUnknown(builder, candidate.location.Coordinates, "nested archive not cataloged: the nested archive depth limit was reached") + } + } return nil } var errs error