This is an automated email from the ASF dual-hosted git repository.
acosentino 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 f55c086a7197 CAMEL-22960 - Camel-Jbang: Add an harden command and the
related tool to camel-jbang-mcp (#21267)
f55c086a7197 is described below
commit f55c086a7197327c8bb7e6d804d45deb30534adc
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Feb 5 15:03:34 2026 +0100
CAMEL-22960 - Camel-Jbang: Add an harden command and the related tool to
camel-jbang-mcp (#21267)
Signed-off-by: Andrea Cosentino <[email protected]>
---
.../pages/jbang-commands/camel-jbang-commands.adoc | 1 +
.../pages/jbang-commands/camel-jbang-harden.adoc | 40 ++
.../partials/jbang-commands/examples/harden.adoc | 161 +++++
.../META-INF/camel-jbang-commands-metadata.json | 1 +
.../dsl/jbang/core/commands/CamelJBangMain.java | 1 +
.../camel/dsl/jbang/core/commands/Harden.java | 679 +++++++++++++++++++++
.../dsl/jbang/core/commands/mcp/HardenTools.java | 452 ++++++++++++++
.../src/main/resources/application.properties | 3 +
8 files changed, 1338 insertions(+)
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc
index d519eb557fe6..179bbddbfaa8 100644
---
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc
+++
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc
@@ -23,6 +23,7 @@ TIP: You can also use `camel --help` or `camel <command>
--help` to see availabl
| xref:jbang-commands/camel-jbang-explain.adoc[camel explain] | Explain what a
Camel route does using AI/LLM
| xref:jbang-commands/camel-jbang-export.adoc[camel export] | Export to other
runtimes (Camel Main, Spring Boot, or Quarkus)
| xref:jbang-commands/camel-jbang-get.adoc[camel get] | Get status of Camel
integrations
+| xref:jbang-commands/camel-jbang-harden.adoc[camel harden] | Suggest security
hardening for Camel routes using AI/LLM
| xref:jbang-commands/camel-jbang-hawtio.adoc[camel hawtio] | Launch Hawtio
web console
| xref:jbang-commands/camel-jbang-infra.adoc[camel infra] | List and Run
external services for testing and prototyping
| xref:jbang-commands/camel-jbang-init.adoc[camel init] | Creates a new Camel
integration
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-harden.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-harden.adoc
new file mode 100644
index 000000000000..f1d0e9f1b7ca
--- /dev/null
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-harden.adoc
@@ -0,0 +1,40 @@
+
+// AUTO-GENERATED by camel-package-maven-plugin - DO NOT EDIT THIS FILE
+= camel harden
+
+Suggest security hardening for Camel routes using AI/LLM
+
+
+== Usage
+
+[source,bash]
+----
+camel harden [options]
+----
+
+
+
+== Options
+
+[cols="2,5,1,2",options="header"]
+|===
+| Option | Description | Default | Type
+| `--api-key` | API key for authentication. Also reads OPENAI_API_KEY or
LLM_API_KEY env vars | | String
+| `--api-type` | API type: 'ollama' or 'openai' (OpenAI-compatible) | ollama |
ApiType
+| `--catalog-context` | Include Camel Catalog descriptions in the prompt | |
boolean
+| `--format` | Output format: text, markdown | text | String
+| `--model` | Model to use | DEFAULT_MODEL | String
+| `--show-prompt` | Show the prompt sent to the LLM | | boolean
+| `--stream` | Stream the response as it's generated (shows progress) | true |
boolean
+| `--system-prompt` | Custom system prompt | | String
+| `--temperature` | Temperature for response generation (0.0-2.0) | 0.7 |
double
+| `--timeout` | Timeout in seconds for LLM response | 120 | int
+| `--url` | LLM API endpoint URL. Auto-detected from 'camel infra' for Ollama
if not specified. | | String
+| `--verbose,-v` | Include detailed security recommendations with code
examples | | boolean
+| `-h,--help` | Display the help and sub-commands | | boolean
+|===
+
+
+
+include::partial$jbang-commands/examples/harden.adoc[]
+
diff --git
a/docs/user-manual/modules/ROOT/partials/jbang-commands/examples/harden.adoc
b/docs/user-manual/modules/ROOT/partials/jbang-commands/examples/harden.adoc
new file mode 100644
index 000000000000..266cc2f1a3a7
--- /dev/null
+++ b/docs/user-manual/modules/ROOT/partials/jbang-commands/examples/harden.adoc
@@ -0,0 +1,161 @@
+== Examples
+
+The `camel harden` command uses AI/LLM to analyze Camel routes and provide
security hardening recommendations.
+It supports multiple LLM providers including Ollama (local), OpenAI, Azure
OpenAI, vLLM, LM Studio, and LocalAI.
+
+=== Prerequisites
+
+Start Ollama locally using Camel infra:
+
+[source,bash]
+----
+camel infra run ollama
+----
+
+=== Basic Usage
+
+Analyze a YAML route for security issues:
+
+[source,bash]
+----
+camel harden my-route.yaml
+----
+
+Analyze a Java route:
+
+[source,bash]
+----
+camel harden OrderRoute.java
+----
+
+Analyze multiple route files:
+
+[source,bash]
+----
+camel harden route1.yaml route2.xml MyRoute.java
+----
+
+=== Security Analysis Focus
+
+The harden command analyzes routes for these security concerns:
+
+* **Authentication & Authorization** - Missing or weak authentication,
credential exposure
+* **Encryption & Data Protection** - TLS/SSL configuration, data in transit
security
+* **Secrets Management** - Hardcoded credentials, vault integration
recommendations
+* **Input Validation & Injection Prevention** - SQL, command, and path
traversal vulnerabilities
+* **Secure Component Configuration** - Insecure defaults, missing security
headers
+* **Logging & Monitoring** - Sensitive data in logs, audit trail
recommendations
+
+=== Output Options
+
+Use verbose mode for detailed recommendations with code examples:
+
+[source,bash]
+----
+camel harden my-route.yaml --verbose
+----
+
+Output as Markdown for documentation:
+
+[source,bash]
+----
+camel harden my-route.yaml --format=markdown
+----
+
+=== Prompt Options
+
+Include Camel Catalog descriptions for component-specific security advice:
+
+[source,bash]
+----
+camel harden my-route.yaml --catalog-context
+----
+
+Show the prompt sent to the LLM (useful for debugging):
+
+[source,bash]
+----
+camel harden my-route.yaml --show-prompt
+----
+
+Use a custom system prompt:
+
+[source,bash]
+----
+camel harden my-route.yaml --system-prompt="Focus on OWASP Top 10
vulnerabilities."
+----
+
+=== LLM Configuration
+
+Use OpenAI or compatible services:
+
+[source,bash]
+----
+camel harden my-route.yaml --url=https://api.openai.com --api-type=openai
--api-key=sk-...
+----
+
+Use environment variables for the API key:
+
+[source,bash]
+----
+export OPENAI_API_KEY=sk-...
+camel harden my-route.yaml --url=https://api.openai.com --api-type=openai
+----
+
+Use a specific model:
+
+[source,bash]
+----
+camel harden my-route.yaml --model=llama3.1:70b
+----
+
+=== Advanced Options
+
+Disable streaming (wait for complete response):
+
+[source,bash]
+----
+camel harden my-route.yaml --stream=false
+----
+
+Adjust temperature (0.0 = deterministic, 2.0 = creative):
+
+[source,bash]
+----
+camel harden my-route.yaml --temperature=0.3
+----
+
+Set a custom timeout (in seconds):
+
+[source,bash]
+----
+camel harden my-route.yaml --timeout=300
+----
+
+=== Security Findings Severity Levels
+
+The harden command categorizes findings by severity:
+
+* **Critical** - Immediate security risks (command injection, hardcoded
credentials, disabled TLS)
+* **High** - Significant security concerns (HTTP instead of HTTPS, SQL
injection risk, plain FTP)
+* **Medium** - Moderate security issues (missing authentication hints, path
validation concerns)
+* **Low** - Minor security improvements (missing optional security headers)
+
+=== Example Workflow
+
+A typical security review workflow:
+
+[source,bash]
+----
+# 1. First, understand what the route does
+camel explain my-route.yaml
+
+# 2. Perform security analysis
+camel harden my-route.yaml
+
+# 3. Get detailed recommendations with code examples
+camel harden my-route.yaml --verbose --format=markdown
+
+# 4. Full analysis with catalog context
+camel harden my-route.yaml --catalog-context --verbose
+----
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index bc094d14443a..49a490218e55 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -12,6 +12,7 @@
{ "name": "explain", "fullName": "explain", "description": "Explain what a
Camel route does using AI\/LLM", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Explain", "options": [ { "names":
"--api-key", "description": "API key for authentication. Also reads
OPENAI_API_KEY or LLM_API_KEY env vars", "javaType": "java.lang.String",
"type": "string" }, { "names": "--api-type", "description": "API type: 'ollama'
or 'openai' (OpenAI-compatible)", "defaultValue": "ollama", "javaTyp [...]
{ "name": "export", "fullName": "export", "description": "Export to other
runtimes (Camel Main, Spring Boot, or Quarkus)", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Export", "options": [ { "names":
"--build-property", "description": "Maven\/Gradle build properties, ex.
--build-property=prop1=foo", "javaType": "java.util.List", "type": "array" }, {
"names": "--build-tool", "description": "DEPRECATED: Build tool to use (maven
or gradle) (gradle is deprecated)", "defaultV [...]
{ "name": "get", "fullName": "get", "description": "Get status of Camel
integrations", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.process.CamelStatus", "options": [ {
"names": "--watch", "description": "Execute periodically and showing output
fullscreen", "javaType": "boolean", "type": "boolean" }, { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "bean",
"fullName": "get [...]
+ { "name": "harden", "fullName": "harden", "description": "Suggest security
hardening for Camel routes using AI\/LLM", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Harden", "options": [ { "names":
"--api-key", "description": "API key for authentication. Also reads
OPENAI_API_KEY or LLM_API_KEY env vars", "javaType": "java.lang.String",
"type": "string" }, { "names": "--api-type", "description": "API type: 'ollama'
or 'openai' (OpenAI-compatible)", "defaultValue": "ollama", [...]
{ "name": "hawtio", "fullName": "hawtio", "description": "Launch Hawtio
web console", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.process.Hawtio", "options": [ {
"names": "--openUrl", "description": "To automatic open Hawtio web console in
the web browser", "defaultValue": "true", "javaType": "boolean", "type":
"boolean" }, { "names": "--port", "description": "Port number to use for Hawtio
web console (port 8888 by default)", "defaultValue": "8888", "javaType": "int",
"t [...]
{ "name": "infra", "fullName": "infra", "description": "List and Run
external services for testing and prototyping", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.infra.InfraCommand", "options": [ {
"names": "--json", "description": "Output in JSON Format", "javaType":
"boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display
the help and sub-commands", "javaType": "boolean", "type": "boolean" } ],
"subcommands": [ { "name": "get", "fullName": "infra [...]
{ "name": "init", "fullName": "init", "description": "Creates a new Camel
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Init",
"options": [ { "names": "--clean-dir,--clean-directory", "description":
"Whether to clean directory first (deletes all files in directory)",
"javaType": "boolean", "type": "boolean" }, { "names": "--dir,--directory",
"description": "Directory relative path where the new Camel integration will be
saved", "defaultValue": ".", "javaType" [...]
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
index 885d8d490dfd..c62c219295e8 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
@@ -127,6 +127,7 @@ public class CamelJBangMain implements Callable<Integer> {
.addSubcommand("dirty", new CommandLine(new Dirty(main)))
.addSubcommand("export", new CommandLine(new Export(main)))
.addSubcommand("explain", new CommandLine(new Explain(main)))
+ .addSubcommand("harden", new CommandLine(new Harden(main)))
.addSubcommand("get", new CommandLine(new CamelStatus(main))
.addSubcommand("bean", new CommandLine(new
CamelBeanDump(main)))
.addSubcommand("blocked", new CommandLine(new
ListBlocked(main)))
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Harden.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Harden.java
new file mode 100644
index 000000000000..244d03619b5b
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Harden.java
@@ -0,0 +1,679 @@
+/*
+ * 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;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.BiFunction;
+import java.util.stream.Stream;
+
+import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.catalog.DefaultCamelCatalog;
+import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.tooling.model.ComponentModel;
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Parameters;
+
+/**
+ * Command to suggest security hardening improvements for Camel routes using
AI/LLM services.
+ * <p>
+ * Analyzes routes for security vulnerabilities, authentication issues,
encryption gaps, secrets management, input
+ * validation, and other security best practices. Supports multiple LLM
providers: Ollama, OpenAI, Azure OpenAI, vLLM,
+ * LM Studio, LocalAI, etc.
+ */
+@Command(name = "harden",
+ description = "Suggest security hardening for Camel routes using
AI/LLM",
+ sortOptions = false, showDefaultValues = true)
+public class Harden extends CamelCommand {
+
+ private static final String DEFAULT_OLLAMA_URL = "http://localhost:11434";
+ private static final String DEFAULT_MODEL = "llama3.2";
+ private static final int CONNECT_TIMEOUT_SECONDS = 10;
+ private static final int HEALTH_CHECK_TIMEOUT_SECONDS = 5;
+
+ // Components with significant security considerations
+ private static final List<String> SECURITY_SENSITIVE_COMPONENTS =
Arrays.asList(
+ // Network/API components - need TLS, authentication
+ "http", "https", "netty-http", "vertx-http", "websocket",
+ "rest", "rest-api", "platform-http", "servlet", "undertow",
"jetty",
+ // Messaging - need authentication, encryption
+ "kafka", "jms", "activemq", "amqp", "rabbitmq", "pulsar",
+ "aws2-sqs", "aws2-sns", "aws2-kinesis",
+ "azure-servicebus", "azure-eventhubs",
+ "google-pubsub",
+ // File/Storage - need access control, path validation
+ "file", "ftp", "sftp", "ftps",
+ "aws2-s3", "azure-storage-blob", "azure-storage-queue",
"azure-files",
+ "google-storage", "minio",
+ // Database - need authentication, SQL injection prevention
+ "sql", "jdbc", "mongodb", "couchdb", "cassandraql",
+ "elasticsearch", "opensearch", "redis",
+ // Email - need authentication, TLS
+ "smtp", "smtps", "imap", "imaps", "pop3", "pop3s",
+ // Remote execution - high risk, need strict validation
+ "exec", "ssh", "docker",
+ // Directory services - need secure binding
+ "ldap", "ldaps",
+ // Secrets management
+ "hashicorp-vault", "aws2-secrets-manager", "azure-key-vault",
"google-secret-manager");
+
+ // Security-related categories
+ private static final List<String> OWASP_CATEGORIES = Arrays.asList(
+ "Injection (SQL, Command, LDAP, XPath)",
+ "Broken Authentication",
+ "Sensitive Data Exposure",
+ "XML External Entities (XXE)",
+ "Broken Access Control",
+ "Security Misconfiguration",
+ "Cross-Site Scripting (XSS)",
+ "Insecure Deserialization",
+ "Using Components with Known Vulnerabilities",
+ "Insufficient Logging & Monitoring");
+
+ enum ApiType {
+ ollama((harden, prompts) -> harden.callOllama(prompts[0], prompts[1],
prompts[2])),
+ openai((harden, prompts) -> harden.callOpenAiCompatible(prompts[0],
prompts[1], prompts[2], prompts[3]));
+
+ private final BiFunction<Harden, String[], String> caller;
+
+ ApiType(BiFunction<Harden, String[], String> caller) {
+ this.caller = caller;
+ }
+
+ String call(Harden harden, String endpoint, String sysPrompt, String
userPrompt, String apiKey) {
+ return caller.apply(harden, new String[] { endpoint, sysPrompt,
userPrompt, apiKey });
+ }
+ }
+
+ @Parameters(description = "Route file(s) to analyze for security
hardening", arity = "1..*")
+ List<String> files;
+
+ @Option(names = { "--url" },
+ description = "LLM API endpoint URL. Auto-detected from 'camel
infra' for Ollama if not specified.")
+ String url;
+
+ @Option(names = { "--api-type" },
+ description = "API type: 'ollama' or 'openai' (OpenAI-compatible)",
+ defaultValue = "ollama")
+ ApiType apiType = ApiType.ollama;
+
+ @Option(names = { "--api-key" },
+ description = "API key for authentication. Also reads
OPENAI_API_KEY or LLM_API_KEY env vars")
+ String apiKey;
+
+ @Option(names = { "--model" },
+ description = "Model to use",
+ defaultValue = DEFAULT_MODEL)
+ String model = DEFAULT_MODEL;
+
+ @Option(names = { "--verbose", "-v" },
+ description = "Include detailed security recommendations with code
examples")
+ boolean verbose;
+
+ @Option(names = { "--format" },
+ description = "Output format: text, markdown",
+ defaultValue = "text")
+ String format = "text";
+
+ @Option(names = { "--timeout" },
+ description = "Timeout in seconds for LLM response",
+ defaultValue = "120")
+ int timeout = 120;
+
+ @Option(names = { "--catalog-context" },
+ description = "Include Camel Catalog descriptions in the prompt")
+ boolean catalogContext;
+
+ @Option(names = { "--show-prompt" },
+ description = "Show the prompt sent to the LLM")
+ boolean showPrompt;
+
+ @Option(names = { "--temperature" },
+ description = "Temperature for response generation (0.0-2.0)",
+ defaultValue = "0.7")
+ double temperature = 0.7;
+
+ @Option(names = { "--system-prompt" },
+ description = "Custom system prompt")
+ String systemPrompt;
+
+ @Option(names = { "--stream" },
+ description = "Stream the response as it's generated (shows
progress)",
+ defaultValue = "true")
+ boolean stream = true;
+
+ private final HttpClient httpClient = HttpClient.newBuilder()
+ .connectTimeout(Duration.ofSeconds(CONNECT_TIMEOUT_SECONDS))
+ .build();
+
+ public Harden(CamelJBangMain main) {
+ super(main);
+ }
+
+ @Override
+ public Integer doCall() throws Exception {
+ String endpoint = detectEndpoint();
+ if (endpoint == null) {
+ printUsageHelp();
+ return 1;
+ }
+
+ String resolvedApiKey = resolveApiKey();
+ printConfiguration(endpoint, resolvedApiKey);
+
+ for (String file : files) {
+ int result = hardenRoute(file, endpoint, resolvedApiKey);
+ if (result != 0) {
+ return result;
+ }
+ }
+ return 0;
+ }
+
+ private void printConfiguration(String endpoint, String resolvedApiKey) {
+ printer().println("LLM Configuration:");
+ printer().println(" URL: " + endpoint);
+ printer().println(" API Type: " + apiType);
+ printer().println(" Model: " + model);
+ printMaskedApiKey(resolvedApiKey);
+ printer().println();
+ }
+
+ private void printMaskedApiKey(String key) {
+ if (key == null || key.isBlank()) {
+ return;
+ }
+ String masked = "****" + key.substring(Math.max(0, key.length() - 4));
+ printer().println(" API Key: " + masked);
+ }
+
+ private void printUsageHelp() {
+ printer().printErr("LLM service is not running or not reachable.");
+ printer().printErr("");
+ printer().printErr("Options:");
+ printer().printErr(" 1. camel infra run ollama");
+ printer().printErr(" 2. camel harden my-route.yaml
--url=http://localhost:11434");
+ printer().printErr(" 3. camel harden my-route.yaml
--url=https://api.openai.com --api-type=openai --api-key=sk-...");
+ }
+
+ private String detectEndpoint() {
+ return tryExplicitUrl()
+ .or(this::tryInfraOllama)
+ .or(this::tryDefaultOllama)
+ .orElse(null);
+ }
+
+ private Optional<String> tryExplicitUrl() {
+ if (url == null || url.isBlank()) {
+ return Optional.empty();
+ }
+ if (isEndpointReachable(url)) {
+ return Optional.of(url);
+ }
+ printer().printErr("Cannot connect to LLM service at: " + url);
+ return Optional.empty();
+ }
+
+ private Optional<String> tryInfraOllama() {
+ try {
+ Map<Long, Path> pids = findOllamaPids();
+ for (Path pidFile : pids.values()) {
+ String baseUrl = readBaseUrlFromPidFile(pidFile);
+ if (baseUrl != null && isEndpointReachable(baseUrl)) {
+ apiType = ApiType.ollama;
+ return Optional.of(baseUrl);
+ }
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ return Optional.empty();
+ }
+
+ private String readBaseUrlFromPidFile(Path pidFile) throws Exception {
+ String json = Files.readString(pidFile);
+ JsonObject jo = (JsonObject) Jsoner.deserialize(json);
+ return jo.getString("baseUrl");
+ }
+
+ private Optional<String> tryDefaultOllama() {
+ if (isEndpointReachable(DEFAULT_OLLAMA_URL)) {
+ apiType = ApiType.ollama;
+ return Optional.of(DEFAULT_OLLAMA_URL);
+ }
+ return Optional.empty();
+ }
+
+ private String resolveApiKey() {
+ if (apiKey != null && !apiKey.isBlank()) {
+ return apiKey;
+ }
+ return Stream.of("OPENAI_API_KEY", "LLM_API_KEY")
+ .map(System::getenv)
+ .filter(k -> k != null && !k.isBlank())
+ .findFirst()
+ .orElse(null);
+ }
+
+ private Map<Long, Path> findOllamaPids() throws Exception {
+ Map<Long, Path> pids = new HashMap<>();
+ Path camelDir = CommandLineHelper.getCamelDir();
+
+ if (!Files.exists(camelDir)) {
+ return pids;
+ }
+
+ try (Stream<Path> fileStream = Files.list(camelDir)) {
+ fileStream
+ .filter(this::isOllamaPidFile)
+ .forEach(p -> addPidEntry(pids, p));
+ }
+ return pids;
+ }
+
+ private boolean isOllamaPidFile(Path p) {
+ String name = p.getFileName().toString();
+ return name.startsWith("infra-ollama-") && name.endsWith(".json");
+ }
+
+ private void addPidEntry(Map<Long, Path> pids, Path p) {
+ String name = p.getFileName().toString();
+ String pidStr = name.substring(name.lastIndexOf("-") + 1,
name.lastIndexOf('.'));
+ try {
+ pids.put(Long.valueOf(pidStr), p);
+ } catch (NumberFormatException e) {
+ // ignore
+ }
+ }
+
+ private boolean isEndpointReachable(String endpoint) {
+ return tryHealthCheck(endpoint + "/api/tags")
+ || tryHealthCheck(endpoint + "/v1/models")
+ || tryHealthCheck(endpoint);
+ }
+
+ private boolean tryHealthCheck(String healthUrl) {
+ try {
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(healthUrl))
+ .timeout(Duration.ofSeconds(HEALTH_CHECK_TIMEOUT_SECONDS))
+ .GET()
+ .build();
+ HttpResponse<String> response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
+ return response.statusCode() == 200;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private int hardenRoute(String file, String endpoint, String
resolvedApiKey) throws Exception {
+ Path path = Path.of(file);
+ if (!Files.exists(path)) {
+ printer().printErr("File not found: " + file);
+ return 1;
+ }
+
+ String routeContent = Files.readString(path);
+ String ext = Optional.ofNullable(FileUtil.onlyExt(file,
false)).orElse("yaml");
+
+ printFileHeader(file);
+
+ String sysPrompt = buildSystemPrompt();
+ String userPrompt = buildUserPrompt(routeContent, ext, file);
+
+ printPromptsIfRequested(sysPrompt, userPrompt);
+
+ String suggestions = apiType.call(this, endpoint, sysPrompt,
userPrompt, resolvedApiKey);
+
+ return handleHardeningResult(suggestions);
+ }
+
+ private void printFileHeader(String file) {
+ printer().println("=".repeat(70));
+ printer().println("Security Hardening Analysis: " + file);
+ printer().println("=".repeat(70));
+ printer().println();
+ }
+
+ private void printPromptsIfRequested(String sysPrompt, String userPrompt) {
+ if (!showPrompt) {
+ return;
+ }
+ printer().println("--- SYSTEM PROMPT ---");
+ printer().println(sysPrompt);
+ printer().println("--- USER PROMPT ---");
+ printer().println(userPrompt);
+ printer().println("--- END PROMPTS ---");
+ printer().println();
+ }
+
+ private int handleHardeningResult(String suggestions) {
+ if (suggestions == null) {
+ printer().printErr("Failed to get security hardening suggestions
from LLM");
+ return 1;
+ }
+ // With streaming, response was already printed during generation
+ // Without streaming, we need to print it now
+ if (!stream) {
+ printer().println(suggestions);
+ }
+ printer().println();
+ return 0;
+ }
+
+ private String buildSystemPrompt() {
+ if (systemPrompt != null && !systemPrompt.isBlank()) {
+ return systemPrompt;
+ }
+
+ StringBuilder prompt = new StringBuilder();
+ prompt.append("You are an Apache Camel security expert specializing in
integration security. ");
+ prompt.append("Your task is to analyze Camel routes and provide
security hardening recommendations.\n\n");
+
+ prompt.append("Focus on these security areas:\n");
+ prompt.append("1. AUTHENTICATION & AUTHORIZATION\n");
+ prompt.append(" - Missing or weak authentication mechanisms\n");
+ prompt.append(" - Lack of authorization checks\n");
+ prompt.append(" - API key and credential exposure\n\n");
+
+ prompt.append("2. ENCRYPTION & DATA PROTECTION\n");
+ prompt.append(" - Data in transit (TLS/SSL configuration)\n");
+ prompt.append(" - Data at rest encryption\n");
+ prompt.append(" - Certificate validation\n\n");
+
+ prompt.append("3. SECRETS MANAGEMENT\n");
+ prompt.append(" - Hardcoded credentials or secrets\n");
+ prompt.append(" - Secrets in configuration files\n");
+ prompt.append(" - Recommend vault integration (HashiCorp Vault, AWS
Secrets Manager, etc.)\n\n");
+
+ prompt.append("4. INPUT VALIDATION & INJECTION PREVENTION\n");
+ prompt.append(" - SQL injection vulnerabilities\n");
+ prompt.append(" - Command injection risks\n");
+ prompt.append(" - XML/JSON injection\n");
+ prompt.append(" - Path traversal vulnerabilities\n\n");
+
+ prompt.append("5. SECURE COMPONENT CONFIGURATION\n");
+ prompt.append(" - Insecure default settings\n");
+ prompt.append(" - Missing security headers\n");
+ prompt.append(" - Overly permissive configurations\n\n");
+
+ prompt.append("6. LOGGING & MONITORING\n");
+ prompt.append(" - Sensitive data in logs\n");
+ prompt.append(" - Missing audit trails\n");
+ prompt.append(" - Security event monitoring\n\n");
+
+ prompt.append("Guidelines:\n");
+ prompt.append("- Start with an executive summary of the security
posture\n");
+ prompt.append("- Prioritize findings by severity: Critical, High,
Medium, Low\n");
+ prompt.append("- For each finding, explain the risk and provide a
specific remediation\n");
+ prompt.append("- Reference OWASP guidelines where applicable\n");
+
+ if ("markdown".equals(format)) {
+ prompt.append("- Format output as Markdown with clear sections and
code blocks\n");
+ }
+ if (verbose) {
+ prompt.append("- Include specific code examples showing secure
implementations\n");
+ prompt.append("- Provide configuration snippets for recommended
security settings\n");
+ }
+
+ return prompt.toString();
+ }
+
+ private String buildUserPrompt(String routeContent, String fileExtension,
String fileName) {
+ StringBuilder prompt = new StringBuilder();
+
+ if (catalogContext) {
+ appendCatalogContext(prompt, routeContent);
+ }
+
+ prompt.append("File: ").append(fileName).append("\n");
+ prompt.append("Format:
").append(fileExtension.toUpperCase()).append("\n\n");
+ prompt.append("Route
definition:\n```").append(fileExtension).append("\n");
+ prompt.append(routeContent).append("\n```\n\n");
+ prompt.append("Please perform a security analysis of this Camel route
and provide hardening recommendations:");
+
+ return prompt.toString();
+ }
+
+ private void appendCatalogContext(StringBuilder prompt, String
routeContent) {
+ String catalogInfo = buildCatalogContext(routeContent);
+ if (catalogInfo.isEmpty()) {
+ return;
+ }
+ prompt.append("Security-relevant component
information:\n").append(catalogInfo).append("\n");
+ }
+
+ private String buildCatalogContext(String routeContent) {
+ StringBuilder context = new StringBuilder();
+ CamelCatalog catalog = new DefaultCamelCatalog();
+ String lowerContent = routeContent.toLowerCase();
+
+ SECURITY_SENSITIVE_COMPONENTS.stream()
+ .filter(comp -> containsComponent(lowerContent, comp))
+ .forEach(comp -> {
+ ComponentModel model = catalog.componentModel(comp);
+ if (model != null) {
+ context.append("- ").append(comp).append(":
").append(model.getDescription());
+ String securityNote = getSecurityNote(comp);
+ if (securityNote != null) {
+ context.append(" [Security:
").append(securityNote).append("]");
+ }
+ context.append("\n");
+ }
+ });
+
+ return context.toString();
+ }
+
+ private String getSecurityNote(String component) {
+ return switch (component) {
+ case "http" -> "Prefer HTTPS; validate certificates; configure
timeouts";
+ case "https" -> "Verify TLS version >= 1.2; validate certificates";
+ case "kafka" -> "Enable SASL authentication; use SSL; configure
ACLs";
+ case "sql", "jdbc" -> "Use parameterized queries to prevent SQL
injection";
+ case "file" -> "Validate file paths; prevent path traversal";
+ case "ftp" -> "Prefer SFTP/FTPS over plain FTP";
+ case "exec" -> "High risk - validate all inputs to prevent command
injection";
+ case "ssh" -> "Use key-based authentication; validate host keys";
+ case "rest", "rest-api", "platform-http" ->
+ "Implement authentication; validate input; set security
headers";
+ case "ldap" -> "Use LDAPS; prevent LDAP injection";
+ case "mongodb", "redis" -> "Enable authentication; use TLS";
+ case "jms", "activemq", "amqp" -> "Enable authentication; use
SSL/TLS";
+ case "aws2-s3", "aws2-sqs", "aws2-sns" -> "Use IAM roles; enable
server-side encryption";
+ case "azure-storage-blob" -> "Use managed identities; enable
encryption";
+ case "smtp", "imap" -> "Use TLS (SMTPS/IMAPS); authenticate
securely";
+ default -> null;
+ };
+ }
+
+ private boolean containsComponent(String content, String comp) {
+ return content.contains(comp + ":")
+ || content.contains("\"" + comp + "\"")
+ || content.contains("'" + comp + "'");
+ }
+
+ String callOllama(String endpoint, String sysPrompt, String userPrompt) {
+ JsonObject request = new JsonObject();
+ request.put("model", model);
+ request.put("prompt", userPrompt);
+ request.put("system", sysPrompt);
+ request.put("stream", stream);
+
+ JsonObject options = new JsonObject();
+ options.put("temperature", temperature);
+ request.put("options", options);
+
+ printer().println("Performing security analysis with " + model + "
(Ollama)...");
+ printer().println();
+
+ if (stream) {
+ return sendStreamingRequest(endpoint + "/api/generate", request);
+ }
+ JsonObject response = sendRequest(endpoint + "/api/generate", request,
null);
+ return response != null ? response.getString("response") : null;
+ }
+
+ String callOpenAiCompatible(String endpoint, String sysPrompt, String
userPrompt, String resolvedApiKey) {
+ JsonArray messages = new JsonArray();
+ messages.add(createMessage("system", sysPrompt));
+ messages.add(createMessage("user", userPrompt));
+
+ JsonObject request = new JsonObject();
+ request.put("model", model);
+ request.put("messages", messages);
+ request.put("temperature", temperature);
+
+ String apiUrl = normalizeOpenAiUrl(endpoint);
+
+ printer().println("Performing security analysis with " + model + "
(OpenAI-compatible)...");
+ printer().println();
+
+ JsonObject response = sendRequest(apiUrl, request, resolvedApiKey);
+ return extractOpenAiContent(response);
+ }
+
+ private JsonObject createMessage(String role, String content) {
+ JsonObject msg = new JsonObject();
+ msg.put("role", role);
+ msg.put("content", content);
+ return msg;
+ }
+
+ private String normalizeOpenAiUrl(String endpoint) {
+ String normalizedUrl = endpoint.endsWith("/") ? endpoint.substring(0,
endpoint.length() - 1) : endpoint;
+ if (!normalizedUrl.endsWith("/v1/chat/completions")) {
+ normalizedUrl = normalizedUrl.endsWith("/v1") ? normalizedUrl :
normalizedUrl + "/v1";
+ normalizedUrl = normalizedUrl + "/chat/completions";
+ }
+ return normalizedUrl;
+ }
+
+ private String extractOpenAiContent(JsonObject response) {
+ if (response == null) {
+ return null;
+ }
+ JsonArray choices = (JsonArray) response.get("choices");
+ if (choices == null || choices.isEmpty()) {
+ return null;
+ }
+ JsonObject firstChoice = (JsonObject) choices.get(0);
+ JsonObject message = (JsonObject) firstChoice.get("message");
+ return message != null ? message.getString("content") : null;
+ }
+
+ private String sendStreamingRequest(String requestUrl, JsonObject body) {
+ try {
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(requestUrl))
+ .timeout(Duration.ofSeconds(timeout))
+ .header("Content-Type", "application/json")
+ .POST(HttpRequest.BodyPublishers.ofString(body.toJson()))
+ .build();
+
+ HttpResponse<Stream<String>> response = httpClient.send(
+ request, HttpResponse.BodyHandlers.ofLines());
+
+ if (response.statusCode() != 200) {
+ handleErrorStatus(response.statusCode(), "Streaming request
failed");
+ return null;
+ }
+
+ StringBuilder fullResponse = new StringBuilder();
+ response.body().forEach(line -> {
+ if (line.isBlank()) {
+ return;
+ }
+ try {
+ JsonObject chunk = (JsonObject) Jsoner.deserialize(line);
+ String text = chunk.getString("response");
+ if (text != null) {
+ printer().print(text);
+ fullResponse.append(text);
+ }
+ } catch (Exception e) {
+ // Skip malformed chunks
+ }
+ });
+
+ printer().println();
+ return fullResponse.toString();
+
+ } catch (java.net.http.HttpTimeoutException e) {
+ printer().printErr("\nRequest timed out after " + timeout + "
seconds.");
+ return null;
+ } catch (Exception e) {
+ printer().printErr("\nError during streaming: " + e.getMessage());
+ return null;
+ }
+ }
+
+ private JsonObject sendRequest(String requestUrl, JsonObject body, String
authKey) {
+ try {
+ HttpRequest.Builder builder = HttpRequest.newBuilder()
+ .uri(URI.create(requestUrl))
+ .timeout(Duration.ofSeconds(timeout))
+ .header("Content-Type", "application/json")
+ .POST(HttpRequest.BodyPublishers.ofString(body.toJson()));
+
+ if (authKey != null && !authKey.isBlank()) {
+ builder.header("Authorization", "Bearer " + authKey);
+ }
+
+ HttpResponse<String> response = httpClient.send(builder.build(),
HttpResponse.BodyHandlers.ofString());
+
+ if (response.statusCode() == 200) {
+ return (JsonObject) Jsoner.deserialize(response.body());
+ }
+
+ handleErrorStatus(response.statusCode(), response.body());
+ return null;
+
+ } catch (java.net.http.HttpTimeoutException e) {
+ printer().printErr("Request timed out after " + timeout + "
seconds.");
+ return null;
+ } catch (Exception e) {
+ printer().printErr("Error calling LLM: " + e.getMessage());
+ return null;
+ }
+ }
+
+ private void handleErrorStatus(int statusCode, String body) {
+ printer().printErr("LLM returned status: " + statusCode);
+ switch (statusCode) {
+ case 401 -> printer().printErr("Authentication failed. Check your
API key.");
+ case 404 -> printer().printErr("Model '" + model + "' not found.");
+ case 429 -> printer().printErr("Rate limit exceeded.");
+ default -> printer().printErr(body);
+ }
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/HardenTools.java
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/HardenTools.java
new file mode 100644
index 000000000000..8832fd3c336a
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/HardenTools.java
@@ -0,0 +1,452 @@
+/*
+ * 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.Arrays;
+import java.util.List;
+
+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.catalog.CamelCatalog;
+import org.apache.camel.catalog.DefaultCamelCatalog;
+import org.apache.camel.tooling.model.ComponentModel;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+
+/**
+ * MCP Tool for providing security hardening context and analysis for Camel
routes.
+ * <p>
+ * This tool analyzes routes for security-sensitive components, identifies
potential vulnerabilities, and provides
+ * structured context that an LLM can use to formulate security hardening
recommendations.
+ */
+@ApplicationScoped
+public class HardenTools {
+
+ // Components with significant security considerations
+ private static final List<String> SECURITY_SENSITIVE_COMPONENTS =
Arrays.asList(
+ // Network/API components - need TLS, authentication
+ "http", "https", "netty-http", "vertx-http", "websocket",
+ "rest", "rest-api", "platform-http", "servlet", "undertow",
"jetty",
+ // Messaging - need authentication, encryption
+ "kafka", "jms", "activemq", "amqp", "rabbitmq", "pulsar",
+ "aws2-sqs", "aws2-sns", "aws2-kinesis",
+ "azure-servicebus", "azure-eventhubs",
+ "google-pubsub",
+ // File/Storage - need access control, path validation
+ "file", "ftp", "sftp", "ftps",
+ "aws2-s3", "azure-storage-blob", "azure-storage-queue",
"azure-files",
+ "google-storage", "minio",
+ // Database - need authentication, SQL injection prevention
+ "sql", "jdbc", "mongodb", "couchdb", "cassandraql",
+ "elasticsearch", "opensearch", "redis",
+ // Email - need authentication, TLS
+ "smtp", "smtps", "imap", "imaps", "pop3", "pop3s",
+ // Remote execution - high risk, need strict validation
+ "exec", "ssh", "docker",
+ // Directory services - need secure binding
+ "ldap", "ldaps",
+ // Secrets management
+ "hashicorp-vault", "aws2-secrets-manager", "azure-key-vault",
"google-secret-manager");
+
+ private static final List<String> SECURITY_BEST_PRACTICES = Arrays.asList(
+ "Use TLS/SSL (version 1.2+) for all network communications",
+ "Store secrets in vault services (HashiCorp Vault, AWS Secrets
Manager, Azure Key Vault, etc.)",
+ "Use property placeholders for sensitive configuration values",
+ "Enable authentication for all endpoints and services",
+ "Validate and sanitize all input data to prevent injection
attacks",
+ "Use parameterized queries for database operations",
+ "Implement proper certificate validation - do not disable SSL
verification",
+ "Use principle of least privilege for service accounts and IAM
roles",
+ "Enable audit logging for sensitive operations",
+ "Implement proper error handling without exposing internal
details",
+ "Use HTTPS instead of HTTP for all external communications",
+ "Configure appropriate timeouts to prevent resource exhaustion",
+ "Validate file paths to prevent path traversal attacks",
+ "Use SFTP/FTPS instead of plain FTP");
+
+ private final CamelCatalog catalog;
+
+ public HardenTools() {
+ this.catalog = new DefaultCamelCatalog();
+ }
+
+ /**
+ * Tool to get security hardening context for a Camel route.
+ */
+ @Tool(description = "Get security hardening analysis context for a Camel
route. " +
+ "Returns security-sensitive components, potential
vulnerabilities, " +
+ "and security best practices. Use this context to
provide security " +
+ "hardening recommendations for the route.")
+ public String camel_route_harden_context(
+ @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);
+ }
+
+ String resolvedFormat = format != null && !format.isBlank() ?
format.toLowerCase() : "yaml";
+
+ JsonObject result = new JsonObject();
+ result.put("format", resolvedFormat);
+ result.put("route", route);
+
+ // Analyze security-sensitive components
+ List<String> securityComponents = extractSecurityComponents(route);
+ JsonArray securityComponentsJson = new JsonArray();
+ for (String comp : securityComponents) {
+ ComponentModel model = catalog.componentModel(comp);
+ if (model != null) {
+ JsonObject compJson = new JsonObject();
+ compJson.put("name", comp);
+ compJson.put("title", model.getTitle());
+ compJson.put("description", model.getDescription());
+ compJson.put("label", model.getLabel());
+ compJson.put("securityConsiderations",
getSecurityConsiderations(comp));
+ compJson.put("riskLevel", getRiskLevel(comp));
+ securityComponentsJson.add(compJson);
+ }
+ }
+ result.put("securitySensitiveComponents", securityComponentsJson);
+
+ // Security analysis
+ JsonObject securityAnalysis = analyzeSecurityConcerns(route);
+ result.put("securityAnalysis", securityAnalysis);
+
+ // Best practices
+ JsonArray bestPractices = new JsonArray();
+ for (String practice : SECURITY_BEST_PRACTICES) {
+ bestPractices.add(practice);
+ }
+ result.put("securityBestPractices", bestPractices);
+
+ // Summary
+ JsonObject summary = new JsonObject();
+ summary.put("securityComponentCount", securityComponentsJson.size());
+ summary.put("criticalRiskComponents",
countComponentsByRisk(securityComponents, "critical"));
+ summary.put("highRiskComponents",
countComponentsByRisk(securityComponents, "high"));
+ summary.put("concernCount",
securityAnalysis.getInteger("concernCount"));
+ summary.put("positiveCount",
securityAnalysis.getInteger("positiveCount"));
+ summary.put("hasExternalConnections", hasExternalConnections(route));
+ summary.put("hasSecretsManagement", hasSecretsManagement(route));
+ summary.put("usesTLS", usesTLS(route));
+ summary.put("hasAuthentication", hasAuthentication(route));
+ result.put("summary", summary);
+
+ return result.toJson();
+ }
+
+ /**
+ * Extract security-sensitive components from route content.
+ */
+ private List<String> extractSecurityComponents(String route) {
+ List<String> found = new ArrayList<>();
+ String lowerRoute = route.toLowerCase();
+
+ for (String comp : SECURITY_SENSITIVE_COMPONENTS) {
+ if (containsComponent(lowerRoute, comp)) {
+ found.add(comp);
+ }
+ }
+
+ return found;
+ }
+
+ /**
+ * Analyze security concerns in the route.
+ */
+ private JsonObject analyzeSecurityConcerns(String route) {
+ JsonObject analysis = new JsonObject();
+ JsonArray concerns = new JsonArray();
+ JsonArray positives = new JsonArray();
+ String lowerRoute = route.toLowerCase();
+
+ // Check for hardcoded credentials
+ if (containsHardcodedCredentials(lowerRoute)) {
+ JsonObject concern = new JsonObject();
+ concern.put("severity", "critical");
+ concern.put("category", "Secrets Management");
+ concern.put("issue", "Potential hardcoded credentials detected");
+ concern.put("recommendation", "Use property placeholders
{{secret}} or vault services for credentials");
+ concerns.add(concern);
+ }
+
+ // Check for HTTP instead of HTTPS
+ if (lowerRoute.contains("http:") && !lowerRoute.contains("https:")) {
+ JsonObject concern = new JsonObject();
+ concern.put("severity", "high");
+ concern.put("category", "Encryption");
+ concern.put("issue", "Using HTTP instead of HTTPS");
+ concern.put("recommendation", "Use HTTPS for secure communication.
Configure TLS version 1.2 or higher");
+ concerns.add(concern);
+ }
+
+ // Check for plain FTP
+ if (lowerRoute.contains("ftp:") && !lowerRoute.contains("sftp:") &&
!lowerRoute.contains("ftps:")) {
+ JsonObject concern = new JsonObject();
+ concern.put("severity", "high");
+ concern.put("category", "Encryption");
+ concern.put("issue", "Using plain FTP instead of SFTP/FTPS");
+ concern.put("recommendation", "Use SFTP or FTPS for encrypted file
transfers");
+ concerns.add(concern);
+ }
+
+ // Check for SSL/TLS disabled
+ if (lowerRoute.contains("sslcontextparameters") &&
lowerRoute.contains("false")) {
+ JsonObject concern = new JsonObject();
+ concern.put("severity", "critical");
+ concern.put("category", "Encryption");
+ concern.put("issue", "SSL/TLS may be disabled or misconfigured");
+ concern.put("recommendation", "Ensure SSL/TLS is properly enabled
and configured");
+ concerns.add(concern);
+ }
+
+ // Check for exec component (high risk)
+ if (lowerRoute.contains("exec:")) {
+ JsonObject concern = new JsonObject();
+ concern.put("severity", "critical");
+ concern.put("category", "Command Injection");
+ concern.put("issue", "Using exec component - high risk for command
injection");
+ concern.put("recommendation",
+ "Validate all inputs strictly. Consider if exec is really
necessary or if safer alternatives exist");
+ concerns.add(concern);
+ }
+
+ // Check for SQL without parameterized queries indicator
+ if (lowerRoute.contains("sql:") && !lowerRoute.contains(":#") &&
!lowerRoute.contains(":?")) {
+ JsonObject concern = new JsonObject();
+ concern.put("severity", "high");
+ concern.put("category", "SQL Injection");
+ concern.put("issue", "SQL query may not use parameterized
queries");
+ concern.put("recommendation", "Use parameterized queries with
named parameters (:#param) or positional (:?)");
+ concerns.add(concern);
+ }
+
+ // Check for LDAP injection risk
+ if (lowerRoute.contains("ldap:") && !lowerRoute.contains("ldaps:")) {
+ JsonObject concern = new JsonObject();
+ concern.put("severity", "medium");
+ concern.put("category", "Encryption");
+ concern.put("issue", "Using LDAP instead of LDAPS");
+ concern.put("recommendation", "Use LDAPS for encrypted LDAP
communication");
+ concerns.add(concern);
+ }
+
+ // Check for file component path validation
+ if (lowerRoute.contains("file:") && lowerRoute.contains("${")) {
+ JsonObject concern = new JsonObject();
+ concern.put("severity", "medium");
+ concern.put("category", "Path Traversal");
+ concern.put("issue", "File path contains dynamic expression -
potential path traversal risk");
+ concern.put("recommendation", "Validate file paths and restrict to
allowed directories");
+ concerns.add(concern);
+ }
+
+ // POSITIVE CHECKS
+
+ // Check for TLS/SSL usage
+ if (usesTLS(lowerRoute)) {
+ JsonObject positive = new JsonObject();
+ positive.put("category", "Encryption");
+ positive.put("finding", "TLS/SSL encryption is configured");
+ positives.add(positive);
+ }
+
+ // Check for property placeholders (good practice)
+ if (lowerRoute.contains("{{") && lowerRoute.contains("}}")) {
+ JsonObject positive = new JsonObject();
+ positive.put("category", "Secrets Management");
+ positive.put("finding", "Using property placeholders for
configuration");
+ positives.add(positive);
+ }
+
+ // Check for vault integration
+ if (hasSecretsManagement(lowerRoute)) {
+ JsonObject positive = new JsonObject();
+ positive.put("category", "Secrets Management");
+ positive.put("finding", "Integrated with secrets management
service");
+ positives.add(positive);
+ }
+
+ // Check for authentication configuration
+ if (hasAuthentication(lowerRoute)) {
+ JsonObject positive = new JsonObject();
+ positive.put("category", "Authentication");
+ positive.put("finding", "Authentication appears to be configured");
+ positives.add(positive);
+ }
+
+ // Check for HTTPS usage
+ if (lowerRoute.contains("https:")) {
+ JsonObject positive = new JsonObject();
+ positive.put("category", "Encryption");
+ positive.put("finding", "Using HTTPS for secure HTTP
communication");
+ positives.add(positive);
+ }
+
+ // Check for SFTP/FTPS usage
+ if (lowerRoute.contains("sftp:") || lowerRoute.contains("ftps:")) {
+ JsonObject positive = new JsonObject();
+ positive.put("category", "Encryption");
+ positive.put("finding", "Using secure file transfer protocol
(SFTP/FTPS)");
+ positives.add(positive);
+ }
+
+ analysis.put("concerns", concerns);
+ analysis.put("positives", positives);
+ analysis.put("concernCount", concerns.size());
+ analysis.put("positiveCount", positives.size());
+
+ return analysis;
+ }
+
+ /**
+ * Get security considerations for a specific component.
+ */
+ private String getSecurityConsiderations(String component) {
+ return switch (component) {
+ case "http" ->
+ "Prefer HTTPS over HTTP. Validate certificates. Configure
appropriate timeouts. Set security headers.";
+ case "https" ->
+ "Verify TLS version is 1.2 or higher. Enable certificate
validation. Configure secure cipher suites.";
+ case "kafka" ->
+ "Enable SASL authentication (SCRAM-SHA-256/512 or GSSAPI). Use
SSL for encryption. Configure ACLs for authorization.";
+ case "sql", "jdbc" ->
+ "Use parameterized queries to prevent SQL injection. Limit
database user privileges. Enable connection encryption.";
+ case "file" ->
+ "Validate file paths to prevent traversal attacks. Restrict
directory access. Set appropriate file permissions.";
+ case "ftp" ->
+ "INSECURE: Use SFTP or FTPS instead. Plain FTP transmits
credentials in cleartext.";
+ case "sftp" ->
+ "Use key-based authentication. Validate host keys. Configure
known_hosts file.";
+ case "ftps" ->
+ "Enable explicit FTPS. Verify server certificates. Use strong
TLS version.";
+ case "exec" ->
+ "HIGH RISK: Validate and sanitize all inputs to prevent
command injection. Consider safer alternatives.";
+ case "ssh" ->
+ "Use key-based authentication. Validate host keys. Disable
password authentication if possible.";
+ case "rest", "rest-api", "platform-http" ->
+ "Implement authentication (OAuth2, JWT, API keys). Validate
all input. Set CORS policies. Add security headers.";
+ case "ldap" ->
+ "Use LDAPS for encryption. Escape special characters to
prevent LDAP injection. Use service account with minimal privileges.";
+ case "ldaps" ->
+ "Verify server certificates. Use strong TLS. Escape special
characters in queries.";
+ case "mongodb" ->
+ "Enable authentication. Use TLS for connections. Limit network
exposure. Use SCRAM authentication.";
+ case "redis" ->
+ "Enable authentication (requirepass or ACL). Use TLS. Limit
network exposure. Disable dangerous commands.";
+ case "jms", "activemq", "amqp", "rabbitmq" ->
+ "Enable authentication. Use SSL/TLS for connections. Configure
authorization policies.";
+ case "aws2-s3", "aws2-sqs", "aws2-sns", "aws2-kinesis" ->
+ "Use IAM roles instead of access keys. Enable server-side
encryption. Configure bucket/queue policies.";
+ case "aws2-secrets-manager" ->
+ "Use IAM roles for access. Enable automatic rotation. Audit
secret access.";
+ case "azure-storage-blob", "azure-storage-queue", "azure-files" ->
+ "Use managed identities. Enable encryption at rest. Configure
access policies.";
+ case "azure-key-vault" ->
+ "Use managed identities. Enable soft-delete. Configure access
policies and RBAC.";
+ case "google-storage", "google-pubsub" ->
+ "Use service accounts with minimal permissions. Enable
encryption. Configure IAM policies.";
+ case "google-secret-manager" ->
+ "Use service accounts. Enable automatic rotation. Audit
access.";
+ case "hashicorp-vault" ->
+ "Use AppRole or Kubernetes auth. Configure token TTLs. Enable
audit logging.";
+ case "elasticsearch", "opensearch" ->
+ "Enable authentication. Use TLS. Configure role-based access
control.";
+ case "smtp", "smtps", "imap", "imaps", "pop3", "pop3s" ->
+ "Use TLS variants (SMTPS, IMAPS, POP3S). Use secure
authentication. Store credentials securely.";
+ case "websocket" ->
+ "Use WSS (WebSocket Secure). Implement authentication.
Validate origin headers.";
+ case "docker" ->
+ "HIGH RISK: Validate all inputs. Use least privilege. Consider
container security policies.";
+ case "netty-http", "vertx-http", "undertow", "jetty", "servlet" ->
+ "Enable TLS. Implement authentication. Set security headers.
Validate input.";
+ case "pulsar" ->
+ "Enable TLS encryption. Configure authentication (JWT,
Athenz). Set authorization policies.";
+ case "minio" ->
+ "Enable TLS. Use access/secret keys securely. Configure bucket
policies.";
+ case "couchdb", "cassandraql" ->
+ "Enable authentication. Use TLS for connections. Configure
role-based access.";
+ default -> "Review security configuration for this component";
+ };
+ }
+
+ /**
+ * Get risk level for a component.
+ */
+ private String getRiskLevel(String component) {
+ return switch (component) {
+ case "exec", "docker" -> "critical";
+ case "http", "ftp", "ldap", "sql", "jdbc" -> "high";
+ case "file", "ssh", "rest", "rest-api", "platform-http", "kafka",
"mongodb", "redis" -> "medium";
+ default -> "low";
+ };
+ }
+
+ private int countComponentsByRisk(List<String> components, String
riskLevel) {
+ return (int) components.stream()
+ .filter(c -> riskLevel.equals(getRiskLevel(c)))
+ .count();
+ }
+
+ private boolean containsHardcodedCredentials(String route) {
+ return (route.contains("password=") || route.contains("password:"))
+ && !route.contains("{{") && !route.contains("$");
+ }
+
+ private boolean containsComponent(String content, String comp) {
+ return content.contains(comp + ":")
+ || content.contains("\"" + comp + "\"")
+ || content.contains("'" + comp + "'");
+ }
+
+ private boolean hasExternalConnections(String route) {
+ String lowerRoute = route.toLowerCase();
+ return lowerRoute.contains("http:") || lowerRoute.contains("https:")
+ || lowerRoute.contains("kafka:") || lowerRoute.contains("jms:")
+ || lowerRoute.contains("sql:") ||
lowerRoute.contains("mongodb:")
+ || lowerRoute.contains("aws2-") ||
lowerRoute.contains("azure-");
+ }
+
+ private boolean hasSecretsManagement(String route) {
+ String lowerRoute = route.toLowerCase();
+ return lowerRoute.contains("hashicorp-vault") ||
lowerRoute.contains("aws-secrets-manager")
+ || lowerRoute.contains("aws2-secrets-manager")
+ || lowerRoute.contains("azure-key-vault") ||
lowerRoute.contains("google-secret-manager");
+ }
+
+ private boolean usesTLS(String route) {
+ String lowerRoute = route.toLowerCase();
+ return lowerRoute.contains("ssl=true") ||
lowerRoute.contains("usessl=true")
+ || lowerRoute.contains("https:") ||
lowerRoute.contains("sftp:")
+ || lowerRoute.contains("ftps:") ||
lowerRoute.contains("ldaps:")
+ || lowerRoute.contains("smtps:") ||
lowerRoute.contains("imaps:")
+ || lowerRoute.contains("sslcontextparameters");
+ }
+
+ private boolean hasAuthentication(String route) {
+ String lowerRoute = route.toLowerCase();
+ return lowerRoute.contains("username=") ||
lowerRoute.contains("authmethod=")
+ || lowerRoute.contains("saslmechanism=") ||
lowerRoute.contains("oauth")
+ || lowerRoute.contains("bearer") ||
lowerRoute.contains("apikey")
+ || lowerRoute.contains("securityprovider");
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/main/resources/application.properties
b/dsl/camel-jbang/camel-jbang-mcp/src/main/resources/application.properties
index 16a9e79de4f8..bc542097a290 100644
--- a/dsl/camel-jbang/camel-jbang-mcp/src/main/resources/application.properties
+++ b/dsl/camel-jbang/camel-jbang-mcp/src/main/resources/application.properties
@@ -29,3 +29,6 @@ quarkus.log.console.stderr=true
quarkus.log.level=WARN
quarkus.log.category."org.apache.camel".level=INFO
quarkus.log.category."io.quarkiverse.mcp".level=INFO
+
+# Disable HTTP server - use STDIO transport only for CLI integration
+quarkus.http.host-enabled=false