From 46028b90d4582a5280a73f8f861e0da3866e592d Mon Sep 17 00:00:00 2001 From: Croway Date: Wed, 27 May 2026 10:23:52 +0200 Subject: [PATCH 1/2] CAMEL-23621: filter tool argument headers against declared parameters Backport of #23535 to camel-4.18.x. Filters LLM tool argument field names against the tool's declared parameter schema before setting them as Exchange headers, preventing prompt-injection attacks from injecting arbitrary Camel control headers. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../tools/LangChain4jToolsProducer.java | 59 ++++++++++++++++--- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java index 2d7e5cd0b3c75..334dc5548ceff 100644 --- a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java +++ b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.*; import dev.langchain4j.agent.tool.ToolExecutionRequest; import dev.langchain4j.agent.tool.ToolSpecification; import dev.langchain4j.data.message.AiMessage; @@ -32,17 +33,22 @@ import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.chat.request.ChatRequest; +import dev.langchain4j.model.chat.request.json.JsonObjectSchema; import dev.langchain4j.model.chat.response.ChatResponse; import dev.langchain4j.model.output.FinishReason; import dev.langchain4j.model.output.Response; import org.apache.camel.Exchange; import org.apache.camel.InvalidPayloadException; +import org.apache.camel.TypeConverter; import org.apache.camel.component.langchain4j.tools.spec.CamelToolExecutorCache; import org.apache.camel.component.langchain4j.tools.spec.CamelToolSpecification; import org.apache.camel.support.DefaultProducer; import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class LangChain4jToolsProducer extends DefaultProducer { + private static final Logger LOG = LoggerFactory.getLogger(LangChain4jToolsProducer.class); private final LangChain4jToolsEndpoint endpoint; @@ -109,7 +115,7 @@ private String toolsChat(List chatMessages, Exchange exchange) { // First talk to the model to get the tools to be called int i = 0; do { -// System.out.println("Starting iteration " + i); + // System.out.println("Starting iteration " + i); final Response response = chatWithLLM(chatMessages, toolPair, exchange, i); if (isDoneExecuting(response)) { return extractAiResponse(response); @@ -117,7 +123,7 @@ private String toolsChat(List chatMessages, Exchange exchange) { // Only invoke the tools ... the response will be computed on the next loop invokeTools(chatMessages, exchange, response, toolPair); -// System.out.println("Finished iteration " + i); + // System.out.println("Finished iteration " + i); i++; } while (true); } @@ -151,11 +157,47 @@ private void invokeTools( .filter(c -> c.getToolSpecification().name().equals(toolName)).findFirst().get(); try { + TypeConverter typeConverter = endpoint.getCamelContext().getTypeConverter(); + + // Get declared parameters from tool specification to filter incoming fields + Set declaredParams = Set.of(); + JsonObjectSchema paramSchema = camelToolSpecification.getToolSpecification().parameters(); + if (paramSchema != null && paramSchema.properties() != null) { + declaredParams = paramSchema.properties().keySet(); + } + final Set allowedParams = declaredParams; + // Map Json to Header JsonNode jsonNode = objectMapper.readValue(toolExecutionRequest.arguments(), JsonNode.class); jsonNode.fieldNames() - .forEachRemaining(name -> exchange.getMessage().setHeader(name, jsonNode.get(name))); + .forEachRemaining(name -> { + if (!allowedParams.contains(name)) { + LOG.warn("Skipping undeclared tool argument '{}' for tool '{}'", + name, toolName); + return; + } + final JsonNode value = jsonNode.get(name); + Object headerValue; + + // Try to get values for the known tool parameter types + if (value instanceof TextNode) { + headerValue = typeConverter.convertTo(String.class, value); + } else if (value instanceof IntNode) { + headerValue = typeConverter.convertTo(Integer.class, value); + } else if (value instanceof LongNode) { + headerValue = typeConverter.convertTo(Long.class, value); + } else if (value instanceof DoubleNode) { + headerValue = typeConverter.convertTo(Double.class, value); + } else if (value instanceof BooleanNode) { + headerValue = typeConverter.convertTo(Boolean.class, value); + } else { + // Fallback to JsonNode to enable the value to be extracted elsewhere + headerValue = value; + } + + exchange.getMessage().setHeader(name, headerValue); + }); // Execute the consumer route @@ -172,7 +214,7 @@ private void invokeTools( exchange.getIn().getBody(String.class)); if (chatMemory != null) { chatMemory.add(toolExecutionResultMessage); - } + } chatMessages.add(toolExecutionResultMessage); } } @@ -185,14 +227,15 @@ private void invokeTools( * @param toolPair the toolPair containing the available tools to be called * @return the response provided by the model */ - private Response chatWithLLM(List chatMessages, ToolPair toolPair, Exchange exchange, int countNum) { + private Response chatWithLLM( + List chatMessages, ToolPair toolPair, Exchange exchange, int countNum) { if (chatMemory != null) { boolean isEmpty = chatMemory.messages().size() == 0; - if (isEmpty) { // first round chat, need to add System and User message. + if (isEmpty) { // first round chat, need to add System and User message. chatMessages.forEach(chatMemory::add); - }else if (countNum == 0){ // the following rounds only need to add User message. - for (ChatMessage message : chatMessages) { + } else if (countNum == 0) { // the following rounds only need to add User message. + for (ChatMessage message : chatMessages) { if (message.type() == ChatMessageType.USER) { chatMemory.add(message); } From cec62611599bdefb2ba921f5d541c11fd669abc5 Mon Sep 17 00:00:00 2001 From: Francois de Parscau Date: Wed, 9 Sep 2026 15:01:06 +0200 Subject: [PATCH 2/2] fix(DPE-3852): update langchain4j-tools version --- .../langchain4j/tools/LangChain4jToolsConfiguration.java | 4 +++- pom.xml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsConfiguration.java b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsConfiguration.java index 2ce4f9fdd6c5d..752dac069ec87 100644 --- a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsConfiguration.java +++ b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsConfiguration.java @@ -15,6 +15,7 @@ * limitations under the License. */ package org.apache.camel.component.langchain4j.tools; + import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.model.chat.ChatModel; import org.apache.camel.RuntimeCamelException; @@ -36,7 +37,7 @@ public class LangChain4jToolsConfiguration implements Cloneable { public LangChain4jToolsConfiguration() { } - + public ChatMemory getChatMemory() { return chatMemory; } @@ -44,6 +45,7 @@ public ChatMemory getChatMemory() { public void setChatMemory(ChatMemory chatMemory) { this.chatMemory = chatMemory; } + /** * Chat Language Model of type dev.langchain4j.model.chat.ChatModel * diff --git a/pom.xml b/pom.xml index 5ba8ce03faccd..310575319944d 100644 --- a/pom.xml +++ b/pom.xml @@ -115,7 +115,7 @@ 4.1.0.2 4.8.1.20251119 4.8.1.20251119 - 4.8.1.20251119 + 4.8.1.20260909 4.8.1.20260608 4.8.1.20250320 4.8.1.20250320