davsclaus commented on code in PR #25051:
URL: https://github.com/apache/camel/pull/25051#discussion_r3637015935


##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/DependencySecurityAuditTools.java:
##########
@@ -0,0 +1,282 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.mcp;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import io.quarkiverse.mcp.server.Tool;
+import io.quarkiverse.mcp.server.ToolArg;
+import io.quarkiverse.mcp.server.ToolCallException;
+import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.tooling.model.ComponentModel;
+import org.apache.camel.tooling.model.SecurityAdvisoryModel;
+
+/**
+ * MCP Tool for performing security vulnerability analysis on Camel project 
dependencies.
+ * <p>
+ * Distinct from {@link DependencyCheckTools} (dependency hygiene) and {@link 
AdvisoryTools} (Camel CVE listing), this
+ * tool cross-references a project's declared dependencies with the Camel 
security advisory database and component
+ * metadata to produce actionable vulnerability findings per artifact.
+ */
+@ApplicationScoped
+public class DependencySecurityAuditTools {
+
+    @Inject
+    CatalogService catalogService;
+
+    @Inject
+    AdvisoryService advisoryService;
+
+    @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = false),
+          description = "Perform a security vulnerability audit on a Camel 
project's dependencies. "
+                        + "Cross-references the project's pom.xml dependencies 
with the Camel security advisory "
+                        + "database to identify CVEs affecting each artifact 
at the project's Camel version. "
+                        + "Reports severity, affected version ranges, fixed 
versions, and whether the vulnerable "
+                        + "component is directly used (reachable) or only a 
transitive dependency. "
+                        + "POM content is automatically sanitized to mask 
sensitive data.")
+    public AuditResult camel_dependency_security_audit(
+            @ToolArg(description = "The pom.xml file content") String 
pomContent,
+            @ToolArg(description = "Route definitions (YAML, XML, or Java DSL) 
to determine which components "
+                                   + "are actually used (for reachability 
analysis)") String routes,
+            @ToolArg(description = ToolArgDocs.RUNTIME) String runtime,
+            @ToolArg(description = ToolArgDocs.CAMEL_VERSION) String 
camelVersion,
+            @ToolArg(description = ToolArgDocs.PLATFORM_BOM) String 
platformBom,
+            @ToolArg(description = "If true (default), mask credentials in POM 
content") Boolean sanitizePom) {
+
+        if (pomContent == null || pomContent.isBlank()) {
+            throw new ToolCallException("pomContent is required", null);
+        }
+
+        try {
+            PomSanitizer.ProcessedPom processed = 
PomSanitizer.process(pomContent, sanitizePom);
+            CamelCatalog catalog = catalogService.loadCatalog(runtime, 
camelVersion, platformBom);
+            MigrationData.PomAnalysis pom = 
MigrationData.parsePomContent(processed.content());
+
+            String effectiveVersion = pom.camelVersion() != null ? 
pom.camelVersion() : catalog.getCatalogVersion();
+
+            List<SecurityAdvisoryModel> allAdvisories = 
advisoryService.advisories();
+
+            List<String> usedSchemes = routes != null && !routes.isBlank()
+                    ? extractUsedSchemes(routes) : List.of();
+
+            Map<String, ArtifactAudit> auditByArtifact = new LinkedHashMap<>();
+
+            for (String dep : pom.dependencies()) {
+                List<AdvisoryService.AdvisoryView> matched
+                        = AdvisoryService.query(allAdvisories, 
effectiveVersion, dep, null);
+
+                if (!matched.isEmpty()) {
+                    boolean reachable = isReachable(dep, usedSchemes, catalog);
+                    List<VulnerabilityFinding> findings = new ArrayList<>();
+                    for (AdvisoryService.AdvisoryView adv : matched) {
+                        findings.add(new VulnerabilityFinding(
+                                adv.cve(),
+                                adv.summary(),
+                                mapSeverity(adv.summary()),
+                                adv.affected(),
+                                adv.fixed(),
+                                AdvisoryService.SECURITY_PAGE_URL + 
adv.cve().toLowerCase().replace("-", "-")));
+                    }
+                    auditByArtifact.put(dep, new ArtifactAudit(dep, reachable, 
findings));
+                }
+            }
+
+            for (String compName : catalog.findComponentNames()) {
+                ComponentModel model = catalog.componentModel(compName);
+                if (model == null || model.getArtifactId() == null) {
+                    continue;
+                }
+                String artifactId = model.getArtifactId();
+                if (auditByArtifact.containsKey(artifactId)) {
+                    continue;
+                }
+
+                List<AdvisoryService.AdvisoryView> matched
+                        = AdvisoryService.query(allAdvisories, 
effectiveVersion, artifactId, null);
+                if (!matched.isEmpty()) {
+                    boolean reachable = usedSchemes.contains(compName);
+                    boolean declaredDep = 
pom.dependencies().contains(artifactId);
+                    List<VulnerabilityFinding> findings = new ArrayList<>();
+                    for (AdvisoryService.AdvisoryView adv : matched) {

Review Comment:
   Same as above — use `adv.url()` instead of constructing the URL.
   
   ```suggestion
                                   adv.url()));
   ```



##########
dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/DependencySecurityAuditToolsTest.java:
##########
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.mcp;
+
+import io.quarkiverse.mcp.server.ToolCallException;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class DependencySecurityAuditToolsTest {
+
+    private static final String SIMPLE_POM = """
+            <project>
+                <properties>
+                    <camel.version>4.10.0</camel.version>
+                </properties>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.apache.camel</groupId>
+                        <artifactId>camel-core</artifactId>
+                    </dependency>
+                    <dependency>
+                        <groupId>org.apache.camel</groupId>
+                        <artifactId>camel-http</artifactId>
+                    </dependency>
+                </dependencies>
+            </project>
+            """;
+
+    private static final String CURRENT_VERSION_POM = """
+            <project>
+                <properties>
+                    <camel.version>4.22.0</camel.version>
+                </properties>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.apache.camel</groupId>
+                        <artifactId>camel-core</artifactId>
+                    </dependency>
+                </dependencies>
+            </project>
+            """;
+
+    private final DependencySecurityAuditTools tools = createTools();
+
+    private static DependencySecurityAuditTools createTools() {
+        DependencySecurityAuditTools t = new DependencySecurityAuditTools();
+        t.catalogService = new CatalogService();
+        t.advisoryService = new AdvisoryService();
+        return t;
+    }
+
+    @Test
+    void shouldRequirePomContent() {
+        assertThatThrownBy(() -> tools.camel_dependency_security_audit(null, 
null, null, null, null, null))
+                .isInstanceOf(ToolCallException.class)
+                .hasMessageContaining("pomContent is required");
+    }
+
+    @Test
+    void shouldAuditOlderVersionWithKnownCves() {
+        DependencySecurityAuditTools.AuditResult result
+                = tools.camel_dependency_security_audit(SIMPLE_POM, null, 
null, null, null, null);
+
+        assertThat(result).isNotNull();
+        assertThat(result.summary()).isNotNull();
+        assertThat(result.summary().camelVersion()).isEqualTo("4.10.0");
+        assertThat(result.summary().totalDependencies()).isGreaterThan(0);
+        assertThat(result.recommendations()).isNotNull();
+    }
+
+    @Test
+    void shouldReportCleanForCurrentVersion() {
+        DependencySecurityAuditTools.AuditResult result
+                = tools.camel_dependency_security_audit(CURRENT_VERSION_POM, 
null, null, null, null, null);
+
+        assertThat(result).isNotNull();
+        assertThat(result.summary()).isNotNull();
+    }
+
+    @Test
+    void shouldIncludeReachabilityWhenRoutesProvided() {
+        String routes = "from: \"http:example.com\"\nsteps:\n  - to: 
\"log:out\"";
+        DependencySecurityAuditTools.AuditResult result
+                = tools.camel_dependency_security_audit(SIMPLE_POM, routes, 
null, null, null, null);

Review Comment:
   This test verifies reachability analysis when routes are provided, but 
doesn't assert anything about the reachability result. Consider checking 
`reachableVulnerableArtifacts` in the summary or verifying that a specific 
artifact's `reachable` flag is set correctly.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to