java-dependency-analyzer 1.0.0__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- java_dependency_analyzer-1.1.0/LICENSE +21 -0
- java_dependency_analyzer-1.0.0/README.md → java_dependency_analyzer-1.1.0/PKG-INFO +260 -175
- java_dependency_analyzer-1.0.0/PKG-INFO → java_dependency_analyzer-1.1.0/README.md +45 -19
- java_dependency_analyzer-1.1.0/java_dependency_analyzer/__init__.py +18 -0
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/cache/vulnerability_cache.py +6 -2
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/cli.py +21 -5
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/parsers/base.py +4 -4
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/parsers/gradle_dep_tree_parser.py +1 -3
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/parsers/gradle_parser.py +14 -4
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/parsers/maven_dep_tree_parser.py +1 -3
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/parsers/maven_parser.py +13 -4
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/resolvers/__init__.py +11 -11
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/resolvers/transitive.py +14 -9
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/scanners/base.py +1 -3
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/scanners/ghsa_scanner.py +2 -1
- java_dependency_analyzer-1.1.0/logging.ini +28 -0
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/pyproject.toml +39 -37
- java_dependency_analyzer-1.0.0/java_dependency_analyzer/__init__.py +0 -11
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/cache/__init__.py +0 -0
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/cache/db.py +0 -0
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/models/__init__.py +0 -0
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/models/dependency.py +0 -0
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/models/report.py +0 -0
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/parsers/__init__.py +0 -0
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/reporters/__init__.py +0 -0
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/reporters/base.py +0 -0
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/reporters/html_reporter.py +0 -0
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/reporters/json_reporter.py +0 -0
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/reporters/templates/report.html +0 -0
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/scanners/__init__.py +0 -0
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/scanners/osv_scanner.py +0 -0
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/util/__init__.py +0 -0
- {java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/util/logger.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ron Webb
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,175 +1,260 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
##
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: java-dependency-analyzer
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Java Dependency Analyzer is a tool that inspects dependencies.
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 Ron Webb
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Author: Ron Webb
|
|
28
|
+
Author-email: ron@ronella.xyz
|
|
29
|
+
Requires-Python: >=3.14
|
|
30
|
+
Classifier: License :: Other/Proprietary License
|
|
31
|
+
Classifier: Programming Language :: Python :: 3
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
33
|
+
Requires-Dist: beautifulsoup4 (>=4.14.3,<5.0.0)
|
|
34
|
+
Requires-Dist: click (>=8.3.1,<9.0.0)
|
|
35
|
+
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
36
|
+
Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
|
|
37
|
+
Requires-Dist: lxml (>=6.0.2,<7.0.0)
|
|
38
|
+
Requires-Dist: python-dotenv (>=1.2.2,<2.0.0)
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# Java Dependency Analyzer 1.1.0
|
|
42
|
+
|
|
43
|
+
> A Python CLI tool that inspects Java dependency hierarchies in Maven and Gradle projects and reports known vulnerabilities.
|
|
44
|
+
|
|
45
|
+
## Prerequisites
|
|
46
|
+
|
|
47
|
+
- Python `^3.14`
|
|
48
|
+
- [Poetry](https://python-poetry.org/) `2.2`
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
Clone the repository and install all dependencies:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
git clone <repository-url>
|
|
56
|
+
cd java-dependency-analyzer
|
|
57
|
+
poetry install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
jda <COMMAND> [OPTIONS] [FILE]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`COMMAND` is one of `gradle` or `maven`.
|
|
67
|
+
|
|
68
|
+
### gradle
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
jda gradle [OPTIONS] [FILE]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`FILE` is the path to a `build.gradle` or `build.gradle.kts` file.
|
|
75
|
+
Omit `FILE` when supplying `--dependencies`.
|
|
76
|
+
|
|
77
|
+
### maven
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
jda maven [OPTIONS] [FILE]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`FILE` is the path to a `pom.xml` file.
|
|
84
|
+
Omit `FILE` when supplying `--dependencies`.
|
|
85
|
+
|
|
86
|
+
### Options (both subcommands)
|
|
87
|
+
|
|
88
|
+
| Option | Short | Default | Description |
|
|
89
|
+
|---|---|---|---|
|
|
90
|
+
| `--dependencies` | `-d` | | Path to a pre-resolved dependency tree text file (see below). When supplied, parsing and transitive resolution are skipped. |
|
|
91
|
+
| `--output-format` | `-f` | `all` | Report format: `json`, `html`, or `all` (both). |
|
|
92
|
+
| `--output-dir` | `-o` | `.` | Directory to write the report file(s) into. |
|
|
93
|
+
| `--no-transitive` | | `false` | Skip transitive dependency resolution; analyse direct dependencies only. |
|
|
94
|
+
| `--verbose` | `-v` | `false` | Print progress messages to the console. |
|
|
95
|
+
| `--rebuild-cache` | | `false` | Delete the vulnerability cache before scanning. |
|
|
96
|
+
| `--cache-ttl` | | `7` | Cache TTL in days. Set to `0` to disable caching. |
|
|
97
|
+
|
|
98
|
+
### Exit Codes
|
|
99
|
+
|
|
100
|
+
| Code | Meaning |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `0` | Scan completed successfully; no vulnerabilities found. |
|
|
103
|
+
| `10` | Scan completed successfully; at least one vulnerability was detected. |
|
|
104
|
+
|
|
105
|
+
### Pre-resolved dependency trees (`--dependencies`)
|
|
106
|
+
|
|
107
|
+
When a Gradle or Maven project already has a dependency tree available (e.g. from CI), you can pass it directly to skip the parser and transitive resolver:
|
|
108
|
+
|
|
109
|
+
- **Gradle**: generate with `gradle dependencies --configuration runtimeClasspath > gradle.txt`
|
|
110
|
+
- **Maven**: generate with `mvn dependency:tree -Dscope=runtime > maven.txt`
|
|
111
|
+
|
|
112
|
+
The report will reflect the exact tree from the file, including all transitive dependencies.
|
|
113
|
+
|
|
114
|
+
### Examples
|
|
115
|
+
|
|
116
|
+
Analyse a Maven POM and produce both JSON and HTML reports in the current directory:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
jda maven pom.xml
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Analyse a Gradle build file and write only an HTML report to `./reports/`:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
jda gradle build.gradle -f html -o reports/
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Analyse direct dependencies only, with verbose output:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
jda gradle build.gradle.kts --no-transitive -v
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Scan using a pre-resolved Gradle dependency tree (skips transitive resolution):
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
jda gradle --dependencies runtime.txt -f json -o reports/
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Scan using a pre-resolved Maven dependency tree (skips transitive resolution):
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
jda maven --dependencies maven.txt -f json -o reports/
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Logging
|
|
147
|
+
|
|
148
|
+
The tool writes logs to `java_dependency_analyzer.log` in the current working directory, in addition to printing them to the console (`stderr`).
|
|
149
|
+
|
|
150
|
+
Logging requires a `logging.ini` file to be present in the working directory or any of its parent directories. The logger walks up the directory tree until it finds one.
|
|
151
|
+
|
|
152
|
+
**When installed via pip**, no `logging.ini` is bundled. Without it the tool falls back to console-only logging (no log file is created). To enable file logging, copy `logging.ini` from the [repository](https://github.com/rcw3bb/java-dependency-analyzer/blob/master/logging.ini) to your working directory:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
curl -O https://raw.githubusercontent.com/rcw3bb/java-dependency-analyzer/master/logging.ini
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Then run `jda` from that same directory.
|
|
159
|
+
|
|
160
|
+
## Architecture
|
|
161
|
+
|
|
162
|
+
```mermaid
|
|
163
|
+
graph TD
|
|
164
|
+
CLI["jda CLI (cli.py)"] --> Parser["DependencyParser (ABC)"]
|
|
165
|
+
Parser --> MavenParser
|
|
166
|
+
Parser --> GradleParser
|
|
167
|
+
Parser --> MavenDepTreeParser
|
|
168
|
+
Parser --> GradleDepTreeParser
|
|
169
|
+
CLI --> Resolver["TransitiveResolver<br/>(Maven Central)"]
|
|
170
|
+
CLI --> Scanner["VulnerabilityScanner (ABC)"]
|
|
171
|
+
Scanner --> OsvScanner["OsvScanner<br/>(OSV.dev API)"]
|
|
172
|
+
Scanner --> GhsaScanner["GhsaScanner<br/>(GitHub Advisory DB)"]
|
|
173
|
+
OsvScanner --> Cache["VulnerabilityCache<br/>(SQLite)"]
|
|
174
|
+
GhsaScanner --> Cache
|
|
175
|
+
CLI --> Reporter["Reporter (ABC)"]
|
|
176
|
+
Reporter --> JsonReporter
|
|
177
|
+
Reporter --> HtmlReporter
|
|
178
|
+
MavenParser --> Dependency["Dependency / Vulnerability<br/>Dataclasses"]
|
|
179
|
+
GradleParser --> Dependency
|
|
180
|
+
MavenDepTreeParser --> Dependency
|
|
181
|
+
GradleDepTreeParser --> Dependency
|
|
182
|
+
Resolver --> Dependency
|
|
183
|
+
OsvScanner --> Dependency
|
|
184
|
+
GhsaScanner --> Dependency
|
|
185
|
+
JsonReporter --> ScanResult["ScanResult"]
|
|
186
|
+
HtmlReporter --> ScanResult
|
|
187
|
+
Dependency --> ScanResult
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Components
|
|
191
|
+
|
|
192
|
+
| Component | Location | Responsibility |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| CLI | `java_dependency_analyzer/cli.py` | Entry point (`gradle` / `maven` subcommands); orchestrates parsing, resolving, scanning, and reporting. |
|
|
195
|
+
| `MavenParser` | `parsers/maven_parser.py` | Parses `pom.xml`, resolves `${property}` placeholders, filters by runtime scope. |
|
|
196
|
+
| `GradleParser` | `parsers/gradle_parser.py` | Parses Groovy DSL (`build.gradle`) and Kotlin DSL (`build.gradle.kts`) files. |
|
|
197
|
+
| `MavenDepTreeParser` | `parsers/maven_dep_tree_parser.py` | Parses `mvn dependency:tree` text output into a full dependency tree. |
|
|
198
|
+
| `GradleDepTreeParser` | `parsers/gradle_dep_tree_parser.py` | Parses `gradle dependencies` text output into a full dependency tree. |
|
|
199
|
+
| `TransitiveResolver` | `resolvers/transitive.py` | Fetches transitive dependencies by downloading POM files from Maven Central. |
|
|
200
|
+
| `OsvScanner` | `scanners/osv_scanner.py` | Queries the [OSV.dev](https://osv.dev/) batch API for known CVEs. |
|
|
201
|
+
| `GhsaScanner` | `scanners/ghsa_scanner.py` | Queries the [GitHub Advisory Database](https://github.com/advisories) REST API for security advisories. |
|
|
202
|
+
| `VulnerabilityCache` | `cache/vulnerability_cache.py` | SQLite-backed cache for raw vulnerability API payloads with configurable TTL. |
|
|
203
|
+
| `DatabaseManager` | `cache/db.py` | Manages SQLite connection lifecycle and schema initialisation. |
|
|
204
|
+
| `JsonReporter` | `reporters/json_reporter.py` | Writes a `ScanResult` to a JSON file. |
|
|
205
|
+
| `HtmlReporter` | `reporters/html_reporter.py` | Renders a `ScanResult` to a styled HTML report via a Jinja2 template. |
|
|
206
|
+
|
|
207
|
+
## Development Setup
|
|
208
|
+
|
|
209
|
+
Install all dependencies (including dev tools):
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
poetry install
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Running Tests
|
|
216
|
+
|
|
217
|
+
Run the full test suite with coverage and generate an HTML report:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
poetry run pytest --cov=java_dependency_analyzer tests --cov-report html
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Code Quality
|
|
224
|
+
|
|
225
|
+
Format and lint the source code (linter must score 10/10):
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
poetry run black java_dependency_analyzer
|
|
229
|
+
poetry run pylint java_dependency_analyzer
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Publishing to PyPI
|
|
233
|
+
|
|
234
|
+
### Prerequisites
|
|
235
|
+
|
|
236
|
+
- A [PyPI](https://pypi.org/) account with an API token.
|
|
237
|
+
|
|
238
|
+
### Configure the token
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
poetry config pypi-token.pypi <your-token>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Build and publish
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
poetry publish --build
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
This builds the source distribution and wheel, then uploads them to PyPI in one step.
|
|
251
|
+
|
|
252
|
+
> **Note:** PyPI releases are immutable. Once a version is published, it cannot be overwritten.
|
|
253
|
+
> To fix a mistake, yank the release via the PyPI web UI and publish a new version.
|
|
254
|
+
|
|
255
|
+
## [Changelog](CHANGELOG.md)
|
|
256
|
+
|
|
257
|
+
## Author
|
|
258
|
+
|
|
259
|
+
Ron Webb <ron@ronella.xyz>
|
|
260
|
+
|
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Name: java-dependency-analyzer
|
|
3
|
-
Version: 1.0.0
|
|
4
|
-
Summary: Java Dependency Analyzer is a tool that inspects dependencies.
|
|
5
|
-
Author: Ron Webb
|
|
6
|
-
Author-email: ron@ronella.xyz
|
|
7
|
-
Requires-Python: >=3.14
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
10
|
-
Requires-Dist: beautifulsoup4 (>=4.14.3,<5.0.0)
|
|
11
|
-
Requires-Dist: click (>=8.3.1,<9.0.0)
|
|
12
|
-
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
13
|
-
Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
|
|
14
|
-
Requires-Dist: lxml (>=6.0.2,<7.0.0)
|
|
15
|
-
Requires-Dist: python-dotenv (>=1.2.2,<2.0.0)
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
|
|
18
|
-
# Java Dependency Analyzer 1.0.0
|
|
1
|
+
# Java Dependency Analyzer 1.1.0
|
|
19
2
|
|
|
20
3
|
> A Python CLI tool that inspects Java dependency hierarchies in Maven and Gradle projects and reports known vulnerabilities.
|
|
21
4
|
|
|
@@ -72,6 +55,13 @@ Omit `FILE` when supplying `--dependencies`.
|
|
|
72
55
|
| `--rebuild-cache` | | `false` | Delete the vulnerability cache before scanning. |
|
|
73
56
|
| `--cache-ttl` | | `7` | Cache TTL in days. Set to `0` to disable caching. |
|
|
74
57
|
|
|
58
|
+
### Exit Codes
|
|
59
|
+
|
|
60
|
+
| Code | Meaning |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `0` | Scan completed successfully; no vulnerabilities found. |
|
|
63
|
+
| `10` | Scan completed successfully; at least one vulnerability was detected. |
|
|
64
|
+
|
|
75
65
|
### Pre-resolved dependency trees (`--dependencies`)
|
|
76
66
|
|
|
77
67
|
When a Gradle or Maven project already has a dependency tree available (e.g. from CI), you can pass it directly to skip the parser and transitive resolver:
|
|
@@ -113,6 +103,20 @@ Scan using a pre-resolved Maven dependency tree (skips transitive resolution):
|
|
|
113
103
|
jda maven --dependencies maven.txt -f json -o reports/
|
|
114
104
|
```
|
|
115
105
|
|
|
106
|
+
## Logging
|
|
107
|
+
|
|
108
|
+
The tool writes logs to `java_dependency_analyzer.log` in the current working directory, in addition to printing them to the console (`stderr`).
|
|
109
|
+
|
|
110
|
+
Logging requires a `logging.ini` file to be present in the working directory or any of its parent directories. The logger walks up the directory tree until it finds one.
|
|
111
|
+
|
|
112
|
+
**When installed via pip**, no `logging.ini` is bundled. Without it the tool falls back to console-only logging (no log file is created). To enable file logging, copy `logging.ini` from the [repository](https://github.com/rcw3bb/java-dependency-analyzer/blob/master/logging.ini) to your working directory:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
curl -O https://raw.githubusercontent.com/rcw3bb/java-dependency-analyzer/master/logging.ini
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Then run `jda` from that same directory.
|
|
119
|
+
|
|
116
120
|
## Architecture
|
|
117
121
|
|
|
118
122
|
```mermaid
|
|
@@ -185,9 +189,31 @@ poetry run black java_dependency_analyzer
|
|
|
185
189
|
poetry run pylint java_dependency_analyzer
|
|
186
190
|
```
|
|
187
191
|
|
|
192
|
+
## Publishing to PyPI
|
|
193
|
+
|
|
194
|
+
### Prerequisites
|
|
195
|
+
|
|
196
|
+
- A [PyPI](https://pypi.org/) account with an API token.
|
|
197
|
+
|
|
198
|
+
### Configure the token
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
poetry config pypi-token.pypi <your-token>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Build and publish
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
poetry publish --build
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
This builds the source distribution and wheel, then uploads them to PyPI in one step.
|
|
211
|
+
|
|
212
|
+
> **Note:** PyPI releases are immutable. Once a version is published, it cannot be overwritten.
|
|
213
|
+
> To fix a mistake, yank the release via the PyPI web UI and publish a new version.
|
|
214
|
+
|
|
188
215
|
## [Changelog](CHANGELOG.md)
|
|
189
216
|
|
|
190
217
|
## Author
|
|
191
218
|
|
|
192
219
|
Ron Webb <ron@ronella.xyz>
|
|
193
|
-
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
java_dependency_analyzer package.
|
|
3
|
+
|
|
4
|
+
Java Dependency Analyzer is a tool that inspects dependencies.
|
|
5
|
+
|
|
6
|
+
:author: Ron Webb
|
|
7
|
+
:since: 1.0.0
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
11
|
+
|
|
12
|
+
__author__ = "Ron Webb"
|
|
13
|
+
__since__ = "1.0.0"
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
__version__ = version("java-dependency-analyzer")
|
|
17
|
+
except PackageNotFoundError:
|
|
18
|
+
__version__ = "unknown"
|
|
@@ -81,14 +81,18 @@ class VulnerabilityCache:
|
|
|
81
81
|
:author: Ron Webb
|
|
82
82
|
:since: 1.0.0
|
|
83
83
|
"""
|
|
84
|
-
cursor = self._conn.execute(
|
|
84
|
+
cursor = self._conn.execute(
|
|
85
|
+
_SELECT_SQL, (source, group_id, artifact_id, version)
|
|
86
|
+
)
|
|
85
87
|
row = cursor.fetchone()
|
|
86
88
|
if row is None:
|
|
87
89
|
return None
|
|
88
90
|
payload, cached_at = row
|
|
89
91
|
if self._is_expired(cached_at):
|
|
90
92
|
with self._conn:
|
|
91
|
-
self._conn.execute(
|
|
93
|
+
self._conn.execute(
|
|
94
|
+
_DELETE_SQL, (source, group_id, artifact_id, version)
|
|
95
|
+
)
|
|
92
96
|
_logger.debug(
|
|
93
97
|
"Cache entry expired and deleted for %s/%s:%s@%s",
|
|
94
98
|
source,
|
{java_dependency_analyzer-1.0.0 → java_dependency_analyzer-1.1.0}/java_dependency_analyzer/cli.py
RENAMED
|
@@ -7,10 +7,12 @@ Command-line interface entry point for the Java Dependency Analyzer.
|
|
|
7
7
|
:since: 1.0.0
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
import sys
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
|
|
12
13
|
import click
|
|
13
14
|
|
|
15
|
+
from . import __version__
|
|
14
16
|
from .cache.db import delete_database
|
|
15
17
|
from .cache.vulnerability_cache import VulnerabilityCache
|
|
16
18
|
from .models.dependency import Dependency
|
|
@@ -31,6 +33,9 @@ __since__ = "1.0.0"
|
|
|
31
33
|
|
|
32
34
|
_logger = setup_logger(__name__)
|
|
33
35
|
|
|
36
|
+
EXIT_VULNERABILITIES_FOUND = 10
|
|
37
|
+
"""Exit status returned when vulnerabilities are detected."""
|
|
38
|
+
|
|
34
39
|
# ---------------------------------------------------------------------------
|
|
35
40
|
# Shared CLI options applied to both subcommands
|
|
36
41
|
# ---------------------------------------------------------------------------
|
|
@@ -96,6 +101,7 @@ def _common_options(func):
|
|
|
96
101
|
@click.group()
|
|
97
102
|
def main() -> None:
|
|
98
103
|
"""Java Dependency Analyzer -- inspect Java dependency trees for known vulnerabilities."""
|
|
104
|
+
_logger.info("Java Dependency Analyzer v%s", __version__)
|
|
99
105
|
|
|
100
106
|
|
|
101
107
|
# ---------------------------------------------------------------------------
|
|
@@ -162,7 +168,7 @@ def gradle( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
|
162
168
|
click.echo(f"Loading dependency tree from {dependencies}...")
|
|
163
169
|
parsed_deps = GradleDepTreeParser().parse(dependencies)
|
|
164
170
|
source = file if file is not None else dependencies
|
|
165
|
-
_run_analysis(
|
|
171
|
+
found = _run_analysis(
|
|
166
172
|
parsed_deps,
|
|
167
173
|
source_file=source,
|
|
168
174
|
output_format=output_format,
|
|
@@ -175,7 +181,7 @@ def gradle( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
|
175
181
|
if verbose:
|
|
176
182
|
click.echo(f"Parsing {Path(file).name}...") # type: ignore[arg-type]
|
|
177
183
|
parsed_deps = GradleParser().parse(file) # type: ignore[arg-type]
|
|
178
|
-
_run_analysis(
|
|
184
|
+
found = _run_analysis(
|
|
179
185
|
parsed_deps,
|
|
180
186
|
source_file=file, # type: ignore[arg-type]
|
|
181
187
|
output_format=output_format,
|
|
@@ -188,6 +194,9 @@ def gradle( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
|
188
194
|
if cache is not None:
|
|
189
195
|
cache.close()
|
|
190
196
|
|
|
197
|
+
if found:
|
|
198
|
+
sys.exit(EXIT_VULNERABILITIES_FOUND)
|
|
199
|
+
|
|
191
200
|
|
|
192
201
|
# ---------------------------------------------------------------------------
|
|
193
202
|
# maven subcommand
|
|
@@ -251,7 +260,7 @@ def maven( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
|
251
260
|
click.echo(f"Loading dependency tree from {dependencies}...")
|
|
252
261
|
parsed_deps = MavenDepTreeParser().parse(dependencies)
|
|
253
262
|
source = file if file is not None else dependencies
|
|
254
|
-
_run_analysis(
|
|
263
|
+
found = _run_analysis(
|
|
255
264
|
parsed_deps,
|
|
256
265
|
source_file=source,
|
|
257
266
|
output_format=output_format,
|
|
@@ -264,7 +273,7 @@ def maven( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
|
264
273
|
if verbose:
|
|
265
274
|
click.echo(f"Parsing {Path(file).name}...") # type: ignore[arg-type]
|
|
266
275
|
parsed_deps = MavenParser().parse(file) # type: ignore[arg-type]
|
|
267
|
-
_run_analysis(
|
|
276
|
+
found = _run_analysis(
|
|
268
277
|
parsed_deps,
|
|
269
278
|
source_file=file, # type: ignore[arg-type]
|
|
270
279
|
output_format=output_format,
|
|
@@ -277,6 +286,9 @@ def maven( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
|
277
286
|
if cache is not None:
|
|
278
287
|
cache.close()
|
|
279
288
|
|
|
289
|
+
if found:
|
|
290
|
+
sys.exit(EXIT_VULNERABILITIES_FOUND)
|
|
291
|
+
|
|
280
292
|
|
|
281
293
|
# ---------------------------------------------------------------------------
|
|
282
294
|
# Private helpers
|
|
@@ -308,11 +320,13 @@ def _run_analysis( # pylint: disable=too-many-arguments,too-many-positional-arg
|
|
|
308
320
|
no_transitive: bool,
|
|
309
321
|
verbose: bool,
|
|
310
322
|
cache: VulnerabilityCache | None,
|
|
311
|
-
) ->
|
|
323
|
+
) -> bool:
|
|
312
324
|
"""
|
|
313
325
|
Resolve transitive dependencies (unless skipped), scan for vulnerabilities,
|
|
314
326
|
and write the requested reports.
|
|
315
327
|
|
|
328
|
+
Returns True when at least one vulnerability was detected, False otherwise.
|
|
329
|
+
|
|
316
330
|
:author: Ron Webb
|
|
317
331
|
:since: 1.0.0
|
|
318
332
|
"""
|
|
@@ -345,6 +359,8 @@ def _run_analysis( # pylint: disable=too-many-arguments,too-many-positional-arg
|
|
|
345
359
|
f"{result.total_vulnerabilities} vulnerabilities found."
|
|
346
360
|
)
|
|
347
361
|
|
|
362
|
+
return result.total_vulnerabilities > 0
|
|
363
|
+
|
|
348
364
|
|
|
349
365
|
def _scan_all(
|
|
350
366
|
dependencies: list[Dependency],
|
|
@@ -21,7 +21,9 @@ __since__ = "1.0.0"
|
|
|
21
21
|
_logger = setup_logger(__name__)
|
|
22
22
|
|
|
23
23
|
# Runtime scopes that contribute to the executable classpath
|
|
24
|
-
RUNTIME_SCOPES = frozenset(
|
|
24
|
+
RUNTIME_SCOPES = frozenset(
|
|
25
|
+
{"compile", "runtime", "implementation", "api", "runtimeOnly"}
|
|
26
|
+
)
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
def attach_node(
|
|
@@ -138,9 +140,7 @@ class DepTreeParser(DependencyParser):
|
|
|
138
140
|
return build_tree_from_lines(lines, self._line_to_entry)
|
|
139
141
|
|
|
140
142
|
@abstractmethod
|
|
141
|
-
def _line_to_entry(
|
|
142
|
-
self, line: str
|
|
143
|
-
) -> tuple[int, bool, Dependency] | None:
|
|
143
|
+
def _line_to_entry(self, line: str) -> tuple[int, bool, Dependency] | None:
|
|
144
144
|
"""
|
|
145
145
|
Convert a single dependency-tree text line into a
|
|
146
146
|
``(depth, is_leaf, dep)`` tuple, or return *None* to skip the line.
|
|
@@ -56,9 +56,7 @@ class GradleDepTreeParser(DepTreeParser):
|
|
|
56
56
|
# Private helpers
|
|
57
57
|
# ------------------------------------------------------------------
|
|
58
58
|
|
|
59
|
-
def _line_to_entry(
|
|
60
|
-
self, line: str
|
|
61
|
-
) -> tuple[int, bool, Dependency] | None:
|
|
59
|
+
def _line_to_entry(self, line: str) -> tuple[int, bool, Dependency] | None:
|
|
62
60
|
"""
|
|
63
61
|
Convert a single Gradle dep-tree line to a ``(depth, is_leaf, dep)``
|
|
64
62
|
entry, or return *None* to skip the line.
|
|
@@ -139,12 +139,13 @@ class GradleParser(DependencyParser):
|
|
|
139
139
|
:author: Ron Webb
|
|
140
140
|
:since: 1.0.0
|
|
141
141
|
"""
|
|
142
|
+
|
|
142
143
|
def _replacer(match: re.Match) -> str:
|
|
143
144
|
return props.get(match.group(1), match.group(0))
|
|
144
145
|
|
|
145
146
|
return re.sub(r"\$\{(\w+)\}", _replacer, content)
|
|
146
147
|
|
|
147
|
-
def _strip_comments(self, content: str,
|
|
148
|
+
def _strip_comments(self, content: str, _is_kotlin_dsl: bool) -> str:
|
|
148
149
|
"""
|
|
149
150
|
Remove single-line (//) and block (/* */) comments from the file content.
|
|
150
151
|
|
|
@@ -165,7 +166,12 @@ class GradleParser(DependencyParser):
|
|
|
165
166
|
:since: 1.0.0
|
|
166
167
|
"""
|
|
167
168
|
deps: list[Dependency] = []
|
|
168
|
-
for pattern in (
|
|
169
|
+
for pattern in (
|
|
170
|
+
_GROOVY_SHORTHAND,
|
|
171
|
+
_KOTLIN_SHORTHAND,
|
|
172
|
+
_GROOVY_BLOCK,
|
|
173
|
+
_KOTLIN_BLOCK,
|
|
174
|
+
):
|
|
169
175
|
for match in pattern.finditer(content):
|
|
170
176
|
dep = self._match_to_dependency(match)
|
|
171
177
|
if dep is not None:
|
|
@@ -194,10 +200,14 @@ class GradleParser(DependencyParser):
|
|
|
194
200
|
|
|
195
201
|
# Skip variable references that couldn't be resolved
|
|
196
202
|
if "$" in version:
|
|
197
|
-
_logger.debug(
|
|
203
|
+
_logger.debug(
|
|
204
|
+
"Skipping %s:%s — unresolved version: %s", group, artifact, version
|
|
205
|
+
)
|
|
198
206
|
return None
|
|
199
207
|
|
|
200
|
-
_logger.debug(
|
|
208
|
+
_logger.debug(
|
|
209
|
+
"Found dependency: %s:%s:%s (scope=%s)", group, artifact, version, scope
|
|
210
|
+
)
|
|
201
211
|
return Dependency(
|
|
202
212
|
group_id=group,
|
|
203
213
|
artifact_id=artifact,
|
|
@@ -52,9 +52,7 @@ class MavenDepTreeParser(DepTreeParser):
|
|
|
52
52
|
# Private helpers
|
|
53
53
|
# ------------------------------------------------------------------
|
|
54
54
|
|
|
55
|
-
def _line_to_entry(
|
|
56
|
-
self, line: str
|
|
57
|
-
) -> tuple[int, bool, Dependency] | None:
|
|
55
|
+
def _line_to_entry(self, line: str) -> tuple[int, bool, Dependency] | None:
|
|
58
56
|
"""
|
|
59
57
|
Convert a single Maven dep-tree line to a ``(depth, is_leaf, dep)``
|
|
60
58
|
entry, or return *None* to skip the line.
|
|
@@ -43,7 +43,9 @@ class MavenParser(DependencyParser):
|
|
|
43
43
|
"""
|
|
44
44
|
_logger.info("Parsing Maven POM: %s", file_path)
|
|
45
45
|
try:
|
|
46
|
-
tree = etree.parse(
|
|
46
|
+
tree = etree.parse(
|
|
47
|
+
file_path
|
|
48
|
+
) # nosec B320 # pylint: disable=c-extension-no-member
|
|
47
49
|
except etree.XMLSyntaxError as exc: # pylint: disable=c-extension-no-member
|
|
48
50
|
_logger.error("Failed to parse POM XML: %s", exc)
|
|
49
51
|
return []
|
|
@@ -88,7 +90,9 @@ class MavenParser(DependencyParser):
|
|
|
88
90
|
props_el = root.find(self._tag("properties", namespace))
|
|
89
91
|
if props_el is not None:
|
|
90
92
|
for child in props_el:
|
|
91
|
-
local = etree.QName(
|
|
93
|
+
local = etree.QName(
|
|
94
|
+
child.tag
|
|
95
|
+
).localname # pylint: disable=c-extension-no-member
|
|
92
96
|
if child.text:
|
|
93
97
|
props[local] = child.text.strip()
|
|
94
98
|
|
|
@@ -153,9 +157,12 @@ class MavenParser(DependencyParser):
|
|
|
153
157
|
:author: Ron Webb
|
|
154
158
|
:since: 1.0.0
|
|
155
159
|
"""
|
|
160
|
+
|
|
156
161
|
def text(tag: str) -> str:
|
|
157
162
|
child_el = dep_el.find(self._tag(tag, namespace))
|
|
158
|
-
raw =
|
|
163
|
+
raw = (
|
|
164
|
+
child_el.text.strip() if child_el is not None and child_el.text else ""
|
|
165
|
+
)
|
|
159
166
|
return self._resolve_value(raw, properties)
|
|
160
167
|
|
|
161
168
|
group_id = text("groupId")
|
|
@@ -167,7 +174,9 @@ class MavenParser(DependencyParser):
|
|
|
167
174
|
return None
|
|
168
175
|
|
|
169
176
|
if scope not in RUNTIME_SCOPES:
|
|
170
|
-
_logger.debug(
|
|
177
|
+
_logger.debug(
|
|
178
|
+
"Skipping dependency %s:%s (scope=%s)", group_id, artifact_id, scope
|
|
179
|
+
)
|
|
171
180
|
return None
|
|
172
181
|
|
|
173
182
|
if not version:
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
"""
|
|
2
|
-
resolvers package.
|
|
3
|
-
|
|
4
|
-
Provides transitive dependency resolution from Maven Central.
|
|
5
|
-
|
|
6
|
-
:author: Ron Webb
|
|
7
|
-
:since: 1.0.0
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
__author__ = "Ron Webb"
|
|
11
|
-
__since__ = "1.0.0"
|
|
1
|
+
"""
|
|
2
|
+
resolvers package.
|
|
3
|
+
|
|
4
|
+
Provides transitive dependency resolution from Maven Central.
|
|
5
|
+
|
|
6
|
+
:author: Ron Webb
|
|
7
|
+
:since: 1.0.0
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__author__ = "Ron Webb"
|
|
11
|
+
__since__ = "1.0.0"
|
|
@@ -71,7 +71,9 @@ class TransitiveResolver:
|
|
|
71
71
|
_visited = set()
|
|
72
72
|
|
|
73
73
|
if depth >= _MAX_DEPTH:
|
|
74
|
-
_logger.debug(
|
|
74
|
+
_logger.debug(
|
|
75
|
+
"Max depth %d reached at %s", _MAX_DEPTH, dependency.coordinates
|
|
76
|
+
)
|
|
75
77
|
return dependency
|
|
76
78
|
|
|
77
79
|
key = dependency.coordinates
|
|
@@ -132,8 +134,7 @@ class TransitiveResolver:
|
|
|
132
134
|
:since: 1.0.0
|
|
133
135
|
"""
|
|
134
136
|
return (
|
|
135
|
-
f"{_MAVEN_CENTRAL}/{dep.maven_path}"
|
|
136
|
-
f"/{dep.artifact_id}-{dep.version}.pom"
|
|
137
|
+
f"{_MAVEN_CENTRAL}/{dep.maven_path}" f"/{dep.artifact_id}-{dep.version}.pom"
|
|
137
138
|
)
|
|
138
139
|
|
|
139
140
|
def _fetch_pom(self, url: str) -> bytes | None:
|
|
@@ -162,7 +163,9 @@ class TransitiveResolver:
|
|
|
162
163
|
:since: 1.0.0
|
|
163
164
|
"""
|
|
164
165
|
try:
|
|
165
|
-
root = etree.fromstring(
|
|
166
|
+
root = etree.fromstring(
|
|
167
|
+
pom_content
|
|
168
|
+
) # nosec B320 # pylint: disable=c-extension-no-member
|
|
166
169
|
except etree.XMLSyntaxError as exc: # pylint: disable=c-extension-no-member
|
|
167
170
|
_logger.warning("Could not parse POM for %s: %s", parent.coordinates, exc)
|
|
168
171
|
return []
|
|
@@ -171,7 +174,9 @@ class TransitiveResolver:
|
|
|
171
174
|
ns_prefix = "m:" if root.tag.startswith("{") else ""
|
|
172
175
|
|
|
173
176
|
def find(node: etree._Element, tag: str) -> etree._Element | None:
|
|
174
|
-
return
|
|
177
|
+
return (
|
|
178
|
+
node.find(f"{ns_prefix}{tag}", ns_map) if ns_prefix else node.find(tag)
|
|
179
|
+
)
|
|
175
180
|
|
|
176
181
|
def text(node: etree._Element, tag: str) -> str:
|
|
177
182
|
child = find(node, tag)
|
|
@@ -213,15 +218,15 @@ class TransitiveResolver:
|
|
|
213
218
|
"""
|
|
214
219
|
props: dict[str, str] = {}
|
|
215
220
|
props_el = (
|
|
216
|
-
root.find("m:properties", ns_map)
|
|
217
|
-
if ns_prefix
|
|
218
|
-
else root.find("properties")
|
|
221
|
+
root.find("m:properties", ns_map) if ns_prefix else root.find("properties")
|
|
219
222
|
)
|
|
220
223
|
if props_el is not None:
|
|
221
224
|
for child in props_el:
|
|
222
225
|
if not isinstance(child.tag, str): # skip comments and PIs
|
|
223
226
|
continue
|
|
224
|
-
local = etree.QName(
|
|
227
|
+
local = etree.QName(
|
|
228
|
+
child.tag
|
|
229
|
+
).localname # pylint: disable=c-extension-no-member
|
|
225
230
|
if child.text:
|
|
226
231
|
props[local] = child.text.strip()
|
|
227
232
|
return props
|
|
@@ -83,9 +83,7 @@ class VulnerabilityScanner(ABC):
|
|
|
83
83
|
payload,
|
|
84
84
|
)
|
|
85
85
|
|
|
86
|
-
def _apply_cache_source(
|
|
87
|
-
self, data: str, source: str
|
|
88
|
-
) -> list[Vulnerability]:
|
|
86
|
+
def _apply_cache_source(self, data: str, source: str) -> list[Vulnerability]:
|
|
89
87
|
"""
|
|
90
88
|
Deserialise *data* (a cached JSON string), parse into ``Vulnerability``
|
|
91
89
|
objects, and mark each with ``source = "<source>-cache"``.
|
|
@@ -97,7 +97,8 @@ class GhsaScanner(VulnerabilityScanner):
|
|
|
97
97
|
response = self._client.get(_GHSA_API_URL, params=params)
|
|
98
98
|
if response.status_code == 429:
|
|
99
99
|
_logger.warning(
|
|
100
|
-
"GitHub Advisory API rate limit exceeded for %s",
|
|
100
|
+
"GitHub Advisory API rate limit exceeded for %s",
|
|
101
|
+
dependency.coordinates,
|
|
101
102
|
)
|
|
102
103
|
return []
|
|
103
104
|
response.raise_for_status()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[loggers]
|
|
2
|
+
keys=root
|
|
3
|
+
|
|
4
|
+
[handlers]
|
|
5
|
+
keys=consoleHandler,fileHandler
|
|
6
|
+
|
|
7
|
+
[formatters]
|
|
8
|
+
keys=logFormatter,consoleFormatter
|
|
9
|
+
|
|
10
|
+
[logger_root]
|
|
11
|
+
level=INFO
|
|
12
|
+
handlers=consoleHandler,fileHandler
|
|
13
|
+
|
|
14
|
+
[handler_consoleHandler]
|
|
15
|
+
class=StreamHandler
|
|
16
|
+
formatter=consoleFormatter
|
|
17
|
+
args=(sys.stderr,)
|
|
18
|
+
|
|
19
|
+
[handler_fileHandler]
|
|
20
|
+
class=FileHandler
|
|
21
|
+
formatter=logFormatter
|
|
22
|
+
args=('java_dependency_analyzer.log', 'a')
|
|
23
|
+
|
|
24
|
+
[formatter_logFormatter]
|
|
25
|
+
format=%(asctime)s [%(levelname)s] %(name)s - %(message)s
|
|
26
|
+
|
|
27
|
+
[formatter_consoleFormatter]
|
|
28
|
+
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
|
|
@@ -1,37 +1,39 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "java-dependency-analyzer"
|
|
3
|
-
version = "1.
|
|
4
|
-
description = "Java Dependency Analyzer is a tool that inspects dependencies."
|
|
5
|
-
authors = [
|
|
6
|
-
{name = "Ron Webb",email = "ron@ronella.xyz"}
|
|
7
|
-
]
|
|
8
|
-
readme = "README.md"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
[
|
|
27
|
-
|
|
28
|
-
build-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"pytest
|
|
37
|
-
|
|
1
|
+
[project]
|
|
2
|
+
name = "java-dependency-analyzer"
|
|
3
|
+
version = "1.1.0"
|
|
4
|
+
description = "Java Dependency Analyzer is a tool that inspects dependencies."
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Ron Webb",email = "ron@ronella.xyz"}
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
license = {file = "LICENSE"}
|
|
10
|
+
requires-python = ">=3.14"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"click (>=8.3.1,<9.0.0)",
|
|
13
|
+
"httpx (>=0.28.1,<0.29.0)",
|
|
14
|
+
"beautifulsoup4 (>=4.14.3,<5.0.0)",
|
|
15
|
+
"lxml (>=6.0.2,<7.0.0)",
|
|
16
|
+
"jinja2 (>=3.1.6,<4.0.0)",
|
|
17
|
+
"python-dotenv (>=1.2.2,<2.0.0)"
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
jda = "java_dependency_analyzer.cli:main"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
[tool.poetry]
|
|
25
|
+
packages = [{include = "java_dependency_analyzer"}]
|
|
26
|
+
include = ["logging.ini"]
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
30
|
+
build-backend = "poetry.core.masonry.api"
|
|
31
|
+
|
|
32
|
+
[dependency-groups]
|
|
33
|
+
dev = [
|
|
34
|
+
"black (>=26.3.1,<27.0.0)",
|
|
35
|
+
"pylint (>=4.0.5,<5.0.0)",
|
|
36
|
+
"pytest (>=9.0.2,<10.0.0)",
|
|
37
|
+
"pytest-cov (>=7.1.0,<8.0.0)",
|
|
38
|
+
"pytest-httpx (>=0.36.0,<0.37.0)"
|
|
39
|
+
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|