Skip to content

Commit 34ab09d

Browse files
authored
fix(java): update logic to detect pom.xml file snapshot artifacts from remote repositories (aquasecurity#6412)
1 parent 1ba5b59 commit 34ab09d

File tree

6 files changed

+145
-33
lines changed

6 files changed

+145
-33
lines changed

docs/docs/coverage/language/java.md

+16-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,19 @@ Trivy parses your `pom.xml` file and tries to find files with dependencies from
4242
- relativePath field[^5]
4343
- local repository directory[^6].
4444

45-
If your machine doesn't have the necessary files - Trivy tries to find the information about these dependencies in the [maven repository](https://repo.maven.apache.org/maven2/).
45+
### remote repositories
46+
If your machine doesn't have the necessary files - Trivy tries to find the information about these dependencies in the remote repositories:
47+
48+
- [repositories from pom files][maven-pom-repos]
49+
- [maven central repository][maven-central]
50+
51+
Trivy reproduces Maven's repository selection and priority:
52+
53+
- for snapshot artifacts:
54+
- check only snapshot repositories from pom files (if exists)
55+
- for other artifacts:
56+
- check release repositories from pom files (if exists)
57+
- check [maven central][maven-central]
4658

4759
!!! Note
4860
Trivy only takes information about packages. We don't take a list of vulnerabilities for packages from the `maven repository`.
@@ -92,4 +104,6 @@ Make sure that you have cache[^8] directory to find licenses from `*.pom` depend
92104
[^8]: The supported directories are `$GRADLE_USER_HOME/caches` and `$HOME/.gradle/caches` (`%HOMEPATH%\.gradle\caches` for Windows).
93105

94106
[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies
95-
[maven-invoker-plugin]: https://maven.apache.org/plugins/maven-invoker-plugin/usage.html
107+
[maven-invoker-plugin]: https://maven.apache.org/plugins/maven-invoker-plugin/usage.html
108+
[maven-central]: https://repo.maven.apache.org/maven2/
109+
[maven-pom-repos]: https://maven.apache.org/settings.html#repositories

pkg/dependency/parser/java/pom/parse.go

+40-24
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ const (
3030
)
3131

3232
type options struct {
33-
offline bool
34-
remoteRepos []string
33+
offline bool
34+
releaseRemoteRepos []string
35+
snapshotRemoteRepos []string
3536
}
3637

3738
type option func(*options)
@@ -42,26 +43,27 @@ func WithOffline(offline bool) option {
4243
}
4344
}
4445

45-
func WithRemoteRepos(repos []string) option {
46+
func WithReleaseRemoteRepos(repos []string) option {
4647
return func(opts *options) {
47-
opts.remoteRepos = repos
48+
opts.releaseRemoteRepos = repos
4849
}
4950
}
5051

5152
type parser struct {
52-
logger *log.Logger
53-
rootPath string
54-
cache pomCache
55-
localRepository string
56-
remoteRepositories []string
57-
offline bool
58-
servers []Server
53+
logger *log.Logger
54+
rootPath string
55+
cache pomCache
56+
localRepository string
57+
releaseRemoteRepos []string
58+
snapshotRemoteRepos []string
59+
offline bool
60+
servers []Server
5961
}
6062

6163
func NewParser(filePath string, opts ...option) types.Parser {
6264
o := &options{
63-
offline: false,
64-
remoteRepos: []string{centralURL},
65+
offline: false,
66+
releaseRemoteRepos: []string{centralURL}, // Maven doesn't use central repository for snapshot dependencies
6567
}
6668

6769
for _, opt := range opts {
@@ -76,13 +78,14 @@ func NewParser(filePath string, opts ...option) types.Parser {
7678
}
7779

7880
return &parser{
79-
logger: log.WithPrefix("pom"),
80-
rootPath: filepath.Clean(filePath),
81-
cache: newPOMCache(),
82-
localRepository: localRepository,
83-
remoteRepositories: o.remoteRepos,
84-
offline: o.offline,
85-
servers: s.Servers,
81+
logger: log.WithPrefix("pom"),
82+
rootPath: filepath.Clean(filePath),
83+
cache: newPOMCache(),
84+
localRepository: localRepository,
85+
releaseRemoteRepos: o.releaseRemoteRepos,
86+
snapshotRemoteRepos: o.snapshotRemoteRepos,
87+
offline: o.offline,
88+
servers: s.Servers,
8689
}
8790
}
8891

@@ -324,7 +327,9 @@ func (p *parser) analyze(pom *pom, opts analysisOptions) (analysisResult, error)
324327
}
325328

326329
// Update remoteRepositories
327-
p.remoteRepositories = utils.UniqueStrings(append(pom.repositories(p.servers), p.remoteRepositories...))
330+
pomReleaseRemoteRepos, pomSnapshotRemoteRepos := pom.repositories(p.servers)
331+
p.releaseRemoteRepos = lo.Uniq(append(pomReleaseRemoteRepos, p.releaseRemoteRepos...))
332+
p.snapshotRemoteRepos = lo.Uniq(append(pomSnapshotRemoteRepos, p.snapshotRemoteRepos...))
328333

329334
// Parent
330335
parent, err := p.parseParent(pom.filePath, pom.content.Parent)
@@ -615,7 +620,7 @@ func (p *parser) tryRepository(groupID, artifactID, version string) (*pom, error
615620
}
616621

617622
// Search remote remoteRepositories
618-
loaded, err = p.fetchPOMFromRemoteRepositories(paths)
623+
loaded, err = p.fetchPOMFromRemoteRepositories(paths, isSnapshot(version))
619624
if err == nil {
620625
return loaded, nil
621626
}
@@ -630,15 +635,21 @@ func (p *parser) loadPOMFromLocalRepository(paths []string) (*pom, error) {
630635
return p.openPom(localPath)
631636
}
632637

633-
func (p *parser) fetchPOMFromRemoteRepositories(paths []string) (*pom, error) {
638+
func (p *parser) fetchPOMFromRemoteRepositories(paths []string, snapshot bool) (*pom, error) {
634639
// Do not try fetching pom.xml from remote repositories in offline mode
635640
if p.offline {
636641
p.logger.Debug("Fetching the remote pom.xml is skipped")
637642
return nil, xerrors.New("offline mode")
638643
}
639644

645+
remoteRepos := p.releaseRemoteRepos
646+
// Maven uses only snapshot repos for snapshot artifacts
647+
if snapshot {
648+
remoteRepos = p.snapshotRemoteRepos
649+
}
650+
640651
// try all remoteRepositories
641-
for _, repo := range p.remoteRepositories {
652+
for _, repo := range remoteRepos {
642653
fetched, err := p.fetchPOMFromRemoteRepository(repo, paths)
643654
if err != nil {
644655
return nil, xerrors.Errorf("fetch repository error: %w", err)
@@ -703,3 +714,8 @@ func parsePom(r io.Reader) (*pomXML, error) {
703714
func packageID(name, version string) string {
704715
return dependency.ID(ftypes.Pom, name, version)
705716
}
717+
718+
// cf. https://github.com/apache/maven/blob/259404701402230299fe05ee889ecdf1c9dae816/maven-artifact/src/main/java/org/apache/maven/artifact/DefaultArtifact.java#L482-L486
719+
func isSnapshot(ver string) bool {
720+
return strings.HasSuffix(ver, "SNAPSHOT") || ver == "LATEST"
721+
}

pkg/dependency/parser/java/pom/parse_test.go

+33-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func TestPom_Parse(t *testing.T) {
7070
},
7171
},
7272
{
73-
name: "remote repository",
73+
name: "remote release repository",
7474
inputFile: filepath.Join("testdata", "happy", "pom.xml"),
7575
local: false,
7676
want: []types.Library{
@@ -114,6 +114,37 @@ func TestPom_Parse(t *testing.T) {
114114
},
115115
},
116116
},
117+
{
118+
name: "snapshot dependency",
119+
inputFile: filepath.Join("testdata", "snapshot", "pom.xml"),
120+
local: false,
121+
want: []types.Library{
122+
{
123+
ID: "com.example:happy:1.0.0",
124+
Name: "com.example:happy",
125+
Version: "1.0.0",
126+
},
127+
{
128+
ID: "org.example:example-dependency:1.2.3-SNAPSHOT",
129+
Name: "org.example:example-dependency",
130+
Version: "1.2.3-SNAPSHOT",
131+
Locations: types.Locations{
132+
{
133+
StartLine: 14,
134+
EndLine: 18,
135+
},
136+
},
137+
},
138+
},
139+
wantDeps: []types.Dependency{
140+
{
141+
ID: "com.example:happy:1.0.0",
142+
DependsOn: []string{
143+
"org.example:example-dependency:1.2.3-SNAPSHOT",
144+
},
145+
},
146+
},
147+
},
117148
{
118149
name: "offline mode",
119150
inputFile: filepath.Join("testdata", "offline", "pom.xml"),
@@ -1295,7 +1326,7 @@ func TestPom_Parse(t *testing.T) {
12951326
remoteRepos = []string{ts.URL}
12961327
}
12971328

1298-
p := pom.NewParser(tt.inputFile, pom.WithRemoteRepos(remoteRepos), pom.WithOffline(tt.offline))
1329+
p := pom.NewParser(tt.inputFile, pom.WithReleaseRemoteRepos(remoteRepos), pom.WithOffline(tt.offline))
12991330

13001331
gotLibs, gotDeps, err := p.Parse(f)
13011332
if tt.wantErr != "" {

pkg/dependency/parser/java/pom/pom.go

+13-5
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,14 @@ func (p pom) licenses() []string {
115115
})
116116
}
117117

118-
func (p pom) repositories(servers []Server) []string {
118+
func (p pom) repositories(servers []Server) ([]string, []string) {
119119
logger := log.WithPrefix("pom")
120-
var urls []string
120+
var releaseRepos, snapshotRepos []string
121121
for _, rep := range p.content.Repositories.Repository {
122+
snapshot := rep.Snapshots.Enabled == "true"
123+
release := rep.Releases.Enabled == "true"
122124
// Add only enabled repositories
123-
if rep.Releases.Enabled == "false" && rep.Snapshots.Enabled == "false" {
125+
if !release && !snapshot {
124126
continue
125127
}
126128

@@ -140,9 +142,15 @@ func (p pom) repositories(servers []Server) []string {
140142
}
141143

142144
logger.Debug("Adding repository", log.String("id", rep.ID), log.String("url", rep.URL))
143-
urls = append(urls, repoURL.String())
145+
if snapshot {
146+
snapshotRepos = append(snapshotRepos, repoURL.String())
147+
}
148+
if release {
149+
releaseRepos = append(releaseRepos, repoURL.String())
150+
}
144151
}
145-
return urls
152+
153+
return releaseRepos, snapshotRepos
146154
}
147155

148156
type pomXML struct {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<groupId>org.example</groupId>
8+
<artifactId>example-dependency</artifactId>
9+
<version>1.2.3-SNAPSHOT</version>
10+
11+
<packaging>jar</packaging>
12+
<name>Example API Dependency</name>
13+
<description>The example API</description>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>org.example</groupId>
18+
<artifactId>example-api</artifactId>
19+
<version>2.0.0</version>
20+
</dependency>
21+
</dependencies>
22+
23+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<groupId>com.example</groupId>
6+
<artifactId>happy</artifactId>
7+
<version>1.0.0</version>
8+
9+
<name>happy</name>
10+
<description>Example</description>
11+
12+
13+
<dependencies>
14+
<dependency>
15+
<groupId>org.example</groupId>
16+
<artifactId>example-dependency</artifactId>
17+
<version>1.2.3-SNAPSHOT</version>
18+
</dependency>
19+
</dependencies>
20+
</project>

0 commit comments

Comments
 (0)