gnodet commented on code in PR #25026:
URL: https://github.com/apache/camel/pull/25026#discussion_r3630619660


##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/SecurityScanTools.java:
##########
@@ -0,0 +1,323 @@
+/*
+ * 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.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+import io.quarkiverse.mcp.server.Tool;
+import io.quarkiverse.mcp.server.ToolArg;
+import io.quarkiverse.mcp.server.ToolCallException;
+import org.apache.camel.util.SecurityUtils;
+
+/**
+ * MCP Tool for static security analysis of Camel route definitions.
+ * <p>
+ * Scans route content (YAML, XML, or Java DSL) for security anti-patterns 
defined in the Camel security model
+ * ({@code design/security.adoc}). Produces actionable findings with severity, 
line location, and remediation guidance.
+ * <p>
+ * Distinct from {@link HardenTools} which provides general security context 
and CVE advisories — this tool performs
+ * line-by-line static analysis to find specific violations.
+ */
+@ApplicationScoped
+public class SecurityScanTools {
+
+    private static final Pattern SECRET_IN_URI = Pattern.compile(
+            
"(?i)(password|passwd|pwd|secret|token|apikey|api[_-]?key|access[_-]?key)\\s*[=:]\\s*(?!\\{\\{)(?!\\$\\{)(?!RAW\\()([^\\s,;}'\"\\]&]+)");
+
+    private static final Pattern CONNECTION_STRING_CREDS = Pattern.compile(
+            "(?i)(mongodb(\\+srv)?://|amqp://|redis://|jdbc:)\\S+:\\S+@");
+
+    private static final String[] CONSUMER_COMPONENTS = {
+            "platform-http", "netty-http", "jetty", "undertow", "servlet",
+            "cxf", "cxfrs", "rest", "coap", "grpc"
+    };
+
+    @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = false),
+          description = "Scan a Camel route for security anti-patterns. "
+                        + "Performs static analysis to detect: exposed secrets 
in URIs, "
+                        + "insecure configuration options 
(trustAllCertificates, allowJavaSerializedObject, etc.), "
+                        + "missing Camel* header filters on consumers, 
unencrypted protocols, "
+                        + "and other violations from the Camel security model. 
"
+                        + "Returns findings with severity, line number, and 
remediation guidance. "
+                        + "Distinct from camel_route_harden_context which 
provides general security context "
+                        + "and CVE advisories.")
+    public SecurityScanResult camel_security_scan(
+            @ToolArg(description = "The Camel route content (YAML, XML, or 
Java DSL)") String route,
+            @ToolArg(description = "Route format: yaml, xml, or java (default: 
yaml)") String format) {
+
+        if (route == null || route.isBlank()) {
+            throw new ToolCallException("Route content is required", null);
+        }
+
+        try {
+            String resolvedFormat = format != null && !format.isBlank() ? 
format.toLowerCase(Locale.ROOT) : "yaml";
+            String[] lines = route.split("\n", -1);
+
+            List<Finding> findings = new ArrayList<>();
+
+            scanInsecureOptions(lines, findings);
+            scanSecretsInUris(lines, findings);
+            scanConnectionStringCredentials(lines, findings);
+            scanUnencryptedProtocols(lines, findings);
+            scanMissingHeaderFilters(route, lines, findings);
+            scanExecComponent(lines, findings);
+            scanSqlInjection(lines, findings);
+            scanFilePathTraversal(lines, findings);
+
+            findings.sort((a, b) -> severityRank(a.severity()) - 
severityRank(b.severity()));
+
+            int critical = (int) findings.stream().filter(f -> 
"critical".equals(f.severity())).count();
+            int high = (int) findings.stream().filter(f -> 
"high".equals(f.severity())).count();
+            int medium = (int) findings.stream().filter(f -> 
"medium".equals(f.severity())).count();
+            int low = (int) findings.stream().filter(f -> 
"low".equals(f.severity())).count();
+
+            return new SecurityScanResult(
+                    resolvedFormat, findings, findings.size(),
+                    new SeverityCounts(critical, high, medium, low));
+        } catch (ToolCallException e) {
+            throw e;
+        } catch (Throwable e) {
+            throw new ToolCallException(
+                    "Failed to scan route (" + e.getClass().getName() + "): " 
+ e.getMessage(), null);
+        }
+    }
+
+    private void scanInsecureOptions(String[] lines, List<Finding> findings) {
+        Map<String, SecurityUtils.SecurityOption> securityOptions = 
SecurityUtils.getSecurityOptions();

Review Comment:
   **Correctness bug: empty insecureValue causes false positives** — Three 
options (`sslendpointalgorithm`, `stricthostkeychecking`, 
`x509hostnameverifier`) have `insecureValue=""`. The check 
`lower.contains(optionKey + "=" + insecureValue)` becomes 
`lower.contains("sslendpointalgorithm=")` which matches ANY assignment — 
including secure ones like `sslEndpointAlgorithm=HTTPS`.
   
   Note that `SecurityUtils.isInsecureValue` correctly uses `equalsIgnoreCase` 
which only matches when the value is actually empty. Add a guard:
   
   ```java
   if (insecureValue.isEmpty()) {
       continue;
   }
   ```



##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/SecurityScanTools.java:
##########
@@ -0,0 +1,323 @@
+/*
+ * 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.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+import io.quarkiverse.mcp.server.Tool;
+import io.quarkiverse.mcp.server.ToolArg;
+import io.quarkiverse.mcp.server.ToolCallException;
+import org.apache.camel.util.SecurityUtils;
+
+/**
+ * MCP Tool for static security analysis of Camel route definitions.
+ * <p>
+ * Scans route content (YAML, XML, or Java DSL) for security anti-patterns 
defined in the Camel security model
+ * ({@code design/security.adoc}). Produces actionable findings with severity, 
line location, and remediation guidance.
+ * <p>
+ * Distinct from {@link HardenTools} which provides general security context 
and CVE advisories — this tool performs
+ * line-by-line static analysis to find specific violations.
+ */
+@ApplicationScoped
+public class SecurityScanTools {
+
+    private static final Pattern SECRET_IN_URI = Pattern.compile(
+            
"(?i)(password|passwd|pwd|secret|token|apikey|api[_-]?key|access[_-]?key)\\s*[=:]\\s*(?!\\{\\{)(?!\\$\\{)(?!RAW\\()([^\\s,;}'\"\\]&]+)");
+

Review Comment:
   **Correctness bug: RAW() negative lookahead creates false negatives** — The 
`(?!RAW\\()` in `SECRET_IN_URI` prevents matching `password=RAW(mysecret123)`. 
However, `SecurityUtils.isPlainTextSecret` (line 146-147) explicitly documents:
   
   > _"RAW() is a URI encoding wrapper, not a security mechanism -- 
RAW(password) is still plain text."_
   
   This means `password=RAW(mysecret123)` is silently skipped by the scanner, 
creating a false negative for actual plain-text secrets. Remove the 
`(?!RAW\\()` negative lookahead.



##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/SecurityScanTools.java:
##########
@@ -0,0 +1,323 @@
+/*
+ * 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.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+import io.quarkiverse.mcp.server.Tool;
+import io.quarkiverse.mcp.server.ToolArg;
+import io.quarkiverse.mcp.server.ToolCallException;
+import org.apache.camel.util.SecurityUtils;
+
+/**
+ * MCP Tool for static security analysis of Camel route definitions.
+ * <p>
+ * Scans route content (YAML, XML, or Java DSL) for security anti-patterns 
defined in the Camel security model
+ * ({@code design/security.adoc}). Produces actionable findings with severity, 
line location, and remediation guidance.
+ * <p>
+ * Distinct from {@link HardenTools} which provides general security context 
and CVE advisories — this tool performs
+ * line-by-line static analysis to find specific violations.
+ */
+@ApplicationScoped
+public class SecurityScanTools {
+
+    private static final Pattern SECRET_IN_URI = Pattern.compile(
+            
"(?i)(password|passwd|pwd|secret|token|apikey|api[_-]?key|access[_-]?key)\\s*[=:]\\s*(?!\\{\\{)(?!\\$\\{)(?!RAW\\()([^\\s,;}'\"\\]&]+)");
+
+    private static final Pattern CONNECTION_STRING_CREDS = Pattern.compile(
+            "(?i)(mongodb(\\+srv)?://|amqp://|redis://|jdbc:)\\S+:\\S+@");
+
+    private static final String[] CONSUMER_COMPONENTS = {
+            "platform-http", "netty-http", "jetty", "undertow", "servlet",
+            "cxf", "cxfrs", "rest", "coap", "grpc"
+    };
+
+    @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = false),
+          description = "Scan a Camel route for security anti-patterns. "
+                        + "Performs static analysis to detect: exposed secrets 
in URIs, "
+                        + "insecure configuration options 
(trustAllCertificates, allowJavaSerializedObject, etc.), "
+                        + "missing Camel* header filters on consumers, 
unencrypted protocols, "
+                        + "and other violations from the Camel security model. 
"
+                        + "Returns findings with severity, line number, and 
remediation guidance. "
+                        + "Distinct from camel_route_harden_context which 
provides general security context "
+                        + "and CVE advisories.")
+    public SecurityScanResult camel_security_scan(
+            @ToolArg(description = "The Camel route content (YAML, XML, or 
Java DSL)") String route,
+            @ToolArg(description = "Route format: yaml, xml, or java (default: 
yaml)") String format) {
+
+        if (route == null || route.isBlank()) {
+            throw new ToolCallException("Route content is required", null);
+        }
+
+        try {
+            String resolvedFormat = format != null && !format.isBlank() ? 
format.toLowerCase(Locale.ROOT) : "yaml";
+            String[] lines = route.split("\n", -1);
+
+            List<Finding> findings = new ArrayList<>();
+
+            scanInsecureOptions(lines, findings);
+            scanSecretsInUris(lines, findings);
+            scanConnectionStringCredentials(lines, findings);
+            scanUnencryptedProtocols(lines, findings);
+            scanMissingHeaderFilters(route, lines, findings);
+            scanExecComponent(lines, findings);
+            scanSqlInjection(lines, findings);
+            scanFilePathTraversal(lines, findings);
+
+            findings.sort((a, b) -> severityRank(a.severity()) - 
severityRank(b.severity()));
+
+            int critical = (int) findings.stream().filter(f -> 
"critical".equals(f.severity())).count();
+            int high = (int) findings.stream().filter(f -> 
"high".equals(f.severity())).count();
+            int medium = (int) findings.stream().filter(f -> 
"medium".equals(f.severity())).count();
+            int low = (int) findings.stream().filter(f -> 
"low".equals(f.severity())).count();
+
+            return new SecurityScanResult(
+                    resolvedFormat, findings, findings.size(),
+                    new SeverityCounts(critical, high, medium, low));
+        } catch (ToolCallException e) {
+            throw e;
+        } catch (Throwable e) {
+            throw new ToolCallException(
+                    "Failed to scan route (" + e.getClass().getName() + "): " 
+ e.getMessage(), null);
+        }
+    }
+
+    private void scanInsecureOptions(String[] lines, List<Finding> findings) {
+        Map<String, SecurityUtils.SecurityOption> securityOptions = 
SecurityUtils.getSecurityOptions();
+
+        for (int i = 0; i < lines.length; i++) {
+            String lower = 
lines[i].toLowerCase(Locale.ROOT).replaceAll("[\\s-]", "");
+            for (Map.Entry<String, SecurityUtils.SecurityOption> entry : 
securityOptions.entrySet()) {
+                String optionKey = entry.getKey();
+                String insecureValue = entry.getValue().insecureValue();
+                String category = entry.getValue().category();
+
+                if (lower.contains(optionKey + "=" + insecureValue)
+                        || lower.contains(optionKey + ":" + insecureValue)
+                        || lower.contains(optionKey + "\":" + insecureValue)
+                        || lower.contains("\"" + optionKey + "\":" + 
insecureValue)) {
+                    findings.add(new Finding(
+                            severityForCategory(category),
+                            category,
+                            "Insecure option: " + optionKey + "=" + 
insecureValue,
+                            i + 1,
+                            remediationForCategory(category, optionKey)));
+                }
+            }
+        }
+    }
+
+    private void scanSecretsInUris(String[] lines, List<Finding> findings) {
+        for (int i = 0; i < lines.length; i++) {
+            Matcher matcher = SECRET_IN_URI.matcher(lines[i]);
+            while (matcher.find()) {
+                String key = matcher.group(1);
+                findings.add(new Finding(
+                        "critical",
+                        "secret",
+                        "Potential plain-text secret in URI: " + key,
+                        i + 1,
+                        "Use property placeholders {{" + key + "}} or a 
secrets management vault "
+                               + "(HashiCorp Vault, AWS Secrets Manager, Azure 
Key Vault)"));
+            }
+        }
+    }
+
+    private void scanConnectionStringCredentials(String[] lines, List<Finding> 
findings) {
+        for (int i = 0; i < lines.length; i++) {
+            if (CONNECTION_STRING_CREDS.matcher(lines[i]).find()) {
+                findings.add(new Finding(
+                        "critical",
+                        "secret",
+                        "Connection string with embedded credentials",
+                        i + 1,
+                        "Extract credentials from the connection string and 
use property placeholders or vault services"));
+            }
+        }
+    }
+
+    private void scanUnencryptedProtocols(String[] lines, List<Finding> 
findings) {
+        for (int i = 0; i < lines.length; i++) {
+            String lower = lines[i].toLowerCase(Locale.ROOT);
+
+            if (containsScheme(lower, "http") && !containsScheme(lower, 
"https")
+                    && !lower.contains("platform-http")) {
+                findings.add(new Finding(
+                        "high",
+                        "insecure:ssl",
+                        "Using HTTP instead of HTTPS",
+                        i + 1,
+                        "Use HTTPS with TLS 1.2+ for secure communication"));
+            }
+            if (containsScheme(lower, "ftp") && !containsScheme(lower, "sftp") 
&& !containsScheme(lower, "ftps")) {
+                findings.add(new Finding(
+                        "high",
+                        "insecure:ssl",
+                        "Using plain FTP instead of SFTP/FTPS",
+                        i + 1,
+                        "Use SFTP or FTPS for encrypted file transfers"));
+            }
+            if (containsScheme(lower, "ldap") && !containsScheme(lower, 
"ldaps")) {
+                findings.add(new Finding(
+                        "medium",

Review Comment:
   **Route-level header filter check** — `scanMissingHeaderFilters` checks the 
ENTIRE route content for `removeheaders`/`headerfilter`/`camel*`. This means: 
(a) if a route has two HTTP consumers and only one has a header filter, neither 
gets flagged; (b) any occurrence of `"camel*"` anywhere in the route (even in a 
comment or description) suppresses all header-injection warnings globally.



##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/SecurityScanTools.java:
##########
@@ -0,0 +1,323 @@
+/*
+ * 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.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+import io.quarkiverse.mcp.server.Tool;
+import io.quarkiverse.mcp.server.ToolArg;
+import io.quarkiverse.mcp.server.ToolCallException;
+import org.apache.camel.util.SecurityUtils;
+
+/**
+ * MCP Tool for static security analysis of Camel route definitions.
+ * <p>
+ * Scans route content (YAML, XML, or Java DSL) for security anti-patterns 
defined in the Camel security model
+ * ({@code design/security.adoc}). Produces actionable findings with severity, 
line location, and remediation guidance.
+ * <p>
+ * Distinct from {@link HardenTools} which provides general security context 
and CVE advisories — this tool performs
+ * line-by-line static analysis to find specific violations.
+ */
+@ApplicationScoped
+public class SecurityScanTools {
+
+    private static final Pattern SECRET_IN_URI = Pattern.compile(
+            
"(?i)(password|passwd|pwd|secret|token|apikey|api[_-]?key|access[_-]?key)\\s*[=:]\\s*(?!\\{\\{)(?!\\$\\{)(?!RAW\\()([^\\s,;}'\"\\]&]+)");
+
+    private static final Pattern CONNECTION_STRING_CREDS = Pattern.compile(
+            "(?i)(mongodb(\\+srv)?://|amqp://|redis://|jdbc:)\\S+:\\S+@");
+
+    private static final String[] CONSUMER_COMPONENTS = {
+            "platform-http", "netty-http", "jetty", "undertow", "servlet",
+            "cxf", "cxfrs", "rest", "coap", "grpc"
+    };
+
+    @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = false),
+          description = "Scan a Camel route for security anti-patterns. "
+                        + "Performs static analysis to detect: exposed secrets 
in URIs, "
+                        + "insecure configuration options 
(trustAllCertificates, allowJavaSerializedObject, etc.), "
+                        + "missing Camel* header filters on consumers, 
unencrypted protocols, "
+                        + "and other violations from the Camel security model. 
"
+                        + "Returns findings with severity, line number, and 
remediation guidance. "
+                        + "Distinct from camel_route_harden_context which 
provides general security context "
+                        + "and CVE advisories.")
+    public SecurityScanResult camel_security_scan(
+            @ToolArg(description = "The Camel route content (YAML, XML, or 
Java DSL)") String route,
+            @ToolArg(description = "Route format: yaml, xml, or java (default: 
yaml)") String format) {
+
+        if (route == null || route.isBlank()) {
+            throw new ToolCallException("Route content is required", null);
+        }
+
+        try {
+            String resolvedFormat = format != null && !format.isBlank() ? 
format.toLowerCase(Locale.ROOT) : "yaml";
+            String[] lines = route.split("\n", -1);
+
+            List<Finding> findings = new ArrayList<>();
+
+            scanInsecureOptions(lines, findings);
+            scanSecretsInUris(lines, findings);
+            scanConnectionStringCredentials(lines, findings);
+            scanUnencryptedProtocols(lines, findings);
+            scanMissingHeaderFilters(route, lines, findings);
+            scanExecComponent(lines, findings);
+            scanSqlInjection(lines, findings);
+            scanFilePathTraversal(lines, findings);
+
+            findings.sort((a, b) -> severityRank(a.severity()) - 
severityRank(b.severity()));
+
+            int critical = (int) findings.stream().filter(f -> 
"critical".equals(f.severity())).count();
+            int high = (int) findings.stream().filter(f -> 
"high".equals(f.severity())).count();
+            int medium = (int) findings.stream().filter(f -> 
"medium".equals(f.severity())).count();
+            int low = (int) findings.stream().filter(f -> 
"low".equals(f.severity())).count();
+
+            return new SecurityScanResult(
+                    resolvedFormat, findings, findings.size(),
+                    new SeverityCounts(critical, high, medium, low));
+        } catch (ToolCallException e) {
+            throw e;
+        } catch (Throwable e) {
+            throw new ToolCallException(
+                    "Failed to scan route (" + e.getClass().getName() + "): " 
+ e.getMessage(), null);
+        }
+    }
+
+    private void scanInsecureOptions(String[] lines, List<Finding> findings) {
+        Map<String, SecurityUtils.SecurityOption> securityOptions = 
SecurityUtils.getSecurityOptions();
+
+        for (int i = 0; i < lines.length; i++) {
+            String lower = 
lines[i].toLowerCase(Locale.ROOT).replaceAll("[\\s-]", "");
+            for (Map.Entry<String, SecurityUtils.SecurityOption> entry : 
securityOptions.entrySet()) {
+                String optionKey = entry.getKey();
+                String insecureValue = entry.getValue().insecureValue();
+                String category = entry.getValue().category();
+
+                if (lower.contains(optionKey + "=" + insecureValue)
+                        || lower.contains(optionKey + ":" + insecureValue)
+                        || lower.contains(optionKey + "\":" + insecureValue)
+                        || lower.contains("\"" + optionKey + "\":" + 
insecureValue)) {
+                    findings.add(new Finding(
+                            severityForCategory(category),
+                            category,
+                            "Insecure option: " + optionKey + "=" + 
insecureValue,
+                            i + 1,
+                            remediationForCategory(category, optionKey)));
+                }
+            }
+        }
+    }
+
+    private void scanSecretsInUris(String[] lines, List<Finding> findings) {
+        for (int i = 0; i < lines.length; i++) {
+            Matcher matcher = SECRET_IN_URI.matcher(lines[i]);
+            while (matcher.find()) {
+                String key = matcher.group(1);
+                findings.add(new Finding(
+                        "critical",
+                        "secret",
+                        "Potential plain-text secret in URI: " + key,
+                        i + 1,
+                        "Use property placeholders {{" + key + "}} or a 
secrets management vault "
+                               + "(HashiCorp Vault, AWS Secrets Manager, Azure 
Key Vault)"));
+            }
+        }
+    }
+
+    private void scanConnectionStringCredentials(String[] lines, List<Finding> 
findings) {
+        for (int i = 0; i < lines.length; i++) {
+            if (CONNECTION_STRING_CREDS.matcher(lines[i]).find()) {
+                findings.add(new Finding(
+                        "critical",
+                        "secret",
+                        "Connection string with embedded credentials",
+                        i + 1,
+                        "Extract credentials from the connection string and 
use property placeholders or vault services"));
+            }
+        }
+    }
+
+    private void scanUnencryptedProtocols(String[] lines, List<Finding> 
findings) {
+        for (int i = 0; i < lines.length; i++) {
+            String lower = lines[i].toLowerCase(Locale.ROOT);
+
+            if (containsScheme(lower, "http") && !containsScheme(lower, 
"https")
+                    && !lower.contains("platform-http")) {
+                findings.add(new Finding(
+                        "high",
+                        "insecure:ssl",
+                        "Using HTTP instead of HTTPS",
+                        i + 1,
+                        "Use HTTPS with TLS 1.2+ for secure communication"));
+            }
+            if (containsScheme(lower, "ftp") && !containsScheme(lower, "sftp") 
&& !containsScheme(lower, "ftps")) {
+                findings.add(new Finding(
+                        "high",
+                        "insecure:ssl",
+                        "Using plain FTP instead of SFTP/FTPS",
+                        i + 1,
+                        "Use SFTP or FTPS for encrypted file transfers"));
+            }
+            if (containsScheme(lower, "ldap") && !containsScheme(lower, 
"ldaps")) {
+                findings.add(new Finding(
+                        "medium",
+                        "insecure:ssl",
+                        "Using LDAP instead of LDAPS",
+                        i + 1,
+                        "Use LDAPS for encrypted LDAP communication"));
+            }
+            if (containsScheme(lower, "smtp") && !containsScheme(lower, 
"smtps")) {
+                findings.add(new Finding(
+                        "medium",
+                        "insecure:ssl",
+                        "Using SMTP instead of SMTPS",
+                        i + 1,
+                        "Use SMTPS for encrypted email communication"));
+            }
+        }
+    }
+
+    private void scanMissingHeaderFilters(String route, String[] lines, 
List<Finding> findings) {
+        String lower = route.toLowerCase(Locale.ROOT);
+        for (String consumer : CONSUMER_COMPONENTS) {
+            if (containsScheme(lower, consumer)) {
+                if (!lower.contains("removeheaders") && 
!lower.contains("headerfilter")
+                        && !lower.contains("camel*")) {
+                    int lineNum = findLineContaining(lines, consumer + ":");
+                    findings.add(new Finding(
+                            "high",
+                            "header-injection",
+                            "Consumer '" + consumer + "' without Camel* header 
filter",
+                            lineNum,

Review Comment:
   **False positive: `google-bigquery-sql:` URIs** — `containsScheme(lower, 
"sql")` checks for `sql:` as a substring, which matches the composite scheme 
`google-bigquery-sql:project:dataset.table`. BigQuery SQL uses a different 
query model and does not use `:#param` syntax. The same issue applies to other 
composite schemes ending with a target scheme name.



##########
dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/SecurityScanTools.java:
##########
@@ -0,0 +1,323 @@
+/*
+ * 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.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+import io.quarkiverse.mcp.server.Tool;
+import io.quarkiverse.mcp.server.ToolArg;
+import io.quarkiverse.mcp.server.ToolCallException;
+import org.apache.camel.util.SecurityUtils;
+
+/**
+ * MCP Tool for static security analysis of Camel route definitions.

Review Comment:
   **Code duplication with `HardenTools.analyzeSecurityConcerns()`** — The 
HTTP-vs-HTTPS, FTP, exec, SQL injection, file path traversal, and LDAP/SMTP 
checks are all reimplemented here. These tools serve different purposes 
(catalog-aware context vs. line-level scanning) but share substantial 
string-matching logic that will diverge as one file is updated but not the 
other. Consider extracting shared detection rules into a common utility class.



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