narvy-cli 1.0.0__py3-none-any.whl
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.
- narvy/__init__.py +3 -0
- narvy/android/__init__.py +0 -0
- narvy/android/source_analyzer.py +268 -0
- narvy/android/split_bundle.py +475 -0
- narvy/android_rule_context.py +196 -0
- narvy/apk_memory_preflight.py +248 -0
- narvy/auth.py +40 -0
- narvy/ci_templates/bitbucket.yml +25 -0
- narvy/ci_templates/github.yml +60 -0
- narvy/ci_templates/gitlab.yml +32 -0
- narvy/cloud/__init__.py +0 -0
- narvy/cloud/aws_scan.py +1188 -0
- narvy/comment_filter.py +134 -0
- narvy/crypto_taint_lite.py +82 -0
- narvy/decompiler.py +337 -0
- narvy/doctor.py +244 -0
- narvy/host/__init__.py +0 -0
- narvy/host/audit.py +157 -0
- narvy/host/host_knowledge.py +982 -0
- narvy/host/lynis_bootstrap.py +400 -0
- narvy/host/lynis_parser.py +358 -0
- narvy/host/narvy_checks.py +789 -0
- narvy/host/report.py +69 -0
- narvy/host/ssh_exec.py +219 -0
- narvy/ios/__init__.py +0 -0
- narvy/ios/binary_analyzer.py +1201 -0
- narvy/ios/plist_checks.py +249 -0
- narvy/ios/source_analyzer.py +301 -0
- narvy/ios/third_party_filter.py +242 -0
- narvy/ios/trust_all_context.py +66 -0
- narvy/main.py +1983 -0
- narvy/native_hardening.py +503 -0
- narvy/reporter.py +151 -0
- narvy/rule_engine.py +57 -0
- narvy/rules/android/config.yml +77 -0
- narvy/rules/android/crypto.yml +34 -0
- narvy/rules/android/secrets.yml +161 -0
- narvy/rules/android/storage.yml +24 -0
- narvy/rules/android/webview.yml +23 -0
- narvy/rules/android.yml +219 -0
- narvy/rules/ios/objc/crypto.yml +56 -0
- narvy/rules/ios/objc/network.yml +67 -0
- narvy/rules/ios/objc/secrets.yml +79 -0
- narvy/rules/ios/objc/storage.yml +45 -0
- narvy/rules/ios/objc/webview.yml +45 -0
- narvy/rules/ios_swift.yml +327 -0
- narvy/rules/web/go.yml +2837 -0
- narvy/rules/web/java.yml +1576 -0
- narvy/rules/web/javascript.yml +3683 -0
- narvy/rules/web/kotlin.yml +413 -0
- narvy/rules/web/local/csharp_narvy/config.yml +61 -0
- narvy/rules/web/local/csharp_narvy/crypto.yml +64 -0
- narvy/rules/web/local/csharp_narvy/deserialization.yml +59 -0
- narvy/rules/web/local/csharp_narvy/injection.yml +122 -0
- narvy/rules/web/local/csharp_narvy/xxe.yml +48 -0
- narvy/rules/web/local/java_narvy/auth_jwt.yml +134 -0
- narvy/rules/web/local/java_narvy/deserialization.yml +108 -0
- narvy/rules/web/local/java_narvy/mybatis.yml +39 -0
- narvy/rules/web/local/java_narvy/snakeyaml.yml +34 -0
- narvy/rules/web/local/java_narvy/spring_authz.yml +32 -0
- narvy/rules/web/local/java_narvy/spring_config.yml +67 -0
- narvy/rules/web/local/java_narvy/spring_hardening.yml +291 -0
- narvy/rules/web/local/java_narvy/sqli.yml +235 -0
- narvy/rules/web/local/java_narvy/xxe.yml +212 -0
- narvy/rules/web/php.yml +1644 -0
- narvy/rules/web/python.yml +3967 -0
- narvy/rules/web/ruby.yml +703 -0
- narvy/rules/web/rust.yml +258 -0
- narvy/rules/web/secrets.yml +1420 -0
- narvy/rules/web/secrets_supplement.yml +383 -0
- narvy/sca/__init__.py +1 -0
- narvy/sca/android_deps.py +349 -0
- narvy/sca/ios_deps.py +578 -0
- narvy/sca/osv_client.py +617 -0
- narvy/sca/web_deps.py +955 -0
- narvy/scope_config.py +195 -0
- narvy/semgrep_engine.py +219 -0
- narvy/stack_protector_evidence.py +96 -0
- narvy/third_party_filter.py +211 -0
- narvy/uploader.py +92 -0
- narvy/weak_prng_context.py +270 -0
- narvy/web/__init__.py +0 -0
- narvy/web/nuclei_binary.py +105 -0
- narvy/web/scan_blocklist.py +96 -0
- narvy/web/scanner.py +842 -0
- narvy/web/source_analyzer.py +714 -0
- narvy/web/ssrf_guard.py +374 -0
- narvy_cli-1.0.0.dist-info/METADATA +165 -0
- narvy_cli-1.0.0.dist-info/RECORD +93 -0
- narvy_cli-1.0.0.dist-info/WHEEL +5 -0
- narvy_cli-1.0.0.dist-info/entry_points.txt +2 -0
- narvy_cli-1.0.0.dist-info/licenses/LICENSE +202 -0
- narvy_cli-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"""Android SCA: detect third-party dependencies in an APK/AAB zip and look up
|
|
2
|
+
their known CVEs via OSV.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import re
|
|
10
|
+
import zipfile
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any, Dict, List, Set, Tuple
|
|
13
|
+
|
|
14
|
+
from .osv_client import OSVClient, get_default_client
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
# Maven coordinates (group:artifact:version) found as .dex string constants.
|
|
19
|
+
_MAVEN_COORD_PATTERN = re.compile(r'([a-zA-Z0-9_.-]+):([a-zA-Z0-9_.-]+):([0-9][a-zA-Z0-9_.-]*)')
|
|
20
|
+
|
|
21
|
+
# Lower number = more authoritative, wins dedup.
|
|
22
|
+
_SOURCE_PRIORITY = {
|
|
23
|
+
"pom.properties": 0,
|
|
24
|
+
"version-string": 1,
|
|
25
|
+
"version-file": 2,
|
|
26
|
+
"gradle-metadata": 3,
|
|
27
|
+
"dex-strings": 4,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Hard backstop so one scan can never turn into thousands of HTTP calls.
|
|
31
|
+
_MAX_DETAIL_QUERIES = 400
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class _Dep:
|
|
36
|
+
group_id: str
|
|
37
|
+
artifact_id: str
|
|
38
|
+
version: str
|
|
39
|
+
source: str
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def maven_name(self) -> str:
|
|
43
|
+
return f"{self.group_id}:{self.artifact_id}"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _extract_from_version_files(zf: zipfile.ZipFile) -> List[_Dep]:
|
|
47
|
+
deps = []
|
|
48
|
+
GROUP_MAPPINGS = {
|
|
49
|
+
'kotlinx_coroutines': 'org.jetbrains.kotlinx',
|
|
50
|
+
'kotlinx.coroutines': 'org.jetbrains.kotlinx',
|
|
51
|
+
'kotlin': 'org.jetbrains.kotlin',
|
|
52
|
+
}
|
|
53
|
+
for name in zf.namelist():
|
|
54
|
+
if not (name.startswith('META-INF/') and name.endswith('.version')):
|
|
55
|
+
continue
|
|
56
|
+
try:
|
|
57
|
+
filename = name.replace('META-INF/', '').replace('.version', '')
|
|
58
|
+
if '_' in filename:
|
|
59
|
+
parts = filename.rsplit('_', 1)
|
|
60
|
+
group_id = parts[0]
|
|
61
|
+
artifact_id = parts[1] if len(parts) > 1 else filename
|
|
62
|
+
else:
|
|
63
|
+
group_id = filename
|
|
64
|
+
artifact_id = filename.split('.')[-1]
|
|
65
|
+
|
|
66
|
+
version = zf.read(name).decode('utf-8', errors='ignore').strip()
|
|
67
|
+
if not version or len(version) > 20 or 'task' in version.lower() or ':' in version:
|
|
68
|
+
continue
|
|
69
|
+
if len(group_id) <= 3 or len(artifact_id) <= 2:
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
for prefix, mapped_group in GROUP_MAPPINGS.items():
|
|
73
|
+
if group_id.startswith(prefix):
|
|
74
|
+
if 'kotlinx' in group_id.lower():
|
|
75
|
+
artifact_id = f'kotlinx-coroutines-{artifact_id}'
|
|
76
|
+
group_id = mapped_group
|
|
77
|
+
break
|
|
78
|
+
|
|
79
|
+
if version and group_id and artifact_id:
|
|
80
|
+
deps.append(_Dep(group_id, artifact_id, version, 'version-file'))
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.debug(f"[SCA] Error reading {name}: {e}")
|
|
83
|
+
return deps
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _extract_from_pom_properties(zf: zipfile.ZipFile) -> List[_Dep]:
|
|
87
|
+
deps = []
|
|
88
|
+
for name in zf.namelist():
|
|
89
|
+
if 'pom.properties' not in name:
|
|
90
|
+
continue
|
|
91
|
+
try:
|
|
92
|
+
content = zf.read(name).decode('utf-8', errors='ignore')
|
|
93
|
+
group_id = artifact_id = version = None
|
|
94
|
+
for line in content.split('\n'):
|
|
95
|
+
line = line.strip()
|
|
96
|
+
if line.startswith('groupId='):
|
|
97
|
+
group_id = line.split('=', 1)[1].strip()
|
|
98
|
+
elif line.startswith('artifactId='):
|
|
99
|
+
artifact_id = line.split('=', 1)[1].strip()
|
|
100
|
+
elif line.startswith('version='):
|
|
101
|
+
version = line.split('=', 1)[1].strip()
|
|
102
|
+
if group_id and artifact_id and version:
|
|
103
|
+
deps.append(_Dep(group_id, artifact_id, version, 'pom.properties'))
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.debug(f"[SCA] Error reading {name}: {e}")
|
|
106
|
+
return deps
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _extract_from_gradle_metadata(zf: zipfile.ZipFile) -> List[_Dep]:
|
|
110
|
+
deps = []
|
|
111
|
+
for name in zf.namelist():
|
|
112
|
+
if not (name.endswith('.module') or 'gradle-metadata' in name.lower()):
|
|
113
|
+
continue
|
|
114
|
+
try:
|
|
115
|
+
content = zf.read(name).decode('utf-8', errors='ignore')
|
|
116
|
+
data = json.loads(content)
|
|
117
|
+
component = data.get('component', {})
|
|
118
|
+
group = component.get('group')
|
|
119
|
+
module = component.get('module')
|
|
120
|
+
version = component.get('version')
|
|
121
|
+
if group and module and version:
|
|
122
|
+
deps.append(_Dep(group, module, version, 'gradle-metadata'))
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.debug(f"[SCA] Error parsing {name}: {e}")
|
|
125
|
+
return deps
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _is_valid_dependency(group_id: str, artifact_id: str) -> bool:
|
|
129
|
+
"""Filter noise coordinates from the dex-string regex pass (a dotless group id
|
|
130
|
+
is almost always an obfuscated class-name fragment, not a real Maven group)."""
|
|
131
|
+
invalid_patterns = (
|
|
132
|
+
'example', 'test', 'sample', 'demo', 'mock',
|
|
133
|
+
'android.support', 'androidx.', 'com.android.',
|
|
134
|
+
'kotlin.', 'kotlinx.',
|
|
135
|
+
)
|
|
136
|
+
full_name = f"{group_id}.{artifact_id}".lower()
|
|
137
|
+
if any(p in full_name for p in invalid_patterns):
|
|
138
|
+
return False
|
|
139
|
+
if '.' not in group_id:
|
|
140
|
+
return False
|
|
141
|
+
return True
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _extract_dex_strings(dex_data: bytes) -> List[str]:
|
|
145
|
+
strings = []
|
|
146
|
+
current = []
|
|
147
|
+
for byte in dex_data:
|
|
148
|
+
if 32 <= byte <= 126:
|
|
149
|
+
current.append(chr(byte))
|
|
150
|
+
else:
|
|
151
|
+
if len(current) >= 10:
|
|
152
|
+
strings.append(''.join(current))
|
|
153
|
+
current = []
|
|
154
|
+
if len(current) >= 10:
|
|
155
|
+
strings.append(''.join(current))
|
|
156
|
+
return strings
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _extract_from_dex_packages(zf: zipfile.ZipFile) -> List[_Dep]:
|
|
160
|
+
deps = []
|
|
161
|
+
for name in zf.namelist():
|
|
162
|
+
if not name.endswith('.dex'):
|
|
163
|
+
continue
|
|
164
|
+
try:
|
|
165
|
+
dex_data = zf.read(name)
|
|
166
|
+
strings = _extract_dex_strings(dex_data)
|
|
167
|
+
for s in strings:
|
|
168
|
+
for group, artifact, version in _MAVEN_COORD_PATTERN.findall(s):
|
|
169
|
+
if _is_valid_dependency(group, artifact):
|
|
170
|
+
deps.append(_Dep(group, artifact, version, 'dex-strings'))
|
|
171
|
+
except Exception as e:
|
|
172
|
+
logger.debug(f"[SCA] Error reading DEX {name}: {e}")
|
|
173
|
+
return deps
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# Recovers a version from a runtime string constant on R8-shrunk builds that
|
|
177
|
+
# strip pom.properties and carry no literal Maven coordinate. Kept narrow.
|
|
178
|
+
_VERSION_STRING_PATTERNS: List[Tuple[str, str, str]] = [
|
|
179
|
+
# (regex, group:artifact, description)
|
|
180
|
+
(r'okhttp/(\d+\.\d+\.\d+)', 'com.squareup.okhttp3:okhttp', 'OkHttp User-Agent literal'),
|
|
181
|
+
(r'Retrofit/(\d+\.\d+\.\d+)', 'com.squareup.retrofit2:retrofit', 'Retrofit User-Agent literal'),
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _extract_from_version_strings(zf: zipfile.ZipFile) -> List[_Dep]:
|
|
186
|
+
deps = []
|
|
187
|
+
compiled = [(re.compile(p, re.IGNORECASE), coord, desc) for p, coord, desc in _VERSION_STRING_PATTERNS]
|
|
188
|
+
found_coords: Set[str] = set()
|
|
189
|
+
for name in zf.namelist():
|
|
190
|
+
if not name.endswith('.dex'):
|
|
191
|
+
continue
|
|
192
|
+
try:
|
|
193
|
+
dex_data = zf.read(name)
|
|
194
|
+
strings = _extract_dex_strings(dex_data)
|
|
195
|
+
for s in strings:
|
|
196
|
+
if len(s) > 200:
|
|
197
|
+
continue
|
|
198
|
+
for pattern, coord, desc in compiled:
|
|
199
|
+
if coord in found_coords:
|
|
200
|
+
continue
|
|
201
|
+
m = pattern.search(s)
|
|
202
|
+
if m:
|
|
203
|
+
group_id, artifact_id = coord.split(':', 1)
|
|
204
|
+
deps.append(_Dep(group_id, artifact_id, m.group(1), 'version-string'))
|
|
205
|
+
found_coords.add(coord)
|
|
206
|
+
logger.debug(f"[SCA] Found {coord} version {m.group(1)} via {desc}")
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.debug(f"[SCA] Error reading DEX {name} for version strings: {e}")
|
|
209
|
+
return deps
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def extract_dependencies(apk_path: str) -> List[_Dep]:
|
|
213
|
+
all_deps: List[_Dep] = []
|
|
214
|
+
try:
|
|
215
|
+
with zipfile.ZipFile(apk_path, 'r') as zf:
|
|
216
|
+
all_deps.extend(_extract_from_version_files(zf))
|
|
217
|
+
all_deps.extend(_extract_from_pom_properties(zf))
|
|
218
|
+
all_deps.extend(_extract_from_gradle_metadata(zf))
|
|
219
|
+
all_deps.extend(_extract_from_version_strings(zf))
|
|
220
|
+
all_deps.extend(_extract_from_dex_packages(zf))
|
|
221
|
+
except zipfile.BadZipFile:
|
|
222
|
+
logger.error(f"[SCA] Invalid APK/AAB file: {apk_path}")
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logger.error(f"[SCA] Error extracting dependencies from {apk_path}: {e}")
|
|
225
|
+
return all_deps
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _dedupe_by_coordinate(deps: List[_Dep]) -> List[_Dep]:
|
|
229
|
+
best: Dict[Tuple[str, str], _Dep] = {}
|
|
230
|
+
for dep in deps:
|
|
231
|
+
key = (dep.group_id, dep.artifact_id)
|
|
232
|
+
current = best.get(key)
|
|
233
|
+
if current is None or _SOURCE_PRIORITY.get(dep.source, 9) < _SOURCE_PRIORITY.get(current.source, 9):
|
|
234
|
+
best[key] = dep
|
|
235
|
+
return list(best.values())
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _severity_style_default(sev: str) -> str:
|
|
239
|
+
return sev if sev in ("CRITICAL", "HIGH", "MEDIUM", "LOW") else "MEDIUM"
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _finding_from_osv(dep: _Dep, osv_finding_dict: Dict[str, Any]) -> Dict[str, Any]:
|
|
243
|
+
cve_id = osv_finding_dict.get("cve") or osv_finding_dict.get("osv_id") or "UNKNOWN"
|
|
244
|
+
severity = _severity_style_default(osv_finding_dict.get("severity", "MEDIUM"))
|
|
245
|
+
fixed_version = osv_finding_dict.get("fixed_version")
|
|
246
|
+
summary = osv_finding_dict.get("summary") or osv_finding_dict.get("details", "")[:400] or "No summary available."
|
|
247
|
+
|
|
248
|
+
description = (
|
|
249
|
+
f"{dep.maven_name}@{dep.version} is affected by {cve_id} "
|
|
250
|
+
f"(detected via {dep.source}). {summary}"
|
|
251
|
+
)
|
|
252
|
+
recommendation = (
|
|
253
|
+
f"Update {dep.maven_name} to version {fixed_version} or later."
|
|
254
|
+
if fixed_version
|
|
255
|
+
else f"No fixed version is listed by OSV yet for {dep.maven_name}. "
|
|
256
|
+
f"Check {dep.group_id}:{dep.artifact_id}'s release notes / consider an alternative library."
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
rule_id = f"SCA-{cve_id}"
|
|
260
|
+
return {
|
|
261
|
+
"rule_id": rule_id,
|
|
262
|
+
"file_path": dep.maven_name,
|
|
263
|
+
"name": f"{cve_id}: {dep.artifact_id}@{dep.version} (vulnerable dependency)",
|
|
264
|
+
"severity": severity,
|
|
265
|
+
"details": {
|
|
266
|
+
"description": description,
|
|
267
|
+
"recommendation": recommendation,
|
|
268
|
+
"cwe": osv_finding_dict.get("cwe", "CWE-1104"),
|
|
269
|
+
"masvs": "MSTG-CODE-5",
|
|
270
|
+
},
|
|
271
|
+
# SARIF startLine requires an int; a dependency finding has no source line.
|
|
272
|
+
"line": 1,
|
|
273
|
+
"engine": "sca",
|
|
274
|
+
"sca": {
|
|
275
|
+
"package": dep.maven_name,
|
|
276
|
+
"version": dep.version,
|
|
277
|
+
"detected_via": dep.source,
|
|
278
|
+
"fixed_version": fixed_version,
|
|
279
|
+
"references": osv_finding_dict.get("references", []),
|
|
280
|
+
"ecosystem": "Maven",
|
|
281
|
+
},
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _rule_def_from_finding(finding: Dict[str, Any]) -> Dict[str, Any]:
|
|
286
|
+
return {
|
|
287
|
+
"id": finding["rule_id"],
|
|
288
|
+
"name": finding["name"],
|
|
289
|
+
"severity": finding["severity"],
|
|
290
|
+
"masvs": finding["details"]["masvs"],
|
|
291
|
+
"details": dict(finding["details"]),
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def scan(apk_path: str, osv_client: OSVClient = None) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]], Dict[str, Any]]:
|
|
296
|
+
"""Scan an APK/AAB for vulnerable third-party dependencies: (findings, rule_defs, stats)."""
|
|
297
|
+
client = osv_client or get_default_client()
|
|
298
|
+
|
|
299
|
+
all_deps = extract_dependencies(apk_path)
|
|
300
|
+
deduped = _dedupe_by_coordinate(all_deps)
|
|
301
|
+
|
|
302
|
+
# "_detected" is the pre-cap count; "_checked" is what was actually queried.
|
|
303
|
+
stats: Dict[str, Any] = {
|
|
304
|
+
"total_dependencies_detected": len(all_deps),
|
|
305
|
+
"unique_dependencies_detected": len(deduped),
|
|
306
|
+
"unique_dependencies_checked": len(deduped),
|
|
307
|
+
"dependencies_capped": False,
|
|
308
|
+
"cves_found": 0,
|
|
309
|
+
"vulnerable_dependencies": 0,
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if not deduped:
|
|
313
|
+
return [], [], stats
|
|
314
|
+
|
|
315
|
+
if len(deduped) > _MAX_DETAIL_QUERIES:
|
|
316
|
+
logger.warning(
|
|
317
|
+
f"[SCA] {len(deduped)} unique dependencies detected - capping OSV "
|
|
318
|
+
f"queries at {_MAX_DETAIL_QUERIES} to stay considerate of the free "
|
|
319
|
+
f"public API."
|
|
320
|
+
)
|
|
321
|
+
stats["dependencies_capped"] = True
|
|
322
|
+
deduped = deduped[:_MAX_DETAIL_QUERIES]
|
|
323
|
+
stats["unique_dependencies_checked"] = len(deduped)
|
|
324
|
+
|
|
325
|
+
# Batch existence pre-filter: spend a full detail query only on packages with a vuln.
|
|
326
|
+
pkg_version_pairs = [(d.maven_name, d.version) for d in deduped]
|
|
327
|
+
has_vuln = client.has_any_vuln_batch(pkg_version_pairs, ecosystem="Maven")
|
|
328
|
+
|
|
329
|
+
findings: List[Dict[str, Any]] = []
|
|
330
|
+
rule_defs: List[Dict[str, Any]] = []
|
|
331
|
+
vulnerable_coords: Set[str] = set()
|
|
332
|
+
|
|
333
|
+
for dep in deduped:
|
|
334
|
+
if not dep.version or dep.version == "unknown":
|
|
335
|
+
continue
|
|
336
|
+
key = (dep.maven_name, dep.version)
|
|
337
|
+
if not has_vuln.get(key, False):
|
|
338
|
+
continue
|
|
339
|
+
|
|
340
|
+
osv_findings = client.query_dict(dep.maven_name, dep.version, ecosystem="Maven")
|
|
341
|
+
for osv_finding in osv_findings:
|
|
342
|
+
finding = _finding_from_osv(dep, osv_finding)
|
|
343
|
+
findings.append(finding)
|
|
344
|
+
rule_defs.append(_rule_def_from_finding(finding))
|
|
345
|
+
stats["cves_found"] += 1
|
|
346
|
+
vulnerable_coords.add(dep.maven_name)
|
|
347
|
+
|
|
348
|
+
stats["vulnerable_dependencies"] = len(vulnerable_coords)
|
|
349
|
+
return findings, rule_defs, stats
|