Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,27 +51,41 @@ public class DependencyCheckTools {
+ "detects outdated Camel dependencies compared to the latest catalog version, "
+ "missing Maven dependencies for components used in routes, "
+ "and version conflicts between the Camel BOM and explicit dependency overrides. "
+ "Returns actionable recommendations with corrected dependency snippets.")
+ "Returns actionable recommendations with corrected dependency snippets. "
+ "POM content is automatically sanitized to mask sensitive data (passwords, tokens, API keys) "
+ "unless sanitizePom is set to false.")
public String camel_dependency_check(
@ToolArg(description = "The pom.xml file content") String pomContent,
@ToolArg(description = "The pom.xml file content. "
+ "IMPORTANT: Avoid including sensitive data such as passwords, tokens, or API keys. "
+ "Sensitive content is automatically detected and masked.") String pomContent,
@ToolArg(description = "Route definitions (YAML, XML, or Java DSL) to check for missing component dependencies. "
+ "Multiple routes can be provided concatenated.") String routes,
@ToolArg(description = "Runtime type: main, spring-boot, or quarkus (default: main)") String runtime,
@ToolArg(description = "Camel version to use (e.g., 4.17.0). If not specified, uses the default catalog version.") String camelVersion,
@ToolArg(description = "Platform BOM coordinates in GAV format (groupId:artifactId:version). "
+ "When provided, overrides camelVersion.") String platformBom) {
+ "When provided, overrides camelVersion.") String platformBom,
@ToolArg(description = "If true (default), automatically sanitize POM content by masking credentials") Boolean sanitizePom) {

if (pomContent == null || pomContent.isBlank()) {
throw new ToolCallException("pomContent is required", null);
}

try {
PomSanitizer.ProcessedPom processed = PomSanitizer.process(pomContent, sanitizePom);

CamelCatalog catalog = catalogService.loadCatalog(runtime, camelVersion, platformBom);

MigrationData.PomAnalysis pom = MigrationData.parsePomContent(pomContent);
MigrationData.PomAnalysis pom = MigrationData.parsePomContent(processed.content());

JsonObject result = new JsonObject();

// Add sanitization warnings if any
if (!processed.warnings().isEmpty()) {
JsonArray sanitizationArr = new JsonArray();
processed.warnings().forEach(sanitizationArr::add);
result.put("sanitizationWarnings", sanitizationArr);
}

// Project info
JsonObject projectInfo = new JsonObject();
projectInfo.put("camelVersion", pom.camelVersion());
Expand All @@ -92,7 +106,7 @@ public String camel_dependency_check(
result.put("missingDependencies", missingDeps);

// 3. Check for version conflicts (explicit overrides when BOM is present)
JsonArray conflicts = checkVersionConflicts(pomContent, pom);
JsonArray conflicts = checkVersionConflicts(processed.content(), pom);
result.put("versionConflicts", conflicts);

// 4. Build recommendations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,28 @@ public class MigrationTools {
*/
@Tool(description = "Analyze a Camel project's pom.xml to detect the runtime type (main, spring-boot, quarkus, "
+ "wildfly, karaf), Camel version, Java version, and Camel component dependencies. "
+ "This is the first step in a migration workflow.")
+ "This is the first step in a migration workflow. "
+ "POM content is automatically sanitized to mask sensitive data (passwords, tokens, API keys) "
+ "unless sanitizePom is set to false.")
public ProjectAnalysisResult camel_migration_analyze(
@ToolArg(description = "The pom.xml file content") String pomContent) {
@ToolArg(description = "The pom.xml file content. "
+ "IMPORTANT: Avoid including sensitive data such as passwords, tokens, or API keys. "
+ "Sensitive content is automatically detected and masked.") String pomContent,
@ToolArg(description = "If true (default), automatically sanitize POM content by masking credentials") Boolean sanitizePom) {

if (pomContent == null || pomContent.isBlank()) {
throw new ToolCallException("pomContent is required", null);
}

try {
MigrationData.PomAnalysis pom = MigrationData.parsePomContent(pomContent);
PomSanitizer.ProcessedPom processed = PomSanitizer.process(pomContent, sanitizePom);

MigrationData.PomAnalysis pom = MigrationData.parsePomContent(processed.content());

String runtimeType = pom.runtimeType();
int majorVersion = pom.majorVersion();

List<String> warnings = new ArrayList<>();
List<String> warnings = new ArrayList<>(processed.warnings());
if (pom.camelVersion() == null) {
warnings.add("Could not detect Camel version from pom.xml. "
+ "Check if the version is defined in a parent POM.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,25 @@ public class MigrationWildflyKarafTools {
+ "IMPORTANT: When migrating to a different runtime (e.g., WildFly to Quarkus, Karaf to Spring Boot), "
+ "you MUST use the archetype command returned by this tool to create a new project. "
+ "Do NOT manually rewrite the pom.xml — always generate a new project with the archetype first, "
+ "then migrate routes and source files into it.")
+ "then migrate routes and source files into it. "
+ "POM content is automatically sanitized to mask sensitive data (passwords, tokens, API keys) "
+ "unless sanitizePom is set to false.")
public WildflyKarafMigrationResult camel_migration_wildfly_karaf(
@ToolArg(description = "The pom.xml file content of the WildFly/Karaf project") String pomContent,
@ToolArg(description = "The pom.xml file content of the WildFly/Karaf project. "
+ "IMPORTANT: Avoid including sensitive data such as passwords, tokens, or API keys. "
+ "Sensitive content is automatically detected and masked.") String pomContent,
@ToolArg(description = "Target runtime: spring-boot or quarkus (default: quarkus)") String targetRuntime,
@ToolArg(description = "Target Camel version (e.g., 4.18.0)") String targetVersion) {
@ToolArg(description = "Target Camel version (e.g., 4.18.0)") String targetVersion,
@ToolArg(description = "If true (default), automatically sanitize POM content by masking credentials") Boolean sanitizePom) {

if (pomContent == null || pomContent.isBlank()) {
throw new ToolCallException("pomContent is required", null);
}

try {
MigrationData.PomAnalysis pom = MigrationData.parsePomContent(pomContent);
PomSanitizer.ProcessedPom processed = PomSanitizer.process(pomContent, sanitizePom);

MigrationData.PomAnalysis pom = MigrationData.parsePomContent(processed.content());

String sourceRuntime = pom.isWildfly() ? "wildfly" : pom.isKaraf() ? "karaf" : "unknown";
String resolvedTarget = targetRuntime != null && !targetRuntime.isBlank()
Expand All @@ -83,7 +90,7 @@ public WildflyKarafMigrationResult camel_migration_wildfly_karaf(
.collect(Collectors.toList());

// Warnings specific to the source runtime
List<String> warnings = new ArrayList<>();
List<String> warnings = new ArrayList<>(processed.warnings());
if ("karaf".equals(sourceRuntime)) {
warnings.add("Blueprint XML is not supported in Camel 3.x+. "
+ "Routes must be converted to YAML DSL, XML DSL, or Java DSL.");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* 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.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.jboss.logging.Logger;

/**
* Utility to detect and sanitize sensitive data in POM content before processing.
* <p>
* Scans for common credential patterns (passwords, tokens, API keys, secrets) in XML element values and masks them.
* Property placeholders (e.g., {@code ${db.password}}) are preserved since they reference external values and do not
* contain actual secrets.
* <p>
* <b>Limitations:</b> Detection is tag-name-based using keyword matching. This means:
* <ul>
* <li><b>False positives</b> — non-secret values in elements whose names happen to contain a keyword (e.g.,
* {@code <password-policy>strict</password-policy>},
* {@code <token-refresh-interval>300</token-refresh-interval>}).</li>
* <li><b>False negatives</b> — actual secrets in elements with non-obvious names (e.g., credentials embedded in JDBC
* URLs, or elements named {@code <my.credential>} where the singular form is not in the keyword list).</li>
* </ul>
* This heuristic is a best-effort safety net, not a guarantee. Users should still avoid passing sensitive data.
*/
final class PomSanitizer {

private static final Logger LOG = Logger.getLogger(PomSanitizer.class);

private static final String SENSITIVE_KEYWORDS
= "password|passwd|token|apikey|api-key|api_key|secret|secretkey|secret-key|secret_key"
+ "|accesskey|access-key|access_key|passphrase|privatekey|private-key|private_key|credentials";

/**
* Pattern matching XML elements whose tag names contain sensitive keywords. Captures: group(1) = element name,
* group(2) = element value.
*/
private static final Pattern SENSITIVE_ELEMENT_PATTERN = Pattern.compile(
"<([a-zA-Z0-9_.:-]*(?:" + SENSITIVE_KEYWORDS + ")[a-zA-Z0-9_.:-]*)>"
+ "\\s*([^<]+?)\\s*"
+ "</\\1>",
Pattern.CASE_INSENSITIVE);

private PomSanitizer() {
}

/**
* Detect sensitive content patterns in POM content.
*
* @return list of element names that contain sensitive values
*/
static List<String> detectSensitiveContent(String pomContent) {
Set<String> findings = new LinkedHashSet<>();

Matcher matcher = SENSITIVE_ELEMENT_PATTERN.matcher(pomContent);
while (matcher.find()) {
String value = matcher.group(2).trim();
// Property placeholders like ${my.password} are not actual secrets
if (!value.startsWith("${")) {
findings.add(matcher.group(1));
}
}

return new ArrayList<>(findings);
}

/**
* Sanitize POM content by masking sensitive element values.
* <p>
* Property placeholders (e.g., {@code ${db.password}}) are preserved since they do not contain actual secret
* values.
*
* @return sanitization result with the processed POM content and detected patterns
*/
static SanitizationResult sanitize(String pomContent) {
List<String> detected = detectSensitiveContent(pomContent);

String sanitized = pomContent;

// Mask sensitive element values (preserve property placeholders)
sanitized = SENSITIVE_ELEMENT_PATTERN.matcher(sanitized).replaceAll(mr -> {
String value = mr.group(2).trim();
if (value.startsWith("${")) {
return Matcher.quoteReplacement(mr.group());
}
return Matcher.quoteReplacement(
"<" + mr.group(1) + ">***MASKED***</" + mr.group(1) + ">");
});

if (!detected.isEmpty()) {
LOG.warnf("Sensitive data detected in pomContent: %s. Content was sanitized before processing.", detected);
}

return new SanitizationResult(sanitized, detected);
}

/**
* Process POM content with optional sanitization. This is the entry point for tool methods.
*
* @param pomContent the raw POM content
* @param sanitize if {@code null} or {@code true}, sanitize; if {@code false}, skip sanitization
* @return processed result with content and any warnings
*/
static ProcessedPom process(String pomContent, Boolean sanitize) {
if (sanitize != null && !sanitize) {
return new ProcessedPom(pomContent, List.of());
}
SanitizationResult sr = sanitize(pomContent);
List<String> warnings = new ArrayList<>();
if (!sr.detectedPatterns().isEmpty()) {
warnings.add("Sensitive data detected and masked: "
+ sr.detectedPatterns().stream().collect(Collectors.joining(", ")));
}
return new ProcessedPom(sr.pomContent(), warnings);
}

record SanitizationResult(
String pomContent,
List<String> detectedPatterns) {
}

record ProcessedPom(
String content,
List<String> warnings) {
}
}
Loading
Loading