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


##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/AdvisoryTools.java:
##########
@@ -0,0 +1,90 @@
+/*
+ * 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.List;
+
+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.tooling.model.SecurityAdvisoryModel;
+
+/**
+ * MCP Tool exposing the published Apache Camel CVE security advisories from
+ * <a href="https://camel.apache.org/security/";>camel.apache.org/security</a> 
(shipped with the Camel catalog).
+ * <p>
+ * Lets an LLM answer questions such as "is my Camel 4.10.1 project affected 
by known CVEs?" or "which CVEs were
+ * published for camel-kafka and in which versions are they fixed?".
+ */
+@ApplicationScoped
+public class AdvisoryTools {
+
+    @Inject
+    AdvisoryService advisoryService;
+
+    @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = false),
+          description = "List published Apache Camel CVE security advisories 
(the data behind "
+                        + "https://camel.apache.org/security/), optionally 
filtered by Camel version, component and "
+                        + "severity. When camelVersion is set, advisories 
whose parsed affected ranges exclude that "
+                        + "version are dropped; advisories whose ranges cannot 
be parsed are kept with "
+                        + "affectsGivenVersion unset - judge those from the 
'affected' text. The advisory data ships "
+                        + "with the Camel catalog (synced from the published 
advisories when Camel is released), so "
+                        + "advisories published after this Camel version was 
released are not included - check the "
+                        + "web page for the very latest.")
+    public AdvisoriesResult camel_security_advisories(
+            @ToolArg(description = "Camel version to check, e.g. 4.10.1 
(optional)") String camelVersion,
+            @ToolArg(description = "Component to filter by, e.g. kafka or 
camel-kafka (optional; best-effort match "
+                                   + "against components named in the advisory 
text - older advisories may not name "
+                                   + "components)") String component,
+            @ToolArg(description = "Severity to filter by as published, e.g. 
LOW, MEDIUM, MODERATE, IMPORTANT or "
+                                   + "CRITICAL (optional)") String severity) {

Review Comment:
   The description lists `MODERATE` and `IMPORTANT` as valid severity values, 
but the generated JSON only uses `LOW`, `MEDIUM`, `HIGH`, `CRITICAL` (the mojo 
normalizes with `toUpperCase`). Listing non-existent values will confuse the 
LLM into using filter values that never match.
   
   ```suggestion
               @ToolArg(description = "Severity to filter by: LOW, MEDIUM, 
HIGH, or CRITICAL "
                                      + "(optional)") String severity) {
   ```



##########
tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/UpdateSecurityAdvisoriesMojo.java:
##########
@@ -0,0 +1,292 @@
+/*
+ * 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.maven.packaging;
+
+import java.io.File;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.inject.Inject;
+
+import org.apache.camel.tooling.model.JsonMapper;
+import org.apache.camel.tooling.model.SecurityAdvisoryModel;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProjectHelper;
+import org.codehaus.plexus.build.BuildContext;
+import org.snakeyaml.engine.v2.api.Load;
+import org.snakeyaml.engine.v2.api.LoadSettings;
+
+/**
+ * Syncs the published Apache Camel CVE security advisories (the sources behind
+ * <a href="https://camel.apache.org/security/";>camel.apache.org/security</a>, 
maintained as Markdown files with YAML
+ * front matter in the camel-website git repository) into a JSON file shipped 
with camel-catalog, the same way the known
+ * releases are synced by {@code update-camel-releases}.
+ */
+@Mojo(name = "update-security-advisories", threadSafe = true, defaultPhase = 
LifecyclePhase.GENERATE_RESOURCES)
+public class UpdateSecurityAdvisoriesMojo extends AbstractGeneratorMojo {
+
+    private static final String GIT_SECURITY_URL
+            = 
"https://api.github.com/repos/apache/camel-website/contents/content/security";;
+    private static final String WEBSITE_BASE_URL = "https://camel.apache.org";;
+
+    private static final Pattern ADVISORY_FILE = 
Pattern.compile("CVE-\\d{4}-\\d{4,}\\.md");
+    private static final Pattern COMPONENT_TOKEN = 
Pattern.compile("camel-[a-z0-9]+(?:-[a-z0-9]+)*");
+    private static final Pattern CVE_ID = 
Pattern.compile("CVE-(\\d{4})-(\\d+)");
+
+    /**
+     * The output directory for the generated catalog advisories file
+     */
+    @Parameter(defaultValue = 
"${project.basedir}/src/generated/resources/org/apache/camel/catalog/advisories")
+    protected File outDir;
+
+    @Inject
+    public UpdateSecurityAdvisoriesMojo(MavenProjectHelper projectHelper, 
BuildContext buildContext) {
+        super(projectHelper, buildContext);
+    }
+
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+        if (outDir == null) {
+            outDir = new File(project.getBasedir(), "src/generated/resources");
+        }
+
+        try {
+            getLog().info("Updating Camel security advisories from 
camel-website");
+            List<String> links = fetchAdvisoryLinks();
+            List<SecurityAdvisoryModel> advisories = processAdvisories(links);
+            
advisories.sort(Comparator.comparingLong(UpdateSecurityAdvisoriesMojo::cveOrdinal));
+            getLog().info("Found " + advisories.size() + " published security 
advisories");
+
+            JsonArray arr = new JsonArray();
+            for (SecurityAdvisoryModel advisory : advisories) {
+                arr.add(JsonMapper.asJsonObject(advisory));
+            }
+            String json = Jsoner.serialize(arr);
+            json = Jsoner.prettyPrint(json, 4);
+
+            Path path = outDir.toPath();
+            updateResource(path, "camel-security-advisories.json", json);
+            addResourceDirectory(path);
+        } catch (Exception e) {
+            throw new MojoExecutionException(e);
+        }
+    }
+
+    private List<SecurityAdvisoryModel> processAdvisories(List<String> urls) 
throws Exception {
+        List<SecurityAdvisoryModel> answer = new ArrayList<>();
+
+        try (CloseableHttpClient hc = new CloseableHttpClient()) {
+            for (String url : urls) {
+                HttpResponse<String> res
+                        = hc.send(HttpRequest.newBuilder(new 
URI(url)).timeout(Duration.ofSeconds(20)).build(),
+                                HttpResponse.BodyHandlers.ofString());
+
+                if (res.statusCode() == 200) {
+                    SecurityAdvisoryModel model = parseAdvisory(res.body());
+                    if (model != null) {
+                        answer.add(model);
+                    }
+                }
+            }
+        }
+
+        return answer;
+    }
+
+    /**
+     * Parse one advisory Markdown file (YAML front matter). Returns {@code 
null} for drafts, non-advisory pages and
+     * files without parseable front matter, so only published advisories are 
included.
+     */
+    static SecurityAdvisoryModel parseAdvisory(String content) {
+        Map<String, Object> frontMatter = frontMatter(content);
+        if (frontMatter == null) {
+            return null;
+        }
+        if (!"security-advisory".equals(str(frontMatter.get("type")))) {
+            return null;
+        }
+        Object draft = frontMatter.get("draft");
+        if (Boolean.TRUE.equals(draft) || "true".equalsIgnoreCase(str(draft))) 
{
+            return null;
+        }
+        String cve = str(frontMatter.get("cve"));
+        if (cve == null || cve.isBlank()) {
+            return null;
+        }
+
+        SecurityAdvisoryModel model = new SecurityAdvisoryModel();
+        model.setCve(cve.trim());
+        model.setDate(trimmed(frontMatter.get("date")));
+        String severity = str(frontMatter.get("severity"));
+        if (severity != null) {
+            model.setSeverity(severity.trim().toUpperCase(Locale.ROOT));
+        }
+        model.setSummary(trimmed(frontMatter.get("summary")));
+        model.setAffected(trimmed(frontMatter.get("affected")));
+        model.setFixed(trimmed(frontMatter.get("fixed")));
+        model.setMitigation(trimmed(frontMatter.get("mitigation")));
+
+        String url = str(frontMatter.get("url"));
+        if (url == null || url.isBlank()) {
+            url = "/security/" + model.getCve() + ".html";
+        }
+        if (url.startsWith("/")) {
+            url = WEBSITE_BASE_URL + url;
+        }
+        model.setUrl(url.trim());
+
+        model.setComponents(extractComponents(str(frontMatter.get("title")), 
model.getSummary(),
+                str(frontMatter.get("description")), model.getMitigation()));
+        return model;
+    }
+
+    @SuppressWarnings("unchecked")
+    static Map<String, Object> frontMatter(String content) {
+        if (content == null) {
+            return null;
+        }
+        String trimmedContent = content.stripLeading();
+        if (!trimmedContent.startsWith("---")) {
+            return null;
+        }
+        int start = trimmedContent.indexOf('\n');
+        if (start < 0) {
+            return null;
+        }
+        int end = trimmedContent.indexOf("\n---", start);
+        if (end < 0) {
+            return null;
+        }
+        String yaml = trimmedContent.substring(start + 1, end);
+        try {
+            Object parsed = new 
Load(LoadSettings.builder().build()).loadFromString(yaml);
+            return parsed instanceof Map ? (Map<String, Object>) parsed : null;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * The Camel components named by the advisory text, as {@code camel-*} 
tokens (best-effort: some older advisories do
+     * not name components at all).
+     */
+    static List<String> extractComponents(String... texts) {
+        TreeSet<String> found = new TreeSet<>();

Review Comment:
   The `COMPONENT_TOKEN` regex `camel-[a-z0-9]+(?:-[a-z0-9]+)*` matches any 
`camel-*` token in the advisory text, producing **9 distinct spurious component 
entries across 32 occurrences in 25 CVE entries**:
   
   - **JIRA ticket numbers:** `camel-10894`, `camel-12444`, `camel-23200`, 
`camel-23212`, `camel-23372`, `camel-23506` — from prose like "CAMEL-23212 fix" 
lowercased to match.
   - **English words from prose:** `camel-internal` (10 CVEs), `camel-prefixed` 
(13 CVEs), `camel-specific`, `camel-namespace`, `camel-side` — from phrases 
like "camel-internal headers", "non-Camel-prefixed names".
   
   These pollute filtering — e.g. 
`camel_security_advisories(component="internal")` returns 10 false matches, and 
`matchAdvisories()` in HardenTools would try to match route components against 
`camel-internal`.
   
   Suggestion: post-filter extracted tokens against a known-component list from 
the catalog, or at minimum add a blocklist for common false positives 
(`camel-internal`, `camel-prefixed`, `camel-specific`, `camel-side`, 
`camel-namespace`, and tokens matching `camel-\d+`).



##########
catalog/camel-catalog/src/main/java/org/apache/camel/catalog/CamelCatalog.java:
##########
@@ -663,6 +664,13 @@ default BaseModel<? extends BaseOptionModel> model(Kind 
kind, String name) {
      */
     List<ReleaseModel> camelQuarkusReleases();
 
+    /**
+     * Load all published Camel CVE security advisories from catalog (the data 
behind
+     * <a 
href="https://camel.apache.org/security/";>camel.apache.org/security</a>, synced 
into the catalog when it was
+     * built).
+     */
+    List<SecurityAdvisoryModel> camelSecurityAdvisories();

Review Comment:
   Minor: per project conventions, new public methods on API interfaces should 
include a `@since` Javadoc tag. Consider adding `@since 4.22` to the method 
Javadoc.



-- 
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