This is an automated email from the ASF dual-hosted git repository. Croway pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 6851a94075b82375381a7236e081292b67f6bf9a Author: Croway <[email protected]> AuthorDate: Tue May 26 17:56:22 2026 +0200 CAMEL-23621: filter tool argument headers against declared parameters --- .../agent/LangChain4jAgentProducer.java | 18 +++++++++++++- .../tools/LangChain4jToolsProducer.java | 13 ++++++++++ .../langchain4j/tools/LangChain4jToolTest.java | 29 ++++++++++++++++++++++ .../springai/tools/SpringAiToolsEndpoint.java | 14 ++++++++++- 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java index 4002071fbea2..f951b6b7e7d7 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java +++ b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java @@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import dev.langchain4j.agent.tool.ToolSpecification; import dev.langchain4j.mcp.McpToolProvider; import dev.langchain4j.mcp.client.McpClient; +import dev.langchain4j.model.chat.request.json.JsonObjectSchema; import dev.langchain4j.service.tool.ToolExecutor; import dev.langchain4j.service.tool.ToolProvider; import dev.langchain4j.service.tool.ToolProviderRequest; @@ -203,9 +204,24 @@ public class LangChain4jAgentProducer extends DefaultProducer { // Parse JSON arguments if provided String arguments = toolExecutionRequest.arguments(); if (arguments != null && !arguments.trim().isEmpty()) { + // Get declared parameters from tool specification to filter incoming fields + Set<String> declaredParams = Set.of(); + JsonObjectSchema paramSchema = toolSpecification.parameters(); + if (paramSchema != null && paramSchema.properties() != null) { + declaredParams = paramSchema.properties().keySet(); + } + final Set<String> allowedParams = declaredParams; + JsonNode jsonNode = objectMapper.readValue(arguments, JsonNode.class); jsonNode.fieldNames() - .forEachRemaining(name -> exchange.getMessage().setHeader(name, jsonNode.get(name))); + .forEachRemaining(name -> { + if (!allowedParams.isEmpty() && !allowedParams.contains(name)) { + LOG.warn("Skipping undeclared tool argument '{}' for tool '{}'", + name, toolName); + return; + } + exchange.getMessage().setHeader(name, jsonNode.get(name)); + }); } // Set the tool name as a header for route identification 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 522609ee9424..0e68f7d5e937 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 @@ -173,10 +173,23 @@ public class LangChain4jToolsProducer extends DefaultProducer { try { TypeConverter typeConverter = endpoint.getCamelContext().getTypeConverter(); + // Get declared parameters from tool specification to filter incoming fields + Set<String> declaredParams = Set.of(); + JsonObjectSchema paramSchema = camelToolSpecification.getToolSpecification().parameters(); + if (paramSchema != null && paramSchema.properties() != null) { + declaredParams = paramSchema.properties().keySet(); + } + final Set<String> allowedParams = declaredParams; + // Map Json to Header JsonNode jsonNode = objectMapper.readValue(toolExecutionRequest.arguments(), JsonNode.class); jsonNode.fieldNames() .forEachRemaining(name -> { + if (!allowedParams.isEmpty() && !allowedParams.contains(name)) { + LOG.warn("Skipping undeclared tool argument '{}' for tool '{}'", + name, toolName); + return; + } final JsonNode value = jsonNode.get(name); Object headerValue; diff --git a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolTest.java b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolTest.java index 081f304d0e07..6ce3af138e1c 100644 --- a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolTest.java +++ b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolTest.java @@ -56,6 +56,12 @@ public class LangChain4jToolTest extends CamelTestSupport { .withParam("tags", "users") .andThenInvokeTool("queryUserBySSN") .withParam("ssn", "123-45-6789") + .end() + .when("Query user 5\n") + .invokeTool("QueryUserByNumber") + .withParam("number", 5) + .withParam("CamelFileName", "../../etc/passwd") + .withParam("undeclaredParam", "injected") .build(); @Override @@ -172,6 +178,29 @@ public class LangChain4jToolTest extends CamelTestSupport { Assertions.assertThat(response).isNotNull(); } + @Test + public void testUndeclaredToolArgumentsAreNotPropagatedAsHeaders() { + List<ChatMessage> messages = new ArrayList<>(); + messages.add(new SystemMessage("You provide the requested information using the functions you have available.")); + messages.add(new UserMessage("Query user 5\n")); + + Exchange exchange = fluentTemplate.to("direct:test").withBody(messages).request(Exchange.class); + + Assertions.assertThat(exchange).isNotNull(); + Message message = exchange.getMessage(); + + // Declared parameter should be set + Assertions.assertThat(message.getHeader("number")).isEqualTo(5); + + // Undeclared parameters should NOT be propagated as headers + Assertions.assertThat(message.getHeader("CamelFileName")) + .as("Undeclared 'CamelFileName' should not be set as header") + .isNull(); + Assertions.assertThat(message.getHeader("undeclaredParam")) + .as("Undeclared 'undeclaredParam' should not be set as header") + .isNull(); + } + @Test public void testSearchAndUseNonExposedTool() throws Exception { List<ChatMessage> messages = new ArrayList<>(); diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-tools/src/main/java/org/apache/camel/component/springai/tools/SpringAiToolsEndpoint.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-tools/src/main/java/org/apache/camel/component/springai/tools/SpringAiToolsEndpoint.java index 317f49abf8ce..bd83a25882d4 100644 --- a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-tools/src/main/java/org/apache/camel/component/springai/tools/SpringAiToolsEndpoint.java +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-tools/src/main/java/org/apache/camel/component/springai/tools/SpringAiToolsEndpoint.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Function; import com.fasterxml.jackson.databind.ObjectMapper; @@ -122,12 +123,23 @@ public class SpringAiToolsEndpoint extends DefaultEndpoint { final SpringAiToolsConsumer springAiToolsConsumer = new SpringAiToolsConsumer(this, processor); configureConsumer(springAiToolsConsumer); + // Get declared parameter names to filter incoming arguments + final Set<String> declaredParams; + if (parameters != null && !parameters.isEmpty()) { + declaredParams = parseParameterMetadata(parameters).keySet(); + } else { + declaredParams = Set.of(); + } + // Create a function that executes the Camel route Function<Map<String, Object>, String> function = args -> { try { Exchange exchange = createExchange(); - // Set arguments as headers + // Set arguments as headers, filtered against declared parameters for (Map.Entry<String, Object> entry : args.entrySet()) { + if (!declaredParams.isEmpty() && !declaredParams.contains(entry.getKey())) { + continue; + } exchange.getMessage().setHeader(entry.getKey(), entry.getValue()); }
