VYPR
Moderate severityOSV Advisory· Published Jan 14, 2026· Updated Jan 15, 2026

Apache Camel Neo4j: Cypher injection vulnerability in Camel-Neo4j component

CVE-2025-66169

Description

Cypher Injection vulnerability in Apache Camel camel-neo4j component.

This issue affects Apache Camel: from 4.10.0 before 4.10.8, from 4.14.0 before 4.14.3, from 4.15.0 before 4.17.0

Users are recommended to upgrade to version 4.10.8 for 4.10.x LTS and 4.14.3 for 4.14.x LTS and 4.17.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.apache.camel:camel-neo4jMaven
>= 4.10.0, < 4.10.84.10.8
org.apache.camel:camel-neo4jMaven
>= 4.14.0, < 4.14.34.14.3
org.apache.camel:camel-neo4jMaven
>= 4.15.0, < 4.17.04.17.0

Affected products

1

Patches

3
723e2cd98ce4

CAMEL-22719 - camel-neo4j - Improve detection of message body (#20037)

https://github.com/apache/camelAndrea CosentinoNov 24, 2025via ghsa
3 files changed · +124 29
  • components/camel-ai/camel-neo4j/pom.xml+4 0 modified
    @@ -47,6 +47,10 @@
           <groupId>org.apache.camel</groupId>
           <artifactId>camel-support</artifactId>
         </dependency>
    +    <dependency>
    +      <groupId>org.apache.camel</groupId>
    +      <artifactId>camel-jackson</artifactId>
    +    </dependency>
         <dependency>
           <groupId>dev.langchain4j</groupId>
           <artifactId>langchain4j-core</artifactId>
    
  • components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/Neo4jProducer.java+111 20 modified
    @@ -21,6 +21,8 @@
     import java.util.UUID;
     import java.util.stream.Collectors;
     
    +import com.fasterxml.jackson.core.type.TypeReference;
    +import com.fasterxml.jackson.databind.ObjectMapper;
     import org.apache.camel.Exchange;
     import org.apache.camel.InvalidPayloadException;
     import org.apache.camel.Message;
    @@ -50,6 +52,10 @@
     
     public class Neo4jProducer extends DefaultProducer {
     
    +    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    +    private static final TypeReference<Map<String, Object>> MAP_TYPE_REF = new TypeReference<>() {
    +    };
    +
         private Driver driver;
     
         public Neo4jProducer(Neo4jEndpoint endpoint) {
    @@ -104,15 +110,24 @@ private void createNode(Exchange exchange) throws InvalidPayloadException {
     
             final String databaseName = getEndpoint().getName();
     
    -        var query = "";
    -        Map<String, Object> properties = null;
    +        // Always use parameterized queries to prevent Cypher injection
    +        var query = String.format("CREATE (%s:%s $props)", alias, label);
    +        Map<String, Object> properties;
     
             if (body instanceof String) {
    -            // Case we get the object in a Json format
    -            query = String.format("CREATE (%s:%s %s)", alias, label, body);
    +            try {
    +                // Convert JSON string to Map for parameterized query
    +                Map<String, Object> bodyMap = OBJECT_MAPPER.readValue((String) body, MAP_TYPE_REF);
    +                properties = Map.of("props", bodyMap);
    +            } catch (Exception e) {
    +                exchange.setException(
    +                        new Neo4jOperationException(
    +                                Neo4Operation.CREATE_NODE,
    +                                new IllegalArgumentException("Failed to parse body as JSON: " + body, e)));
    +                return;
    +            }
             } else {
    -            // body should be a list of properties
    -            query = String.format("CREATE (%s:%s $props)", alias, label);
    +            // body should be a Map or similar object
                 properties = Map.of("props", body);
             }
     
    @@ -126,17 +141,55 @@ private void retrieveNodes(Exchange exchange) throws NoSuchHeaderException {
             final String alias = getEndpoint().getConfiguration().getAlias();
             ObjectHelper.notNull(alias, "alias");
     
    -        String matchQuery = exchange.getMessage().getHeader(MATCH_PROPERTIES, String.class);
    -        // in this case we search all nodes
    -        if (matchQuery == null) {
    -            matchQuery = "";
    -        }
    +        String matchProperties = exchange.getMessage().getHeader(MATCH_PROPERTIES, String.class);
     
             final String databaseName = getEndpoint().getName();
     
    -        var query = String.format("MATCH (%s:%s %s) RETURN %s", alias, label, matchQuery, alias);
    +        String query;
    +        Map<String, Object> queryParams = null;
    +
    +        if (matchProperties == null || matchProperties.isEmpty()) {
    +            // Search all nodes
    +            query = String.format("MATCH (%s:%s) RETURN %s", alias, label, alias);
    +        } else {
    +            try {
    +                // Convert JSON string to Map and build WHERE clause with parameters
    +                Map<String, Object> matchMap = OBJECT_MAPPER.readValue(matchProperties, MAP_TYPE_REF);
    +
    +                if (!matchMap.isEmpty()) {
    +                    StringBuilder whereClause = new StringBuilder();
    +                    queryParams = new java.util.HashMap<>();
    +                    int paramIndex = 0;
    +
    +                    for (Map.Entry<String, Object> entry : matchMap.entrySet()) {
    +                        if (paramIndex > 0) {
    +                            whereClause.append(" AND ");
    +                        }
    +                        String paramName = "param" + paramIndex;
    +                        whereClause.append(alias).append(".").append(entry.getKey())
    +                                .append(" = $").append(paramName);
    +                        queryParams.put(paramName, entry.getValue());
    +                        paramIndex++;
    +                    }
    +
    +                    query = String.format("MATCH (%s:%s) WHERE %s RETURN %s",
    +                            alias, label, whereClause.toString(), alias);
    +                } else {
    +                    // Empty map, match all nodes
    +                    query = String.format("MATCH (%s:%s) RETURN %s", alias, label, alias);
    +                }
    +            } catch (Exception e) {
    +                exchange.setException(
    +                        new Neo4jOperationException(
    +                                RETRIEVE_NODES,
    +                                new IllegalArgumentException(
    +                                        "Failed to parse MATCH_PROPERTIES as JSON: " + matchProperties,
    +                                        e)));
    +                return;
    +            }
    +        }
     
    -        queryRetriveNodes(exchange, databaseName, null, query, RETRIEVE_NODES);
    +        queryRetriveNodes(exchange, databaseName, queryParams, query, RETRIEVE_NODES);
         }
     
         private void retrieveNodesWithCypherQuery(Exchange exchange) throws NoSuchHeaderException {
    @@ -184,19 +237,57 @@ private void deleteNode(Exchange exchange) throws NoSuchHeaderException {
             final String alias = getEndpoint().getConfiguration().getAlias();
             ObjectHelper.notNull(alias, "alias");
     
    -        String matchQuery = exchange.getMessage().getHeader(MATCH_PROPERTIES, String.class);
    -        // in this case we search all nodes
    -        if (matchQuery == null) {
    -            matchQuery = "";
    -        }
    +        String matchProperties = exchange.getMessage().getHeader(MATCH_PROPERTIES, String.class);
     
             final String databaseName = getEndpoint().getName();
     
             final String detached = getEndpoint().getConfiguration().isDetachRelationship() ? "DETACH" : "";
     
    -        var query = String.format("MATCH (%s:%s %s) %s DELETE %s", alias, label, matchQuery, detached, alias);
    +        String query;
    +        Map<String, Object> queryParams = null;
    +
    +        if (matchProperties == null || matchProperties.isEmpty()) {
    +            // Delete all nodes of this label
    +            query = String.format("MATCH (%s:%s) %s DELETE %s", alias, label, detached, alias);
    +        } else {
    +            try {
    +                // Convert JSON string to Map and build WHERE clause with parameters
    +                Map<String, Object> matchMap = OBJECT_MAPPER.readValue(matchProperties, MAP_TYPE_REF);
    +
    +                if (!matchMap.isEmpty()) {
    +                    StringBuilder whereClause = new StringBuilder();
    +                    queryParams = new java.util.HashMap<>();
    +                    int paramIndex = 0;
    +
    +                    for (Map.Entry<String, Object> entry : matchMap.entrySet()) {
    +                        if (paramIndex > 0) {
    +                            whereClause.append(" AND ");
    +                        }
    +                        String paramName = "param" + paramIndex;
    +                        whereClause.append(alias).append(".").append(entry.getKey())
    +                                .append(" = $").append(paramName);
    +                        queryParams.put(paramName, entry.getValue());
    +                        paramIndex++;
    +                    }
    +
    +                    query = String.format("MATCH (%s:%s) WHERE %s %s DELETE %s",
    +                            alias, label, whereClause.toString(), detached, alias);
    +                } else {
    +                    // Empty map, delete all nodes of this label
    +                    query = String.format("MATCH (%s:%s) %s DELETE %s", alias, label, detached, alias);
    +                }
    +            } catch (Exception e) {
    +                exchange.setException(
    +                        new Neo4jOperationException(
    +                                Neo4Operation.DELETE_NODE,
    +                                new IllegalArgumentException(
    +                                        "Failed to parse MATCH_PROPERTIES as JSON: " + matchProperties,
    +                                        e)));
    +                return;
    +            }
    +        }
     
    -        executeWriteQuery(exchange, query, null, databaseName, Neo4Operation.DELETE_NODE);
    +        executeWriteQuery(exchange, query, queryParams, databaseName, Neo4Operation.DELETE_NODE);
         }
     
         private void createVectorIndex(Exchange exchange) {
    
  • components/camel-ai/camel-neo4j/src/test/java/org/apache/camel/component/neo4j/it/Neo4jNodeIT.java+9 9 modified
    @@ -41,8 +41,8 @@ public class Neo4jNodeIT extends Neo4jTestSupport {
         @Order(0)
         void createNodeWithJsonObject() {
     
    -        var body = "{name: 'Alice', email: 'alice@example.com', age: 30}";
    -        var expectedCypherQuery = "CREATE (u1:User {name: 'Alice', email: 'alice@example.com', age: 30})";
    +        var body = "{\"name\": \"Alice\", \"email\": \"alice@example.com\", \"age\": 30}";
    +        var expectedCypherQuery = "CREATE (u1:User $props)";
     
             Exchange result = fluentTemplate.to("neo4j:neo4j?alias=u1&label=User")
                     .withBodyAs(body, String.class)
    @@ -141,7 +141,7 @@ void testCreateMultipleNodesAndRelationshipWithCypherQuery() {
         void testRetrieveNode() {
             Exchange result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.RETRIEVE_NODES)
    -                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{name: 'Alice'}")
    +                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{\"name\": \"Alice\"}")
                     .request(Exchange.class);
     
             assertNotNull(result);
    @@ -193,7 +193,7 @@ void testDeleteNode() {
             // delete node
             Exchange result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.DELETE_NODE)
    -                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{name: 'Alice'}")
    +                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{\"name\": \"Alice\"}")
                     .request(Exchange.class);
     
             assertNotNull(result);
    @@ -215,7 +215,7 @@ void testDeleteNode() {
     
             result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.RETRIEVE_NODES)
    -                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{name: 'Alice'}")
    +                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{\"name\": \"Alice\"}")
                     .request(Exchange.class);
     
             assertNotNull(result);
    @@ -233,7 +233,7 @@ void testDeleteNodeWithExistingRelationship() {
             // try to delete user named Diana and this should fail as Diana has a relationship with Ethan
             Exchange result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.DELETE_NODE)
    -                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{name: 'Diana'}")
    +                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{\"name\": \"Diana\"}")
                     .request(Exchange.class);
     
             assertNotNull(result);
    @@ -245,7 +245,7 @@ void testDeleteNodeWithExistingRelationship() {
             // delete the Diana by detaching its relationship with Ethan - detachRelationship=true
             result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User&detachRelationship=true")
                     .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.DELETE_NODE)
    -                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{name: 'Diana'}")
    +                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{\"name\": \"Diana\"}")
                     .request(Exchange.class);
             assertNotNull(result);
             assertNull("No exception anymore when deleting relationship at same time", result.getException());
    @@ -269,7 +269,7 @@ void testDeleteNodeWithExistingRelationship() {
     
             result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.RETRIEVE_NODES)
    -                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{name: 'Diana'}")
    +                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{\"name\": \"Diana\"}")
                     .request(Exchange.class);
     
             assertNotNull(result);
    @@ -311,7 +311,7 @@ void testDeleteNodeWithCypherQuery() {
     
             result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.RETRIEVE_NODES)
    -                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{name: 'Bob'}")
    +                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{\"name\": \"Bob\"}")
                     .request(Exchange.class);
     
             assertNotNull(result);
    
e46c4c0ef542

CAMEL-22719 - camel-neo4j - Improve detection of message body (#20036)

https://github.com/apache/camelAndrea CosentinoNov 24, 2025via ghsa
3 files changed · +124 29
  • components/camel-ai/camel-neo4j/pom.xml+4 0 modified
    @@ -45,6 +45,10 @@
           <groupId>org.apache.camel</groupId>
           <artifactId>camel-support</artifactId>
         </dependency>
    +    <dependency>
    +      <groupId>org.apache.camel</groupId>
    +      <artifactId>camel-jackson</artifactId>
    +    </dependency>
         <dependency>
           <groupId>dev.langchain4j</groupId>
           <artifactId>langchain4j-core</artifactId>
    
  • components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/Neo4jProducer.java+111 20 modified
    @@ -21,6 +21,8 @@
     import java.util.UUID;
     import java.util.stream.Collectors;
     
    +import com.fasterxml.jackson.core.type.TypeReference;
    +import com.fasterxml.jackson.databind.ObjectMapper;
     import org.apache.camel.Exchange;
     import org.apache.camel.InvalidPayloadException;
     import org.apache.camel.Message;
    @@ -50,6 +52,10 @@
     
     public class Neo4jProducer extends DefaultProducer {
     
    +    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    +    private static final TypeReference<Map<String, Object>> MAP_TYPE_REF = new TypeReference<>() {
    +    };
    +
         private Driver driver;
     
         public Neo4jProducer(Neo4jEndpoint endpoint) {
    @@ -104,15 +110,24 @@ private void createNode(Exchange exchange) throws InvalidPayloadException {
     
             final String databaseName = getEndpoint().getName();
     
    -        var query = "";
    -        Map<String, Object> properties = null;
    +        // Always use parameterized queries to prevent Cypher injection
    +        var query = String.format("CREATE (%s:%s $props)", alias, label);
    +        Map<String, Object> properties;
     
             if (body instanceof String) {
    -            // Case we get the object in a Json format
    -            query = String.format("CREATE (%s:%s %s)", alias, label, body);
    +            try {
    +                // Convert JSON string to Map for parameterized query
    +                Map<String, Object> bodyMap = OBJECT_MAPPER.readValue((String) body, MAP_TYPE_REF);
    +                properties = Map.of("props", bodyMap);
    +            } catch (Exception e) {
    +                exchange.setException(
    +                        new Neo4jOperationException(
    +                                Neo4Operation.CREATE_NODE,
    +                                new IllegalArgumentException("Failed to parse body as JSON: " + body, e)));
    +                return;
    +            }
             } else {
    -            // body should be a list of properties
    -            query = String.format("CREATE (%s:%s $props)", alias, label);
    +            // body should be a Map or similar object
                 properties = Map.of("props", body);
             }
     
    @@ -126,17 +141,55 @@ private void retrieveNodes(Exchange exchange) throws NoSuchHeaderException {
             final String alias = getEndpoint().getConfiguration().getAlias();
             ObjectHelper.notNull(alias, "alias");
     
    -        String matchQuery = exchange.getMessage().getHeader(MATCH_PROPERTIES, String.class);
    -        // in this case we search all nodes
    -        if (matchQuery == null) {
    -            matchQuery = "";
    -        }
    +        String matchProperties = exchange.getMessage().getHeader(MATCH_PROPERTIES, String.class);
     
             final String databaseName = getEndpoint().getName();
     
    -        var query = String.format("MATCH (%s:%s %s) RETURN %s", alias, label, matchQuery, alias);
    +        String query;
    +        Map<String, Object> queryParams = null;
    +
    +        if (matchProperties == null || matchProperties.isEmpty()) {
    +            // Search all nodes
    +            query = String.format("MATCH (%s:%s) RETURN %s", alias, label, alias);
    +        } else {
    +            try {
    +                // Convert JSON string to Map and build WHERE clause with parameters
    +                Map<String, Object> matchMap = OBJECT_MAPPER.readValue(matchProperties, MAP_TYPE_REF);
    +
    +                if (!matchMap.isEmpty()) {
    +                    StringBuilder whereClause = new StringBuilder();
    +                    queryParams = new java.util.HashMap<>();
    +                    int paramIndex = 0;
    +
    +                    for (Map.Entry<String, Object> entry : matchMap.entrySet()) {
    +                        if (paramIndex > 0) {
    +                            whereClause.append(" AND ");
    +                        }
    +                        String paramName = "param" + paramIndex;
    +                        whereClause.append(alias).append(".").append(entry.getKey())
    +                                .append(" = $").append(paramName);
    +                        queryParams.put(paramName, entry.getValue());
    +                        paramIndex++;
    +                    }
    +
    +                    query = String.format("MATCH (%s:%s) WHERE %s RETURN %s",
    +                            alias, label, whereClause.toString(), alias);
    +                } else {
    +                    // Empty map, match all nodes
    +                    query = String.format("MATCH (%s:%s) RETURN %s", alias, label, alias);
    +                }
    +            } catch (Exception e) {
    +                exchange.setException(
    +                        new Neo4jOperationException(
    +                                RETRIEVE_NODES,
    +                                new IllegalArgumentException(
    +                                        "Failed to parse MATCH_PROPERTIES as JSON: " + matchProperties,
    +                                        e)));
    +                return;
    +            }
    +        }
     
    -        queryRetriveNodes(exchange, databaseName, null, query, RETRIEVE_NODES);
    +        queryRetriveNodes(exchange, databaseName, queryParams, query, RETRIEVE_NODES);
         }
     
         private void retrieveNodesWithCypherQuery(Exchange exchange) throws NoSuchHeaderException {
    @@ -184,19 +237,57 @@ private void deleteNode(Exchange exchange) throws NoSuchHeaderException {
             final String alias = getEndpoint().getConfiguration().getAlias();
             ObjectHelper.notNull(alias, "alias");
     
    -        String matchQuery = exchange.getMessage().getHeader(MATCH_PROPERTIES, String.class);
    -        // in this case we search all nodes
    -        if (matchQuery == null) {
    -            matchQuery = "";
    -        }
    +        String matchProperties = exchange.getMessage().getHeader(MATCH_PROPERTIES, String.class);
     
             final String databaseName = getEndpoint().getName();
     
             final String detached = getEndpoint().getConfiguration().isDetachRelationship() ? "DETACH" : "";
     
    -        var query = String.format("MATCH (%s:%s %s) %s DELETE %s", alias, label, matchQuery, detached, alias);
    +        String query;
    +        Map<String, Object> queryParams = null;
    +
    +        if (matchProperties == null || matchProperties.isEmpty()) {
    +            // Delete all nodes of this label
    +            query = String.format("MATCH (%s:%s) %s DELETE %s", alias, label, detached, alias);
    +        } else {
    +            try {
    +                // Convert JSON string to Map and build WHERE clause with parameters
    +                Map<String, Object> matchMap = OBJECT_MAPPER.readValue(matchProperties, MAP_TYPE_REF);
    +
    +                if (!matchMap.isEmpty()) {
    +                    StringBuilder whereClause = new StringBuilder();
    +                    queryParams = new java.util.HashMap<>();
    +                    int paramIndex = 0;
    +
    +                    for (Map.Entry<String, Object> entry : matchMap.entrySet()) {
    +                        if (paramIndex > 0) {
    +                            whereClause.append(" AND ");
    +                        }
    +                        String paramName = "param" + paramIndex;
    +                        whereClause.append(alias).append(".").append(entry.getKey())
    +                                .append(" = $").append(paramName);
    +                        queryParams.put(paramName, entry.getValue());
    +                        paramIndex++;
    +                    }
    +
    +                    query = String.format("MATCH (%s:%s) WHERE %s %s DELETE %s",
    +                            alias, label, whereClause.toString(), detached, alias);
    +                } else {
    +                    // Empty map, delete all nodes of this label
    +                    query = String.format("MATCH (%s:%s) %s DELETE %s", alias, label, detached, alias);
    +                }
    +            } catch (Exception e) {
    +                exchange.setException(
    +                        new Neo4jOperationException(
    +                                Neo4Operation.DELETE_NODE,
    +                                new IllegalArgumentException(
    +                                        "Failed to parse MATCH_PROPERTIES as JSON: " + matchProperties,
    +                                        e)));
    +                return;
    +            }
    +        }
     
    -        executeWriteQuery(exchange, query, null, databaseName, Neo4Operation.DELETE_NODE);
    +        executeWriteQuery(exchange, query, queryParams, databaseName, Neo4Operation.DELETE_NODE);
         }
     
         private void createVectorIndex(Exchange exchange) {
    
  • components/camel-ai/camel-neo4j/src/test/java/org/apache/camel/component/neo4j/it/Neo4jNodeIT.java+9 9 modified
    @@ -41,8 +41,8 @@ public class Neo4jNodeIT extends Neo4jTestSupport {
         @Order(0)
         void createNodeWithJsonObject() {
     
    -        var body = "{name: 'Alice', email: 'alice@example.com', age: 30}";
    -        var expectedCypherQuery = "CREATE (u1:User {name: 'Alice', email: 'alice@example.com', age: 30})";
    +        var body = "{\"name\": \"Alice\", \"email\": \"alice@example.com\", \"age\": 30}";
    +        var expectedCypherQuery = "CREATE (u1:User $props)";
     
             Exchange result = fluentTemplate.to("neo4j:neo4j?alias=u1&label=User")
                     .withBodyAs(body, String.class)
    @@ -141,7 +141,7 @@ void testCreateMultipleNodesAndRelationshipWithCypherQuery() {
         void testRetrieveNode() {
             Exchange result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.RETRIEVE_NODES)
    -                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{name: 'Alice'}")
    +                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{\"name\": \"Alice\"}")
                     .request(Exchange.class);
     
             assertNotNull(result);
    @@ -193,7 +193,7 @@ void testDeleteNode() {
             // delete node
             Exchange result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.DELETE_NODE)
    -                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{name: 'Alice'}")
    +                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{\"name\": \"Alice\"}")
                     .request(Exchange.class);
     
             assertNotNull(result);
    @@ -215,7 +215,7 @@ void testDeleteNode() {
     
             result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.RETRIEVE_NODES)
    -                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{name: 'Alice'}")
    +                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{\"name\": \"Alice\"}")
                     .request(Exchange.class);
     
             assertNotNull(result);
    @@ -233,7 +233,7 @@ void testDeleteNodeWithExistingRelationship() {
             // try to delete user named Diana and this should fail as Diana has a relationship with Ethan
             Exchange result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.DELETE_NODE)
    -                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{name: 'Diana'}")
    +                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{\"name\": \"Diana\"}")
                     .request(Exchange.class);
     
             assertNotNull(result);
    @@ -245,7 +245,7 @@ void testDeleteNodeWithExistingRelationship() {
             // delete the Diana by detaching its relationship with Ethan - detachRelationship=true
             result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User&detachRelationship=true")
                     .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.DELETE_NODE)
    -                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{name: 'Diana'}")
    +                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{\"name\": \"Diana\"}")
                     .request(Exchange.class);
             assertNotNull(result);
             assertNull("No exception anymore when deleting relationship at same time", result.getException());
    @@ -269,7 +269,7 @@ void testDeleteNodeWithExistingRelationship() {
     
             result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.RETRIEVE_NODES)
    -                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{name: 'Diana'}")
    +                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{\"name\": \"Diana\"}")
                     .request(Exchange.class);
     
             assertNotNull(result);
    @@ -311,7 +311,7 @@ void testDeleteNodeWithCypherQuery() {
     
             result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jConstants.Headers.OPERATION, Neo4Operation.RETRIEVE_NODES)
    -                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{name: 'Bob'}")
    +                .withHeader(Neo4jConstants.Headers.MATCH_PROPERTIES, "{\"name\": \"Bob\"}")
                     .request(Exchange.class);
     
             assertNotNull(result);
    
66715d3feb4b

CAMEL-22719 - camel-neo4j - Improve detection of message body (#20035)

https://github.com/apache/camelAndrea CosentinoNov 24, 2025via ghsa
3 files changed · +124 29
  • components/camel-ai/camel-neo4j/pom.xml+4 0 modified
    @@ -45,6 +45,10 @@
           <groupId>org.apache.camel</groupId>
           <artifactId>camel-support</artifactId>
         </dependency>
    +    <dependency>
    +      <groupId>org.apache.camel</groupId>
    +      <artifactId>camel-jackson</artifactId>
    +    </dependency>
         <dependency>
           <groupId>dev.langchain4j</groupId>
           <artifactId>langchain4j-core</artifactId>
    
  • components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/Neo4jProducer.java+111 20 modified
    @@ -21,6 +21,8 @@
     import java.util.UUID;
     import java.util.stream.Collectors;
     
    +import com.fasterxml.jackson.core.type.TypeReference;
    +import com.fasterxml.jackson.databind.ObjectMapper;
     import org.apache.camel.Exchange;
     import org.apache.camel.InvalidPayloadException;
     import org.apache.camel.Message;
    @@ -50,6 +52,10 @@
     
     public class Neo4jProducer extends DefaultProducer {
     
    +    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    +    private static final TypeReference<Map<String, Object>> MAP_TYPE_REF = new TypeReference<>() {
    +    };
    +
         private Driver driver;
     
         public Neo4jProducer(Neo4jEndpoint endpoint) {
    @@ -104,15 +110,24 @@ private void createNode(Exchange exchange) throws InvalidPayloadException {
     
             final String databaseName = getEndpoint().getName();
     
    -        var query = "";
    -        Map<String, Object> properties = null;
    +        // Always use parameterized queries to prevent Cypher injection
    +        var query = String.format("CREATE (%s:%s $props)", alias, label);
    +        Map<String, Object> properties;
     
             if (body instanceof String) {
    -            // Case we get the object in a Json format
    -            query = String.format("CREATE (%s:%s %s)", alias, label, body);
    +            try {
    +                // Convert JSON string to Map for parameterized query
    +                Map<String, Object> bodyMap = OBJECT_MAPPER.readValue((String) body, MAP_TYPE_REF);
    +                properties = Map.of("props", bodyMap);
    +            } catch (Exception e) {
    +                exchange.setException(
    +                        new Neo4jOperationException(
    +                                Neo4Operation.CREATE_NODE,
    +                                new IllegalArgumentException("Failed to parse body as JSON: " + body, e)));
    +                return;
    +            }
             } else {
    -            // body should be a list of properties
    -            query = String.format("CREATE (%s:%s $props)", alias, label);
    +            // body should be a Map or similar object
                 properties = Map.of("props", body);
             }
     
    @@ -126,17 +141,55 @@ private void retrieveNodes(Exchange exchange) throws NoSuchHeaderException {
             final String alias = getEndpoint().getConfiguration().getAlias();
             ObjectHelper.notNull(alias, "alias");
     
    -        String matchQuery = exchange.getMessage().getHeader(MATCH_PROPERTIES, String.class);
    -        // in this case we search all nodes
    -        if (matchQuery == null) {
    -            matchQuery = "";
    -        }
    +        String matchProperties = exchange.getMessage().getHeader(MATCH_PROPERTIES, String.class);
     
             final String databaseName = getEndpoint().getName();
     
    -        var query = String.format("MATCH (%s:%s %s) RETURN %s", alias, label, matchQuery, alias);
    +        String query;
    +        Map<String, Object> queryParams = null;
    +
    +        if (matchProperties == null || matchProperties.isEmpty()) {
    +            // Search all nodes
    +            query = String.format("MATCH (%s:%s) RETURN %s", alias, label, alias);
    +        } else {
    +            try {
    +                // Convert JSON string to Map and build WHERE clause with parameters
    +                Map<String, Object> matchMap = OBJECT_MAPPER.readValue(matchProperties, MAP_TYPE_REF);
    +
    +                if (!matchMap.isEmpty()) {
    +                    StringBuilder whereClause = new StringBuilder();
    +                    queryParams = new java.util.HashMap<>();
    +                    int paramIndex = 0;
    +
    +                    for (Map.Entry<String, Object> entry : matchMap.entrySet()) {
    +                        if (paramIndex > 0) {
    +                            whereClause.append(" AND ");
    +                        }
    +                        String paramName = "param" + paramIndex;
    +                        whereClause.append(alias).append(".").append(entry.getKey())
    +                                .append(" = $").append(paramName);
    +                        queryParams.put(paramName, entry.getValue());
    +                        paramIndex++;
    +                    }
    +
    +                    query = String.format("MATCH (%s:%s) WHERE %s RETURN %s",
    +                            alias, label, whereClause.toString(), alias);
    +                } else {
    +                    // Empty map, match all nodes
    +                    query = String.format("MATCH (%s:%s) RETURN %s", alias, label, alias);
    +                }
    +            } catch (Exception e) {
    +                exchange.setException(
    +                        new Neo4jOperationException(
    +                                RETRIEVE_NODES,
    +                                new IllegalArgumentException(
    +                                        "Failed to parse MATCH_PROPERTIES as JSON: " + matchProperties,
    +                                        e)));
    +                return;
    +            }
    +        }
     
    -        queryRetriveNodes(exchange, databaseName, null, query, RETRIEVE_NODES);
    +        queryRetriveNodes(exchange, databaseName, queryParams, query, RETRIEVE_NODES);
         }
     
         private void retrieveNodesWithCypherQuery(Exchange exchange) throws NoSuchHeaderException {
    @@ -184,19 +237,57 @@ private void deleteNode(Exchange exchange) throws NoSuchHeaderException {
             final String alias = getEndpoint().getConfiguration().getAlias();
             ObjectHelper.notNull(alias, "alias");
     
    -        String matchQuery = exchange.getMessage().getHeader(MATCH_PROPERTIES, String.class);
    -        // in this case we search all nodes
    -        if (matchQuery == null) {
    -            matchQuery = "";
    -        }
    +        String matchProperties = exchange.getMessage().getHeader(MATCH_PROPERTIES, String.class);
     
             final String databaseName = getEndpoint().getName();
     
             final String detached = getEndpoint().getConfiguration().isDetachRelationship() ? "DETACH" : "";
     
    -        var query = String.format("MATCH (%s:%s %s) %s DELETE %s", alias, label, matchQuery, detached, alias);
    +        String query;
    +        Map<String, Object> queryParams = null;
    +
    +        if (matchProperties == null || matchProperties.isEmpty()) {
    +            // Delete all nodes of this label
    +            query = String.format("MATCH (%s:%s) %s DELETE %s", alias, label, detached, alias);
    +        } else {
    +            try {
    +                // Convert JSON string to Map and build WHERE clause with parameters
    +                Map<String, Object> matchMap = OBJECT_MAPPER.readValue(matchProperties, MAP_TYPE_REF);
    +
    +                if (!matchMap.isEmpty()) {
    +                    StringBuilder whereClause = new StringBuilder();
    +                    queryParams = new java.util.HashMap<>();
    +                    int paramIndex = 0;
    +
    +                    for (Map.Entry<String, Object> entry : matchMap.entrySet()) {
    +                        if (paramIndex > 0) {
    +                            whereClause.append(" AND ");
    +                        }
    +                        String paramName = "param" + paramIndex;
    +                        whereClause.append(alias).append(".").append(entry.getKey())
    +                                .append(" = $").append(paramName);
    +                        queryParams.put(paramName, entry.getValue());
    +                        paramIndex++;
    +                    }
    +
    +                    query = String.format("MATCH (%s:%s) WHERE %s %s DELETE %s",
    +                            alias, label, whereClause.toString(), detached, alias);
    +                } else {
    +                    // Empty map, delete all nodes of this label
    +                    query = String.format("MATCH (%s:%s) %s DELETE %s", alias, label, detached, alias);
    +                }
    +            } catch (Exception e) {
    +                exchange.setException(
    +                        new Neo4jOperationException(
    +                                Neo4Operation.DELETE_NODE,
    +                                new IllegalArgumentException(
    +                                        "Failed to parse MATCH_PROPERTIES as JSON: " + matchProperties,
    +                                        e)));
    +                return;
    +            }
    +        }
     
    -        executeWriteQuery(exchange, query, null, databaseName, Neo4Operation.DELETE_NODE);
    +        executeWriteQuery(exchange, query, queryParams, databaseName, Neo4Operation.DELETE_NODE);
         }
     
         private void createVectorIndex(Exchange exchange) {
    
  • components/camel-ai/camel-neo4j/src/test/java/org/apache/camel/component/neo4j/it/Neo4jNodeIT.java+9 9 modified
    @@ -42,8 +42,8 @@ public class Neo4jNodeIT extends Neo4jTestSupport {
         @Order(0)
         void createNodeWithJsonObject() {
     
    -        var body = "{name: 'Alice', email: 'alice@example.com', age: 30}";
    -        var expectedCypherQuery = "CREATE (u1:User {name: 'Alice', email: 'alice@example.com', age: 30})";
    +        var body = "{\"name\": \"Alice\", \"email\": \"alice@example.com\", \"age\": 30}";
    +        var expectedCypherQuery = "CREATE (u1:User $props)";
     
             Exchange result = fluentTemplate.to("neo4j:neo4j?alias=u1&label=User")
                     .withBodyAs(body, String.class)
    @@ -144,7 +144,7 @@ void testCreateMultipleNodesAndRelationshipWithCypherQuery() {
         void testRetrieveNode() {
             Exchange result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jHeaders.OPERATION, Neo4Operation.RETRIEVE_NODES)
    -                .withHeader(Neo4jHeaders.MATCH_PROPERTIES, "{name: 'Alice'}")
    +                .withHeader(Neo4jHeaders.MATCH_PROPERTIES, "{\"name\": \"Alice\"}")
                     .request(Exchange.class);
     
             Assertions.assertNotNull(result);
    @@ -196,7 +196,7 @@ void testDeleteNode() {
             // delete node
             Exchange result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jHeaders.OPERATION, Neo4Operation.DELETE_NODE)
    -                .withHeader(Neo4jHeaders.MATCH_PROPERTIES, "{name: 'Alice'}")
    +                .withHeader(Neo4jHeaders.MATCH_PROPERTIES, "{\"name\": \"Alice\"}")
                     .request(Exchange.class);
     
             Assertions.assertNotNull(result);
    @@ -218,7 +218,7 @@ void testDeleteNode() {
     
             result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jHeaders.OPERATION, Neo4Operation.RETRIEVE_NODES)
    -                .withHeader(Neo4jHeaders.MATCH_PROPERTIES, "{name: 'Alice'}")
    +                .withHeader(Neo4jHeaders.MATCH_PROPERTIES, "{\"name\": \"Alice\"}")
                     .request(Exchange.class);
     
             Assertions.assertNotNull(result);
    @@ -236,7 +236,7 @@ void testDeleteNodeWithExistingRelationship() {
             // try to delete user named Diana and this should fail as Diana has a relationship with Ethan
             Exchange result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jHeaders.OPERATION, Neo4Operation.DELETE_NODE)
    -                .withHeader(Neo4jHeaders.MATCH_PROPERTIES, "{name: 'Diana'}")
    +                .withHeader(Neo4jHeaders.MATCH_PROPERTIES, "{\"name\": \"Diana\"}")
                     .request(Exchange.class);
     
             Assertions.assertNotNull(result);
    @@ -247,7 +247,7 @@ void testDeleteNodeWithExistingRelationship() {
             // delete the Diana by detaching its relationship with Ethan - detachRelationship=true
             result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User&detachRelationship=true")
                     .withHeader(Neo4jHeaders.OPERATION, Neo4Operation.DELETE_NODE)
    -                .withHeader(Neo4jHeaders.MATCH_PROPERTIES, "{name: 'Diana'}")
    +                .withHeader(Neo4jHeaders.MATCH_PROPERTIES, "{\"name\": \"Diana\"}")
                     .request(Exchange.class);
             Assertions.assertNotNull(result);
             Assertions.assertNull(result.getException(), "No exception anymore when deleting relationship at same time");
    @@ -270,7 +270,7 @@ void testDeleteNodeWithExistingRelationship() {
     
             result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jHeaders.OPERATION, Neo4Operation.RETRIEVE_NODES)
    -                .withHeader(Neo4jHeaders.MATCH_PROPERTIES, "{name: 'Diana'}")
    +                .withHeader(Neo4jHeaders.MATCH_PROPERTIES, "{\"name\": \"Diana\"}")
                     .request(Exchange.class);
     
             Assertions.assertNotNull(result);
    @@ -312,7 +312,7 @@ void testDeleteNodeWithCypherQuery() {
     
             result = fluentTemplate.to("neo4j:neo4j?alias=u&label=User")
                     .withHeader(Neo4jHeaders.OPERATION, Neo4Operation.RETRIEVE_NODES)
    -                .withHeader(Neo4jHeaders.MATCH_PROPERTIES, "{name: 'Bob'}")
    +                .withHeader(Neo4jHeaders.MATCH_PROPERTIES, "{\"name\": \"Bob\"}")
                     .request(Exchange.class);
     
             Assertions.assertNotNull(result);
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

11

News mentions

0

No linked articles in our index yet.