This is an automated email from the ASF dual-hosted git repository.

oscerd 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 865d0b8b99f9 [backport camel-4.18.x] CAMEL-23528: validate property 
names when building MATCH/DELETE WHERE clause (#23294)
865d0b8b99f9 is described below

commit 865d0b8b99f969e06ec6275b69c72670b5763245
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue May 19 10:03:07 2026 +0200

    [backport camel-4.18.x] CAMEL-23528: validate property names when building 
MATCH/DELETE WHERE clause (#23294)
    
    * CAMEL-23528: validate property names when building MATCH/DELETE WHERE 
clause
    
    Validate Neo4j property names used to build the MATCH/DELETE WHERE clause 
in Neo4jProducer, rejecting invalid names with a clear error instead of 
producing a malformed query. Adds a unit test.
    
    Closes #23258
    
    * CAMEL-23528: document camel-neo4j property name validation in the 4.18 
upgrade guide
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    
    ---------
    
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 .../camel/component/neo4j/Neo4jProducer.java       | 26 ++++++++++++
 .../neo4j/Neo4jPropertyNameValidationTest.java     | 46 ++++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_18.adoc    | 10 +++++
 3 files changed, 82 insertions(+)

diff --git 
a/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/Neo4jProducer.java
 
b/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/Neo4jProducer.java
index 351e750b33f5..c821afd8cb3f 100644
--- 
a/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/Neo4jProducer.java
+++ 
b/components/camel-ai/camel-neo4j/src/main/java/org/apache/camel/component/neo4j/Neo4jProducer.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.neo4j;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 import com.fasterxml.jackson.core.type.TypeReference;
@@ -56,6 +57,10 @@ public class Neo4jProducer extends DefaultProducer {
     private static final TypeReference<Map<String, Object>> MAP_TYPE_REF = new 
TypeReference<>() {
     };
 
+    // Only property values are passed as bound parameters; the property name 
is spliced into the
+    // Cypher query text, so it must be restricted to a safe identifier 
pattern.
+    private static final Pattern VALID_PROPERTY_NAME = 
Pattern.compile("^[A-Za-z_][A-Za-z0-9_]*$");
+
     private Driver driver;
 
     public Neo4jProducer(Neo4jEndpoint endpoint) {
@@ -134,6 +139,19 @@ public class Neo4jProducer extends DefaultProducer {
         executeWriteQuery(exchange, query, properties, databaseName, 
Neo4Operation.CREATE_NODE);
     }
 
+    /**
+     * Validates a Neo4j property name before it is spliced into a Cypher 
query. Property values are passed as bound
+     * parameters, but the property name is inserted into the query text, so 
it must be a safe identifier. Names that do
+     * not match are rejected with a clear error instead of producing a 
malformed or unintended query.
+     */
+    static void validatePropertyName(String name) {
+        if (name == null || !VALID_PROPERTY_NAME.matcher(name).matches()) {
+            throw new IllegalArgumentException(
+                    "Invalid Neo4j property name: '" + name + "'. Property 
names must match "
+                                               + 
VALID_PROPERTY_NAME.pattern());
+        }
+    }
+
     private void retrieveNodes(Exchange exchange) throws NoSuchHeaderException 
{
         final String label = getEndpoint().getConfiguration().getLabel();
         ObjectHelper.notNull(label, "label");
@@ -165,6 +183,7 @@ public class Neo4jProducer extends DefaultProducer {
                         if (paramIndex > 0) {
                             whereClause.append(" AND ");
                         }
+                        validatePropertyName(entry.getKey());
                         String paramName = "param" + paramIndex;
                         
whereClause.append(alias).append(".").append(entry.getKey())
                                 .append(" = $").append(paramName);
@@ -178,6 +197,9 @@ public class Neo4jProducer extends DefaultProducer {
                     // Empty map, match all nodes
                     query = String.format("MATCH (%s:%s) RETURN %s", alias, 
label, alias);
                 }
+            } catch (IllegalArgumentException iae) {
+                exchange.setException(new 
Neo4jOperationException(RETRIEVE_NODES, iae));
+                return;
             } catch (Exception e) {
                 exchange.setException(
                         new Neo4jOperationException(
@@ -263,6 +285,7 @@ public class Neo4jProducer extends DefaultProducer {
                         if (paramIndex > 0) {
                             whereClause.append(" AND ");
                         }
+                        validatePropertyName(entry.getKey());
                         String paramName = "param" + paramIndex;
                         
whereClause.append(alias).append(".").append(entry.getKey())
                                 .append(" = $").append(paramName);
@@ -276,6 +299,9 @@ public class Neo4jProducer extends DefaultProducer {
                     // Empty map, delete all nodes of this label
                     query = String.format("MATCH (%s:%s) %s DELETE %s", alias, 
label, detached, alias);
                 }
+            } catch (IllegalArgumentException iae) {
+                exchange.setException(new 
Neo4jOperationException(Neo4Operation.DELETE_NODE, iae));
+                return;
             } catch (Exception e) {
                 exchange.setException(
                         new Neo4jOperationException(
diff --git 
a/components/camel-ai/camel-neo4j/src/test/java/org/apache/camel/component/neo4j/Neo4jPropertyNameValidationTest.java
 
b/components/camel-ai/camel-neo4j/src/test/java/org/apache/camel/component/neo4j/Neo4jPropertyNameValidationTest.java
new file mode 100644
index 000000000000..f4dff68e931d
--- /dev/null
+++ 
b/components/camel-ai/camel-neo4j/src/test/java/org/apache/camel/component/neo4j/Neo4jPropertyNameValidationTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.component.neo4j;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class Neo4jPropertyNameValidationTest {
+
+    @Test
+    void acceptsValidPropertyNames() {
+        assertDoesNotThrow(() -> Neo4jProducer.validatePropertyName("name"));
+        assertDoesNotThrow(() -> 
Neo4jProducer.validatePropertyName("_internal"));
+        assertDoesNotThrow(() -> 
Neo4jProducer.validatePropertyName("firstName2"));
+        assertDoesNotThrow(() -> Neo4jProducer.validatePropertyName("A_B_1"));
+    }
+
+    @Test
+    void rejectsInvalidPropertyNames() {
+        assertThrows(IllegalArgumentException.class, () -> 
Neo4jProducer.validatePropertyName(null));
+        assertThrows(IllegalArgumentException.class, () -> 
Neo4jProducer.validatePropertyName(""));
+        assertThrows(IllegalArgumentException.class, () -> 
Neo4jProducer.validatePropertyName("first name"));
+        assertThrows(IllegalArgumentException.class, () -> 
Neo4jProducer.validatePropertyName("name-1"));
+        assertThrows(IllegalArgumentException.class, () -> 
Neo4jProducer.validatePropertyName("name.sub"));
+        assertThrows(IllegalArgumentException.class, () -> 
Neo4jProducer.validatePropertyName("1name"));
+        // A property name that would otherwise change the structure of the 
generated query.
+        assertThrows(IllegalArgumentException.class,
+                () -> Neo4jProducer.validatePropertyName("x) RETURN n UNION 
MATCH (m) RETURN m //"));
+    }
+}
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 cc272c444800..016c2e110bc1 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
@@ -105,6 +105,16 @@ sjms:queue:foo?objectMessageEnabled=true
 sjms2:queue:foo?objectMessageEnabled=true
 ----
 
+=== camel-neo4j
+
+When using the `RETRIEVE_NODES` or `DELETE_NODE` operations with the 
`CamelNeo4jMatchProperties`
+header, the property names provided in the JSON match map are now validated 
before the `MATCH` /
+`DELETE` `WHERE` clause is built. Property names must be valid identifiers 
matching
+`[A-Za-z_][A-Za-z0-9_]*`. A request whose match map contains a property name 
that does not match
+this pattern now fails fast with an `IllegalArgumentException` (wrapped in a
+`Neo4jOperationException`) instead of producing a malformed query. Property 
values continue to be
+passed as bound query parameters and are unaffected.
+
 == Upgrading from 4.18.1 to 4.18.2
 
 === camel-platform-http-main

Reply via email to