Fix hang when scanning a Java resource adapter (.rar) file (#5348)

* fix: scan a Java resource adapter (.rar) given as a file source

The file source picks an extractor for the scan target from its file name
alone. A Java resource adapter archive (JCA) uses the .rar extension but is
a zip file, so it was handed to the RAR extractor. With rardecode v2.2.0 the
RAR extractor does not return on non-RAR input (an infinite loop in its
signature search), so `syft scan --from file adapter.rar` never finished.
The same bytes named .jar scan in under a second.

When the name says RAR but the content is a zip archive, leave the file
unextracted, the same way .jar, .war and .ear scan targets are handled. The
java cataloger already reads .rar files (anchore/syft#4136) and now finds the
resource adapter and its nested jars. Real RAR archives are still extracted.

Signed-off-by: Ismael Donmez <ismael.donmez@chainguard.dev>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(deps): bump github.com/nwaples/rardecode/v2 from 2.2.0 to 2.4.1

rardecode v2.2.0 loops forever in its RAR signature search when the input
does not start with a RAR signature (fixed upstream in nwaples/rardecode
f501ac8, "fix infinite loop in findSig", released in v2.2.2). syft reaches
that search for any file source whose name contains ".rar", so a scan of a
self-extracting RAR, or of any other file with such a name, never finished.
With v2.4.1 a self-extracting RAR is extracted, and a file that is not a RAR
archive fails fast with "rardecode: RAR signature not found", the same way a
file named .zip that is not a zip archive fails today.

github.com/mholt/archives v0.1.5 (the latest release) still requires v2.2.0,
so the newer version is selected here.

Signed-off-by: Ismael Donmez <ismael.donmez@chainguard.dev>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: clarify the .rar zip log line and cover self-extracting RAR sources

- the debug line no longer calls every `.rar`-named zip a resource adapter
- a self-extracting RAR (stub before the signature) given as a file source is
  still extracted

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

---------

Signed-off-by: Ismael Donmez <ismael.donmez@chainguard.dev>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Ismael Dönmez
2026-09-30 15:54:36 +00:00
committed by GitHub
co-authored by Claude Opus 5.5 Alex Goodman
parent f21e439a63
commit 0bbcff1ae3
6 changed files with 201 additions and 3 deletions
+55
View File
@@ -2,6 +2,8 @@ package cli
import (
"archive/tar"
"archive/zip"
"bytes"
"os"
"path/filepath"
"testing"
@@ -31,6 +33,25 @@ func TestArchiveScan(t *testing.T) {
assertPackageCount(1),
},
},
{
// a Java resource adapter archive is a zip file with a .rar extension
name: "scan a java resource adapter archive",
args: []string{
"scan",
"-o",
"json",
"--from",
"file",
createResourceAdapterArchive(t, t.TempDir()),
},
assertions: []traitAssertion{
assertSuccessfulReturnCode,
assertJsonReport,
assertInOutput("pkg:maven/org.example/example-ra@1.0.0"),
assertInOutput("pkg:maven/org.example/example-lib@1.0.0"),
assertPackageCount(2),
},
},
}
for _, test := range tests {
@@ -58,3 +79,37 @@ func createArchive(t *testing.T, path string, destDir string) string {
return archivePath
}
// createResourceAdapterArchive writes a minimal Java resource adapter archive (a zip file with a .rar extension that
// holds a deployment descriptor and a nested jar) into destDir and returns its path.
func createResourceAdapterArchive(t *testing.T, destDir string) string {
lib := zipBytes(t, [][2]string{
{"META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n"},
{"META-INF/maven/org.example/example-lib/pom.properties", "groupId=org.example\nartifactId=example-lib\nversion=1.0.0\n"},
})
ra := zipBytes(t, [][2]string{
{"META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n"},
{"META-INF/ra.xml", "<connector/>\n"},
{"example-lib-1.0.0.jar", string(lib)},
{"META-INF/maven/org.example/example-ra/pom.properties", "groupId=org.example\nartifactId=example-ra\nversion=1.0.0\n"},
})
archivePath := filepath.Join(destDir, "example-ra-1.0.0.rar")
require.NoError(t, os.WriteFile(archivePath, ra, 0o600))
return archivePath
}
// zipBytes returns a zip archive holding the given (name, contents) entries, in order.
func zipBytes(t *testing.T, entries [][2]string) []byte {
var buf bytes.Buffer
w := zip.NewWriter(&buf)
for _, e := range entries {
f, err := w.Create(e[0])
require.NoError(t, err)
_, err = f.Write([]byte(e[1]))
require.NoError(t, err)
}
require.NoError(t, w.Close())
return buf.Bytes()
}