Files
syft/test/cli/archive_test.go
T
0bbcff1ae3 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>
2026-09-30 15:54:36 +00:00

116 lines
3.1 KiB
Go

package cli
import (
"archive/tar"
"archive/zip"
"bytes"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
func TestArchiveScan(t *testing.T) {
tests := []struct {
name string
args []string
archiveFixture string
env map[string]string
assertions []traitAssertion
}{
{
name: "scan an archive within the temp dir",
args: []string{
"scan",
"-o",
"json",
"file:" + createArchive(t, "testdata/archive", t.TempDir()),
},
assertions: []traitAssertion{
assertSuccessfulReturnCode,
assertJsonReport,
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 {
t.Run(test.name, func(t *testing.T) {
cmd, stdout, stderr := runSyft(t, test.env, test.args...)
for _, traitAssertionFn := range test.assertions {
traitAssertionFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
}
logOutputOnFailure(t, cmd, stdout, stderr)
})
}
}
func createArchive(t *testing.T, path string, destDir string) string {
// create a tarball of the test fixtures (not by shelling out)
archivePath := filepath.Join(destDir, "test.tar")
fh, err := os.Create(archivePath)
require.NoError(t, err)
defer fh.Close()
writer := tar.NewWriter(fh)
require.NoError(t, writer.AddFS(os.DirFS(path)))
require.NoError(t, writer.Close())
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()
}