This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 7bbd202f40df camel-tui: Fix CVE audit using wrong dependency versions
7bbd202f40df is described below
commit 7bbd202f40df1e6a201d88059816bd12e83a4669
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Jul 28 14:42:14 2026 +0200
camel-tui: Fix CVE audit using wrong dependency versions
Use actual JVM classpath as primary source for CVE scanning instead of
Maven transitive resolution which did not apply BOM-managed versions,
causing false positives (e.g. reporting Netty 4.1.x CVEs when the app
actually uses 4.2.x).
camel-tui: Compute CVE audit VIA column from JAR embedded POMs
Resolve the "via" parent relationship by reading META-INF/maven POMs
from direct dependency JARs on the classpath instead of using Maven
transitive resolution. Only resolves when vulnerabilities are found.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../dsl/jbang/core/commands/tui/CveAuditTab.java | 181 +++++++++++++++++++--
1 file changed, 165 insertions(+), 16 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CveAuditTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CveAuditTab.java
index a158921ef7d4..8344a48bf128 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CveAuditTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CveAuditTab.java
@@ -16,15 +16,25 @@
*/
package org.apache.camel.dsl.jbang.core.commands.tui;
+import java.io.InputStream;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
import dev.tamboui.layout.Constraint;
import dev.tamboui.layout.Layout;
import dev.tamboui.layout.Rect;
@@ -44,6 +54,7 @@ import dev.tamboui.widgets.paragraph.Paragraph;
import dev.tamboui.widgets.table.Cell;
import dev.tamboui.widgets.table.Row;
import dev.tamboui.widgets.table.Table;
+import org.apache.camel.dsl.jbang.core.common.XmlHelper;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
@@ -387,27 +398,29 @@ class CveAuditTab extends AbstractTableTab {
ctx.backgroundExecutor.execute(() -> {
try {
- DependencyLoader.LoadResult loadResult =
DependencyLoader.loadDependencies(info);
- if (loadResult.error() != null &&
loadResult.entries().isEmpty()) {
- applyResult(Collections.emptyList(),
Collections.emptyList(), loadResult.error());
- return;
- }
-
- List<DependencyLoader.DepEntry> directDeps =
loadResult.entries();
- List<DependencyLoader.DepEntry> transitiveDeps =
DependencyLoader.resolveTransitives(directDeps);
-
- List<DependencyLoader.DepEntry> allDeps = new
ArrayList<>(directDeps);
- for (DependencyLoader.DepEntry t : transitiveDeps) {
- allDeps.add(t);
+ // use actual JVM classpath as primary source (correct
versions)
+ List<DependencyLoader.DepEntry> allDeps = fetchClasspathDeps();
+ if (allDeps.isEmpty()) {
+ // fallback to pom.xml/run.properties + Maven transitive
resolution
+ DependencyLoader.LoadResult loadResult =
DependencyLoader.loadDependencies(info);
+ if (loadResult.error() != null &&
loadResult.entries().isEmpty()) {
+ applyResult(Collections.emptyList(),
Collections.emptyList(), loadResult.error());
+ return;
+ }
+ List<DependencyLoader.DepEntry> directDeps =
loadResult.entries();
+ List<DependencyLoader.DepEntry> transitiveDeps =
DependencyLoader.resolveTransitives(directDeps);
+ allDeps = new ArrayList<>(directDeps);
+ for (DependencyLoader.DepEntry t : transitiveDeps) {
+ allDeps.add(t);
+ }
}
Map<String, List<OsvClient.Vulnerability>> vulnMap =
osvClient.queryBatch(allDeps);
+ // only resolve "via" parent mapping if vulnerabilities were
found
Map<String, String> gavToParent = new HashMap<>();
- for (DependencyLoader.DepEntry dep : allDeps) {
- if (dep.transitive() && dep.parent() != null) {
- gavToParent.put(dep.display(),
DependencyLoader.shortArtifact(dep.parent()));
- }
+ if (!vulnMap.isEmpty()) {
+ gavToParent = resolveViaParents(info, vulnMap);
}
List<VulnGroup> groups = buildVulnGroups(vulnMap, gavToParent);
@@ -424,6 +437,135 @@ class CveAuditTab extends AbstractTableTab {
});
}
+ private Map<String, String> resolveViaParents(
+ IntegrationInfo info, Map<String, List<OsvClient.Vulnerability>>
vulnMap) {
+ Map<String, String> result = new HashMap<>();
+ try {
+ // collect vulnerable groupId:artifactId keys
+ Set<String> vulnKeys = new HashSet<>();
+ for (String gav : vulnMap.keySet()) {
+ int last = gav.lastIndexOf(':');
+ if (last > 0) {
+ vulnKeys.add(gav.substring(0, last));
+ }
+ }
+ if (vulnKeys.isEmpty()) {
+ return result;
+ }
+
+ // get direct deps and build classpath JAR path map
+ DependencyLoader.LoadResult loadResult =
DependencyLoader.loadDependencies(info);
+ if (loadResult.entries().isEmpty()) {
+ return result;
+ }
+ Set<String> directKeys = new HashSet<>();
+ for (DependencyLoader.DepEntry d : loadResult.entries()) {
+ directKeys.add(d.groupId() + ":" + d.artifactId());
+ }
+ vulnKeys.removeAll(directKeys);
+ if (vulnKeys.isEmpty()) {
+ return result;
+ }
+
+ // build map of groupId:artifactId -> JAR path from classpath
+ Map<String, Path> classpathJars = new HashMap<>();
+ String pid = ctx.selectedPid;
+ if (pid != null) {
+ JsonObject action = new JsonObject();
+ action.put("action", "jvm");
+ JsonObject response = ctx.executeAction(pid, action, 5000);
+ if (response != null) {
+ Object cp = response.get("classpath");
+ if (cp instanceof JsonArray arr) {
+ for (Object item : arr) {
+ ClasspathTab.JarEntry entry =
ClasspathTab.parseJarEntry(String.valueOf(item));
+ if (entry.groupId() != null) {
+ String key = entry.groupId() + ":" +
entry.artifactId();
+ if (directKeys.contains(key)) {
+ classpathJars.put(key,
Path.of(entry.fullPath()));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // read each direct dep's JAR embedded POM to find which pulls in
the vulnerable dep
+ for (Map.Entry<String, Path> e : classpathJars.entrySet()) {
+ Set<String> declaredDeps =
readDeclaredDepsFromJar(e.getValue());
+ for (String vulnKey : vulnKeys) {
+ if (declaredDeps.contains(vulnKey) &&
!result.containsKey(vulnKey)) {
+ result.put(vulnKey,
DependencyLoader.shortArtifact(e.getKey()));
+ }
+ }
+ }
+ } catch (Exception e) {
+ // best effort
+ }
+ return result;
+ }
+
+ private static Set<String> readDeclaredDepsFromJar(Path jarPath) {
+ Set<String> deps = new HashSet<>();
+ try (java.util.jar.JarFile jar = new
java.util.jar.JarFile(jarPath.toFile())) {
+ jar.stream()
+ .filter(entry ->
entry.getName().startsWith("META-INF/maven/")
+ && entry.getName().endsWith("/pom.xml"))
+ .findFirst()
+ .ifPresent(entry -> {
+ try (InputStream is = jar.getInputStream(entry)) {
+ DocumentBuilderFactory dbf =
XmlHelper.createDocumentBuilderFactory();
+ Document dom = dbf.newDocumentBuilder().parse(is);
+ NodeList nl =
dom.getElementsByTagName("dependency");
+ for (int i = 0; i < nl.getLength(); i++) {
+ Element dep = (Element) nl.item(i);
+ String g = DependencyLoader.textContent(dep,
"groupId");
+ String a = DependencyLoader.textContent(dep,
"artifactId");
+ if (g != null && a != null) {
+ deps.add(g + ":" + a);
+ }
+ }
+ } catch (Exception ex) {
+ // skip
+ }
+ });
+ } catch (Exception e) {
+ // skip
+ }
+ return deps;
+ }
+
+ private List<DependencyLoader.DepEntry> fetchClasspathDeps() {
+ try {
+ String pid = ctx.selectedPid;
+ if (pid == null) {
+ return Collections.emptyList();
+ }
+ JsonObject action = new JsonObject();
+ action.put("action", "jvm");
+ JsonObject response = ctx.executeAction(pid, action, 5000);
+ if (response == null) {
+ return Collections.emptyList();
+ }
+ Object cp = response.get("classpath");
+ if (!(cp instanceof JsonArray arr) || arr.isEmpty()) {
+ return Collections.emptyList();
+ }
+ List<DependencyLoader.DepEntry> deps = new ArrayList<>();
+ for (Object item : arr) {
+ ClasspathTab.JarEntry entry =
ClasspathTab.parseJarEntry(String.valueOf(item));
+ if (entry.groupId() != null && entry.version() != null
+ && !DependencyLoader.skipArtifact(entry.groupId(),
entry.artifactId())) {
+ deps.add(new DependencyLoader.DepEntry(entry.groupId(),
entry.artifactId(), entry.version()));
+ }
+ }
+ deps.sort(Comparator.comparing(DependencyLoader.DepEntry::display,
String.CASE_INSENSITIVE_ORDER));
+ return deps;
+ } catch (Exception e) {
+ return Collections.emptyList();
+ }
+ }
+
private void applyResult(List<DependencyLoader.DepEntry> deps,
List<VulnGroup> groups, String error) {
if (ctx.runner == null) {
return;
@@ -483,6 +625,13 @@ class CveAuditTab extends AbstractTableTab {
if (!group.affectedGavs.contains(gav)) {
group.affectedGavs.add(gav);
String parent = gavToParent.get(gav);
+ if (parent == null) {
+ // try lookup by groupId:artifactId without version
+ int last = gav.lastIndexOf(':');
+ if (last > 0) {
+ parent = gavToParent.get(gav.substring(0, last));
+ }
+ }
if (parent != null) {
group.gavParents.put(gav, parent);
}