This is an automated email from the ASF dual-hosted git repository.
Croway pushed a commit to branch camel-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.18.x by this push:
new 5d0028f6bc7a CAMEL-23621: filter tool argument headers against
declared parameters
5d0028f6bc7a is described below
commit 5d0028f6bc7a70556dc1d408b1b6cadb59e1842d
Author: Croway <[email protected]>
AuthorDate: Wed May 27 10:23:52 2026 +0200
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) <[email protected]>
---
.../agent/LangChain4jAgentProducer.java | 31 +++++++++++++++++++++-
.../tools/LangChain4jToolsProducer.java | 13 +++++++++
.../langchain4j/tools/LangChain4jToolTest.java | 29 ++++++++++++++++++++
.../springai/tools/SpringAiToolsEndpoint.java | 14 +++++++++-
.../ROOT/pages/camel-4x-upgrade-guide-4_18.adoc | 24 +++++++++++++++++
5 files changed, 109 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 41fc57ea0001..a18a3377aad5 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
@@ -22,6 +22,7 @@ import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.langchain4j.agent.tool.ToolSpecification;
+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;
@@ -129,9 +130,37 @@ 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.contains(name)) {
+ LOG.warn("Skipping undeclared tool
argument '{}' for tool '{}'",
+ name, toolName);
+ return;
+ }
+ JsonNode value = jsonNode.get(name);
+ Object headerValue;
+ if (value.isInt()) {
+ headerValue = value.intValue();
+ } else if (value.isLong()) {
+ headerValue = value.longValue();
+ } else if (value.isDouble()) {
+ headerValue = value.doubleValue();
+ } else if (value.isBoolean()) {
+ headerValue = value.booleanValue();
+ } else {
+ headerValue = value.asText();
+ }
+ exchange.getMessage().setHeader(name,
headerValue);
+ });
}
// 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..994df32272ab 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.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 06ca58cd8f14..1b45702d9da7 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 21e1209f1544..cc99012809a5 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 com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
@@ -120,12 +121,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
java.util.function.Function<java.util.Map<String, Object>, String>
function = args -> {
try {
org.apache.camel.Exchange exchange = createExchange();
- // Set arguments as headers
+ // Set arguments as headers, filtered against declared
parameters
for (java.util.Map.Entry<String, Object> entry :
args.entrySet()) {
+ if (!declaredParams.contains(entry.getKey())) {
+ continue;
+ }
exchange.getMessage().setHeader(entry.getKey(),
entry.getValue());
}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
index 78a743445589..2553ea29377e 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
@@ -13,6 +13,30 @@ See the xref:camel-upgrade-recipes-tool.adoc[documentation]
page for details.
== Upgrading from 4.18.2 to 4.18.3
+=== camel-langchain4j-tools / camel-langchain4j-agent / camel-spring-ai-tools
+
+LLM tool argument field names are now filtered against the tool's declared
parameter schema
+before being set as Exchange headers. Only arguments whose field names match a
declared
+`parameter.<name>` are propagated; undeclared fields are logged at WARN level
and skipped.
+
+This is a security hardening measure to prevent prompt-injection attacks from
injecting
+arbitrary Camel control headers (such as `CamelFileName`, `CamelSqlQuery`,
`CamelHttpUri`)
+via crafted tool call arguments.
+
+**Action required:** If your tool consumer routes rely on LLM-provided
arguments being set as
+Exchange headers, you must explicitly declare those parameters in the endpoint
URI. For example:
+
+[source,java]
+----
+// Before (all LLM arguments become headers — vulnerable):
+from("langchain4j-tools:myTool?tags=mytag&description=My tool")
+
+// After (only declared parameters become headers):
+from("langchain4j-tools:myTool?tags=mytag&description=My
tool¶meter.city=string¶meter.country=string")
+----
+
+Tools that already declare their parameters via `parameter.<name>=<type>` are
unaffected.
+
=== camel-aws-bedrock
The `applyGuardrail` producer operation now reads the guardrail identifier
from a new dedicated header