Skip to content

Commit 2ad8e33

Browse files
DmitriyLewenknqyf263
authored andcommitted
fix(java): update logic to detect pom.xml file snapshot artifacts from remote repositories (#6412)
1 parent 5f69937 commit 2ad8e33

File tree

6 files changed

+143
-31
lines changed

6 files changed

+143
-31
lines changed

docs/docs/coverage/language/java.md

Lines changed: 16 additions & 2 deletions
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

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ const (
3131
)
3232

3333
type options struct {
34-
offline bool
35-
remoteRepos []string
34+
offline bool
35+
releaseRemoteRepos []string
36+
snapshotRemoteRepos []string
3637
}
3738

3839
type option func(*options)
@@ -43,25 +44,26 @@ func WithOffline(offline bool) option {
4344
}
4445
}
4546

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

5253
type parser struct {
53-
rootPath string
54-
cache pomCache
55-
localRepository string
56-
remoteRepositories []string
57-
offline bool
58-
servers []Server
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,12 +78,13 @@ func NewParser(filePath string, opts ...option) types.Parser {
7678
}
7779

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

@@ -321,7 +324,9 @@ func (p *parser) analyze(pom *pom, opts analysisOptions) (analysisResult, error)
321324
}
322325

323326
// Update remoteRepositories
324-
p.remoteRepositories = utils.UniqueStrings(append(pom.repositories(p.servers), p.remoteRepositories...))
327+
pomReleaseRemoteRepos, pomSnapshotRemoteRepos := pom.repositories(p.servers)
328+
p.releaseRemoteRepos = lo.Uniq(append(pomReleaseRemoteRepos, p.releaseRemoteRepos...))
329+
p.snapshotRemoteRepos = lo.Uniq(append(pomSnapshotRemoteRepos, p.snapshotRemoteRepos...))
325330

326331
// Parent
327332
parent, err := p.parseParent(pom.filePath, pom.content.Parent)
@@ -612,7 +617,7 @@ func (p *parser) tryRepository(groupID, artifactID, version string) (*pom, error
612617
}
613618

614619
// Search remote remoteRepositories
615-
loaded, err = p.fetchPOMFromRemoteRepositories(paths)
620+
loaded, err = p.fetchPOMFromRemoteRepositories(paths, isSnapshot(version))
616621
if err == nil {
617622
return loaded, nil
618623
}
@@ -627,15 +632,21 @@ func (p *parser) loadPOMFromLocalRepository(paths []string) (*pom, error) {
627632
return p.openPom(localPath)
628633
}
629634

630-
func (p *parser) fetchPOMFromRemoteRepositories(paths []string) (*pom, error) {
635+
func (p *parser) fetchPOMFromRemoteRepositories(paths []string, snapshot bool) (*pom, error) {
631636
// Do not try fetching pom.xml from remote repositories in offline mode
632637
if p.offline {
633638
log.Logger.Debug("Fetching the remote pom.xml is skipped")
634639
return nil, xerrors.New("offline mode")
635640
}
636641

642+
remoteRepos := p.releaseRemoteRepos
643+
// Maven uses only snapshot repos for snapshot artifacts
644+
if snapshot {
645+
remoteRepos = p.snapshotRemoteRepos
646+
}
647+
637648
// try all remoteRepositories
638-
for _, repo := range p.remoteRepositories {
649+
for _, repo := range remoteRepos {
639650
fetched, err := fetchPOMFromRemoteRepository(repo, paths)
640651
if err != nil {
641652
return nil, xerrors.Errorf("fetch repository error: %w", err)
@@ -699,3 +710,8 @@ func parsePom(r io.Reader) (*pomXML, error) {
699710
func packageID(name, version string) string {
700711
return dependency.ID(ftypes.Pom, name, version)
701712
}
713+
714+
// cf. https://github.com/apache/maven/blob/259404701402230299fe05ee889ecdf1c9dae816/maven-artifact/src/main/java/org/apache/maven/artifact/DefaultArtifact.java#L482-L486
715+
func isSnapshot(ver string) bool {
716+
return strings.HasSuffix(ver, "SNAPSHOT") || ver == "LATEST"
717+
}

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

Lines changed: 33 additions & 2 deletions
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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,13 @@ func (p pom) licenses() []string {
115115
})
116116
}
117117

118-
func (p pom) repositories(servers []Server) []string {
119-
var urls []string
118+
func (p pom) repositories(servers []Server) ([]string, []string) {
119+
var releaseRepos, snapshotRepos []string
120120
for _, rep := range p.content.Repositories.Repository {
121+
snapshot := rep.Snapshots.Enabled == "true"
122+
release := rep.Releases.Enabled == "true"
121123
// Add only enabled repositories
122-
if rep.Releases.Enabled == "false" && rep.Snapshots.Enabled == "false" {
124+
if !release && !snapshot {
123125
continue
124126
}
125127

@@ -139,9 +141,15 @@ func (p pom) repositories(servers []Server) []string {
139141
}
140142

141143
log.Logger.Debugf("Adding repository %s: %s", rep.ID, rep.URL)
142-
urls = append(urls, repoURL.String())
144+
if snapshot {
145+
snapshotRepos = append(snapshotRepos, repoURL.String())
146+
}
147+
if release {
148+
releaseRepos = append(releaseRepos, repoURL.String())
149+
}
143150
}
144-
return urls
151+
152+
return releaseRepos, snapshotRepos
145153
}
146154

147155
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>
Lines changed: 20 additions & 0 deletions
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)