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

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new a6bc7c254143 CAMEL-24718: observability must not report a 
CamelSqlQuery header the endpoint ignored
a6bc7c254143 is described below

commit a6bc7c254143b83476bed5491c93cfac7c99c945
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Sep 21 21:06:03 2026 +0200

    CAMEL-24718: observability must not report a CamelSqlQuery header the 
endpoint ignored
    
    CAMEL-24292 gated the CamelSqlQuery header behind allowQueryFromHeader,
    which is off by default, so SqlProducer ignores the header and runs the
    endpoint-configured query. Three observability sites still read the
    header unconditionally - the SqlSpanDecorator in camel-telemetry and
    camel-tracing, and SqlTraceDevConsole - and so attributed a statement
    that was never executed, with sender-controlled text, to the exchange.
    
    Each site now consults the endpoint's allowQueryFromHeader through the
    generated PropertyConfigurer (no camel-sql dependency, no per-exchange
    factory lookups) and falls back to the endpoint query. An endpoint that
    does not declare the option, such as jdbc, never honours the header.
    Tests cover both polarities in all three modules; the 4.23 upgrade
    guide notes the observability consequence. No backport: the option
    only exists from 4.23.0.
    
    Closes #26689
    
    Co-Authored-By: Claude Opus 5 <[email protected]>
---
 .../telemetry/decorators/SqlQueryHeaderHelper.java |  57 ++++++++
 .../telemetry/decorators/SqlSpanDecorator.java     |  12 +-
 .../telemetry/decorators/SqlSpanDecoratorTest.java |  83 +++++++++++-
 .../tracing/decorators/SqlQueryHeaderHelper.java   |  57 ++++++++
 .../camel/tracing/decorators/SqlSpanDecorator.java |  12 +-
 .../tracing/decorators/SqlSpanDecoratorTest.java   |  83 +++++++++++-
 .../camel/impl/console/SqlTraceDevConsole.java     |  39 +++++-
 .../console/SqlTraceDevConsoleQueryHeaderTest.java | 146 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |   7 +
 9 files changed, 474 insertions(+), 22 deletions(-)

diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlQueryHeaderHelper.java
 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlQueryHeaderHelper.java
new file mode 100644
index 000000000000..0e489a5f78ee
--- /dev/null
+++ 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlQueryHeaderHelper.java
@@ -0,0 +1,57 @@
+/*
+ * 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.telemetry.decorators;
+
+import org.apache.camel.Component;
+import org.apache.camel.Endpoint;
+import org.apache.camel.spi.PropertyConfigurer;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.support.DefaultEndpoint;
+
+/**
+ * Determines whether an endpoint honours the {@code CamelSqlQuery} header.
+ * <p/>
+ * The camel-sql components gate that header behind the {@code 
allowQueryFromHeader} option, which is disabled by
+ * default. When it is disabled the header is ignored and the 
endpoint-configured query is executed instead, so
+ * surfacing the header value would attribute a statement to the exchange that 
never ran, and would place
+ * sender-controlled text into telemetry.
+ * <p/>
+ * The option is read through the endpoint's generated {@link 
PropertyConfigurer} rather than by casting, because the
+ * tracing modules must not depend on camel-sql. An endpoint that does not 
declare the option at all (jdbc, for
+ * instance) never honours the header.
+ */
+final class SqlQueryHeaderHelper {
+
+    private static final String ALLOW_QUERY_FROM_HEADER = 
"allowQueryFromHeader";
+
+    private SqlQueryHeaderHelper() {
+    }
+
+    static boolean isQueryHeaderHonoured(Endpoint endpoint) {
+        if (!(endpoint instanceof DefaultEndpoint defaultEndpoint)) {
+            return false;
+        }
+        Component component = defaultEndpoint.getComponent();
+        if (component == null) {
+            return false;
+        }
+        if (component.getEndpointPropertyConfigurer() instanceof 
PropertyConfigurerGetter getter) {
+            return Boolean.TRUE.equals(getter.getOptionValue(endpoint, 
ALLOW_QUERY_FROM_HEADER, true));
+        }
+        return false;
+    }
+}
diff --git 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlSpanDecorator.java
 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlSpanDecorator.java
index e50aaa477e06..945f8ec81730 100644
--- 
a/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlSpanDecorator.java
+++ 
b/components/camel-telemetry/src/main/java/org/apache/camel/telemetry/decorators/SqlSpanDecorator.java
@@ -21,6 +21,8 @@ import org.apache.camel.Exchange;
 import org.apache.camel.telemetry.Span;
 import org.apache.camel.telemetry.TagConstants;
 
+import static 
org.apache.camel.telemetry.decorators.SqlQueryHeaderHelper.isQueryHeaderHonoured;
+
 public class SqlSpanDecorator extends AbstractSpanDecorator {
 
     public static final String CAMEL_SQL_QUERY = "CamelSqlQuery";
@@ -40,9 +42,13 @@ public class SqlSpanDecorator extends AbstractSpanDecorator {
         super.beforeTracingEvent(span, exchange, endpoint);
         span.setTag(TagConstants.DB_SYSTEM, "sql");
 
-        String query = exchange.getIn().getHeader(CAMEL_SQL_QUERY, 
String.class);
-        if (query != null) {
-            span.setTag(TagConstants.DB_STATEMENT, query);
+        // the header only reaches the database when the endpoint opts in, so 
tagging it
+        // unconditionally would report a statement that was never executed
+        if (isQueryHeaderHonoured(endpoint)) {
+            String query = exchange.getIn().getHeader(CAMEL_SQL_QUERY, 
String.class);
+            if (query != null) {
+                span.setTag(TagConstants.DB_STATEMENT, query);
+            }
         }
     }
 
diff --git 
a/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/decorators/SqlSpanDecoratorTest.java
 
b/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/decorators/SqlSpanDecoratorTest.java
index fae04200536c..bcc08664f91d 100644
--- 
a/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/decorators/SqlSpanDecoratorTest.java
+++ 
b/components/camel-telemetry/src/test/java/org/apache/camel/telemetry/decorators/SqlSpanDecoratorTest.java
@@ -16,9 +16,14 @@
  */
 package org.apache.camel.telemetry.decorators;
 
+import org.apache.camel.CamelContext;
+import org.apache.camel.Component;
 import org.apache.camel.Endpoint;
 import org.apache.camel.Exchange;
 import org.apache.camel.Message;
+import org.apache.camel.spi.PropertyConfigurer;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.support.DefaultEndpoint;
 import org.apache.camel.telemetry.SpanDecorator;
 import org.apache.camel.telemetry.TagConstants;
 import org.apache.camel.telemetry.mock.MockSpanAdapter;
@@ -26,14 +31,49 @@ import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
 
 public class SqlSpanDecoratorTest {
 
     private static final String SQL_STATEMENT = "select * from customer";
 
     @Test
-    public void testPre() {
-        Endpoint endpoint = Mockito.mock(Endpoint.class);
+    public void testPreTagsTheQueryHeaderWhenTheEndpointAllowsIt() {
+        MockSpanAdapter span = decorate(endpointAllowingQueryHeader(true));
+
+        assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
+        assertEquals(SQL_STATEMENT, 
span.tags().get(TagConstants.DB_STATEMENT));
+    }
+
+    @Test
+    public void testPreIgnoresTheQueryHeaderWhenTheEndpointDisallowsIt() {
+        MockSpanAdapter span = decorate(endpointAllowingQueryHeader(false));
+
+        // allowQueryFromHeader is disabled by default, so this statement was 
never executed and
+        // tagging it would put sender-controlled text into the span
+        assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
+        assertNull(span.tags().get(TagConstants.DB_STATEMENT));
+    }
+
+    @Test
+    public void testPreIgnoresTheQueryHeaderForAnEndpointWithoutTheOption() {
+        // e.g. jdbc, which takes its query from the message body
+        MockSpanAdapter span = decorate(Mockito.mock(Endpoint.class));
+
+        assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
+        assertNull(span.tags().get(TagConstants.DB_STATEMENT));
+    }
+
+    @Test
+    public void testQueryHeaderIsNotHonouredWhenTheComponentIsUnknown() {
+        DefaultEndpoint endpoint = Mockito.mock(DefaultEndpoint.class);
+        Mockito.when(endpoint.getComponent()).thenReturn(null);
+
+        assertFalse(SqlQueryHeaderHelper.isQueryHeaderHonoured(endpoint));
+    }
+
+    private static MockSpanAdapter decorate(Endpoint endpoint) {
         Exchange exchange = Mockito.mock(Exchange.class);
         Message message = Mockito.mock(Message.class);
 
@@ -42,13 +82,44 @@ public class SqlSpanDecoratorTest {
         Mockito.when(message.getHeader(SqlSpanDecorator.CAMEL_SQL_QUERY, 
String.class)).thenReturn(SQL_STATEMENT);
 
         SpanDecorator decorator = new SqlSpanDecorator();
-
         MockSpanAdapter span = new MockSpanAdapter();
-
         decorator.beforeTracingEvent(span, exchange, endpoint);
+        return span;
+    }
 
-        assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
-        assertEquals(SQL_STATEMENT, 
span.tags().get(TagConstants.DB_STATEMENT));
+    private static Endpoint endpointAllowingQueryHeader(boolean allow) {
+        Component component = Mockito.mock(Component.class);
+        Mockito.when(component.getEndpointPropertyConfigurer()).thenReturn(new 
AllowQueryFromHeaderConfigurer(allow));
+
+        DefaultEndpoint endpoint = Mockito.mock(DefaultEndpoint.class);
+        Mockito.when(endpoint.getComponent()).thenReturn(component);
+        return endpoint;
     }
 
+    /**
+     * Stands in for the generated {@code SqlEndpointConfigurer}, which 
camel-telemetry cannot depend on.
+     */
+    private static class AllowQueryFromHeaderConfigurer implements 
PropertyConfigurer, PropertyConfigurerGetter {
+
+        private final boolean allow;
+
+        AllowQueryFromHeaderConfigurer(boolean allow) {
+            this.allow = allow;
+        }
+
+        @Override
+        public boolean configure(CamelContext camelContext, Object target, 
String name, Object value, boolean ignoreCase) {
+            return false;
+        }
+
+        @Override
+        public Class<?> getOptionType(String name, boolean ignoreCase) {
+            return "allowQueryFromHeader".equals(name) ? boolean.class : null;
+        }
+
+        @Override
+        public Object getOptionValue(Object target, String name, boolean 
ignoreCase) {
+            return "allowQueryFromHeader".equals(name) ? allow : null;
+        }
+    }
 }
diff --git 
a/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlQueryHeaderHelper.java
 
b/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlQueryHeaderHelper.java
new file mode 100644
index 000000000000..d3e21c0ebeec
--- /dev/null
+++ 
b/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlQueryHeaderHelper.java
@@ -0,0 +1,57 @@
+/*
+ * 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.tracing.decorators;
+
+import org.apache.camel.Component;
+import org.apache.camel.Endpoint;
+import org.apache.camel.spi.PropertyConfigurer;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.support.DefaultEndpoint;
+
+/**
+ * Determines whether an endpoint honours the {@code CamelSqlQuery} header.
+ * <p/>
+ * The camel-sql components gate that header behind the {@code 
allowQueryFromHeader} option, which is disabled by
+ * default. When it is disabled the header is ignored and the 
endpoint-configured query is executed instead, so
+ * surfacing the header value would attribute a statement to the exchange that 
never ran, and would place
+ * sender-controlled text into telemetry.
+ * <p/>
+ * The option is read through the endpoint's generated {@link 
PropertyConfigurer} rather than by casting, because the
+ * tracing modules must not depend on camel-sql. An endpoint that does not 
declare the option at all (jdbc, for
+ * instance) never honours the header.
+ */
+final class SqlQueryHeaderHelper {
+
+    private static final String ALLOW_QUERY_FROM_HEADER = 
"allowQueryFromHeader";
+
+    private SqlQueryHeaderHelper() {
+    }
+
+    static boolean isQueryHeaderHonoured(Endpoint endpoint) {
+        if (!(endpoint instanceof DefaultEndpoint defaultEndpoint)) {
+            return false;
+        }
+        Component component = defaultEndpoint.getComponent();
+        if (component == null) {
+            return false;
+        }
+        if (component.getEndpointPropertyConfigurer() instanceof 
PropertyConfigurerGetter getter) {
+            return Boolean.TRUE.equals(getter.getOptionValue(endpoint, 
ALLOW_QUERY_FROM_HEADER, true));
+        }
+        return false;
+    }
+}
diff --git 
a/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlSpanDecorator.java
 
b/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlSpanDecorator.java
index 8077de5305df..b6709c84d223 100644
--- 
a/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlSpanDecorator.java
+++ 
b/components/camel-tracing/src/main/java/org/apache/camel/tracing/decorators/SqlSpanDecorator.java
@@ -21,6 +21,8 @@ import org.apache.camel.Exchange;
 import org.apache.camel.tracing.SpanAdapter;
 import org.apache.camel.tracing.TagConstants;
 
+import static 
org.apache.camel.tracing.decorators.SqlQueryHeaderHelper.isQueryHeaderHonoured;
+
 @Deprecated(since = "4.19.0")
 public class SqlSpanDecorator extends AbstractSpanDecorator {
 
@@ -41,9 +43,13 @@ public class SqlSpanDecorator extends AbstractSpanDecorator {
         super.pre(span, exchange, endpoint);
         span.setTag(TagConstants.DB_SYSTEM, "sql");
 
-        String query = exchange.getIn().getHeader(CAMEL_SQL_QUERY, 
String.class);
-        if (query != null) {
-            span.setTag(TagConstants.DB_STATEMENT, query);
+        // the header only reaches the database when the endpoint opts in, so 
tagging it
+        // unconditionally would report a statement that was never executed
+        if (isQueryHeaderHonoured(endpoint)) {
+            String query = exchange.getIn().getHeader(CAMEL_SQL_QUERY, 
String.class);
+            if (query != null) {
+                span.setTag(TagConstants.DB_STATEMENT, query);
+            }
         }
     }
 
diff --git 
a/components/camel-tracing/src/test/java/org/apache/camel/tracing/decorators/SqlSpanDecoratorTest.java
 
b/components/camel-tracing/src/test/java/org/apache/camel/tracing/decorators/SqlSpanDecoratorTest.java
index 3df32dee7d67..f06b6780d2a2 100644
--- 
a/components/camel-tracing/src/test/java/org/apache/camel/tracing/decorators/SqlSpanDecoratorTest.java
+++ 
b/components/camel-tracing/src/test/java/org/apache/camel/tracing/decorators/SqlSpanDecoratorTest.java
@@ -16,9 +16,14 @@
  */
 package org.apache.camel.tracing.decorators;
 
+import org.apache.camel.CamelContext;
+import org.apache.camel.Component;
 import org.apache.camel.Endpoint;
 import org.apache.camel.Exchange;
 import org.apache.camel.Message;
+import org.apache.camel.spi.PropertyConfigurer;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.support.DefaultEndpoint;
 import org.apache.camel.tracing.MockSpanAdapter;
 import org.apache.camel.tracing.SpanDecorator;
 import org.apache.camel.tracing.TagConstants;
@@ -26,6 +31,8 @@ import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
 
 @Deprecated(since = "4.19.0")
 public class SqlSpanDecoratorTest {
@@ -33,8 +40,41 @@ public class SqlSpanDecoratorTest {
     private static final String SQL_STATEMENT = "select * from customer";
 
     @Test
-    public void testPre() {
-        Endpoint endpoint = Mockito.mock(Endpoint.class);
+    public void testPreTagsTheQueryHeaderWhenTheEndpointAllowsIt() {
+        MockSpanAdapter span = decorate(endpointAllowingQueryHeader(true));
+
+        assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
+        assertEquals(SQL_STATEMENT, 
span.tags().get(TagConstants.DB_STATEMENT));
+    }
+
+    @Test
+    public void testPreIgnoresTheQueryHeaderWhenTheEndpointDisallowsIt() {
+        MockSpanAdapter span = decorate(endpointAllowingQueryHeader(false));
+
+        // allowQueryFromHeader is disabled by default, so this statement was 
never executed and
+        // tagging it would put sender-controlled text into the span
+        assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
+        assertNull(span.tags().get(TagConstants.DB_STATEMENT));
+    }
+
+    @Test
+    public void testPreIgnoresTheQueryHeaderForAnEndpointWithoutTheOption() {
+        // e.g. jdbc, which takes its query from the message body
+        MockSpanAdapter span = decorate(Mockito.mock(Endpoint.class));
+
+        assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
+        assertNull(span.tags().get(TagConstants.DB_STATEMENT));
+    }
+
+    @Test
+    public void testQueryHeaderIsNotHonouredWhenTheComponentIsUnknown() {
+        DefaultEndpoint endpoint = Mockito.mock(DefaultEndpoint.class);
+        Mockito.when(endpoint.getComponent()).thenReturn(null);
+
+        assertFalse(SqlQueryHeaderHelper.isQueryHeaderHonoured(endpoint));
+    }
+
+    private static MockSpanAdapter decorate(Endpoint endpoint) {
         Exchange exchange = Mockito.mock(Exchange.class);
         Message message = Mockito.mock(Message.class);
 
@@ -43,13 +83,44 @@ public class SqlSpanDecoratorTest {
         Mockito.when(message.getHeader(SqlSpanDecorator.CAMEL_SQL_QUERY, 
String.class)).thenReturn(SQL_STATEMENT);
 
         SpanDecorator decorator = new SqlSpanDecorator();
-
         MockSpanAdapter span = new MockSpanAdapter();
-
         decorator.pre(span, exchange, endpoint);
+        return span;
+    }
 
-        assertEquals("sql", span.tags().get(TagConstants.DB_SYSTEM));
-        assertEquals(SQL_STATEMENT, 
span.tags().get(TagConstants.DB_STATEMENT));
+    private static Endpoint endpointAllowingQueryHeader(boolean allow) {
+        Component component = Mockito.mock(Component.class);
+        Mockito.when(component.getEndpointPropertyConfigurer()).thenReturn(new 
AllowQueryFromHeaderConfigurer(allow));
+
+        DefaultEndpoint endpoint = Mockito.mock(DefaultEndpoint.class);
+        Mockito.when(endpoint.getComponent()).thenReturn(component);
+        return endpoint;
     }
 
+    /**
+     * Stands in for the generated {@code SqlEndpointConfigurer}, which 
camel-tracing cannot depend on.
+     */
+    private static class AllowQueryFromHeaderConfigurer implements 
PropertyConfigurer, PropertyConfigurerGetter {
+
+        private final boolean allow;
+
+        AllowQueryFromHeaderConfigurer(boolean allow) {
+            this.allow = allow;
+        }
+
+        @Override
+        public boolean configure(CamelContext camelContext, Object target, 
String name, Object value, boolean ignoreCase) {
+            return false;
+        }
+
+        @Override
+        public Class<?> getOptionType(String name, boolean ignoreCase) {
+            return "allowQueryFromHeader".equals(name) ? boolean.class : null;
+        }
+
+        @Override
+        public Object getOptionValue(Object target, String name, boolean 
ignoreCase) {
+            return "allowQueryFromHeader".equals(name) ? allow : null;
+        }
+    }
 }
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/SqlTraceDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/SqlTraceDevConsole.java
index c99504cf7aff..ca8a4578f10a 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/SqlTraceDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/SqlTraceDevConsole.java
@@ -25,12 +25,16 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.apache.camel.Component;
+import org.apache.camel.Endpoint;
 import org.apache.camel.Exchange;
 import org.apache.camel.NonManagedService;
 import org.apache.camel.spi.CamelEvent;
 import org.apache.camel.spi.Configurer;
 import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.PropertyConfigurerGetter;
 import org.apache.camel.spi.annotations.DevConsole;
+import org.apache.camel.support.DefaultEndpoint;
 import org.apache.camel.support.EventNotifierSupport;
 import org.apache.camel.support.ResourceHelper;
 import org.apache.camel.support.console.AbstractDevConsole;
@@ -234,6 +238,29 @@ public class SqlTraceDevConsole extends AbstractDevConsole 
{
         return "resource:" + uri;
     }
 
+    /**
+     * Whether the endpoint honours the {@code CamelSqlQuery} header.
+     * <p/>
+     * camel-sql gates that header behind the {@code allowQueryFromHeader} 
option, disabled by default; when it is
+     * disabled the endpoint-configured query runs instead, so reporting the 
header would show a statement that never
+     * executed. The option is read through the generated property configurer 
rather than by casting, because
+     * camel-console must not depend on camel-sql. Endpoints that do not 
declare the option at all - jdbc, which takes
+     * its query from the body - never honour the header.
+     */
+    private static boolean isQueryHeaderHonoured(Endpoint endpoint) {
+        if (!(endpoint instanceof DefaultEndpoint defaultEndpoint)) {
+            return false;
+        }
+        Component component = defaultEndpoint.getComponent();
+        if (component == null) {
+            return false;
+        }
+        if (component.getEndpointPropertyConfigurer() instanceof 
PropertyConfigurerGetter getter) {
+            return Boolean.TRUE.equals(getter.getOptionValue(endpoint, 
"allowQueryFromHeader", true));
+        }
+        return false;
+    }
+
     private static String detectCategory(String query) {
         if (query != null && !query.isEmpty()) {
             String upper = query.stripLeading().toUpperCase(Locale.ENGLISH);
@@ -273,11 +300,15 @@ public class SqlTraceDevConsole extends 
AbstractDevConsole {
                 if (uri.startsWith("sql:") || uri.startsWith("jdbc:")) {
                     Exchange exchange = ese.getExchange();
 
-                    // prefer the CamelSqlQuery header (runtime override) over 
the URI
+                    // prefer the CamelSqlQuery header (runtime override) over 
the URI, but only when the
+                    // endpoint actually honours it - otherwise the console 
would report a statement that
+                    // was never executed
                     String query = null;
-                    Object headerQuery = 
exchange.getMessage().getHeader("CamelSqlQuery");
-                    if (headerQuery != null) {
-                        query = headerQuery.toString();
+                    if (isQueryHeaderHonoured(ese.getEndpoint())) {
+                        Object headerQuery = 
exchange.getMessage().getHeader("CamelSqlQuery");
+                        if (headerQuery != null) {
+                            query = headerQuery.toString();
+                        }
                     }
                     if (query == null) {
                         query = extractQuery(uri);
diff --git 
a/core/camel-console/src/test/java/org/apache/camel/impl/console/SqlTraceDevConsoleQueryHeaderTest.java
 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/SqlTraceDevConsoleQueryHeaderTest.java
new file mode 100644
index 000000000000..f26d3a703d6d
--- /dev/null
+++ 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/SqlTraceDevConsoleQueryHeaderTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.impl.console;
+
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Consumer;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Endpoint;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.Producer;
+import org.apache.camel.console.DevConsole;
+import org.apache.camel.spi.PropertyConfigurer;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.support.DefaultComponent;
+import org.apache.camel.support.DefaultEndpoint;
+import org.apache.camel.support.DefaultProducer;
+import org.apache.camel.support.PluginHelper;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The {@code CamelSqlQuery} header only changes the executed statement when 
the endpoint sets
+ * {@code allowQueryFromHeader=true}, which is off by default. The console 
must not report the header as the executed
+ * query when the endpoint ignored it - the statement never ran, and the 
header is sender-controlled.
+ *
+ * @see <a 
href="https://issues.apache.org/jira/browse/CAMEL-24718";>CAMEL-24718</a>
+ */
+public class SqlTraceDevConsoleQueryHeaderTest extends ContextTestSupport {
+
+    private static final String ENDPOINT_QUERY = "select * from projects";
+    private static final String HEADER_QUERY = "select * from secrets";
+
+    @Test
+    public void testHeaderQueryIsIgnoredWhenTheEndpointDisallowsIt() throws 
Exception {
+        Assertions.assertEquals(ENDPOINT_QUERY, tracedQuery(false));
+    }
+
+    @Test
+    public void testHeaderQueryIsReportedWhenTheEndpointAllowsIt() throws 
Exception {
+        Assertions.assertEquals(HEADER_QUERY, tracedQuery(true));
+    }
+
+    private String tracedQuery(boolean allowQueryFromHeader) throws Exception {
+        context.addComponent("sql", new 
FakeSqlComponent(allowQueryFromHeader));
+
+        DevConsole con = 
PluginHelper.getDevConsoleResolver(context).resolveDevConsole("sql-trace");
+        Assertions.assertNotNull(con);
+        ServiceHelper.startService(con);
+
+        template.sendBodyAndHeader("sql:" + ENDPOINT_QUERY, "body", 
"CamelSqlQuery", HEADER_QUERY);
+
+        JsonObject out = (JsonObject) con.call(DevConsole.MediaType.JSON);
+        JsonArray statements = (JsonArray) out.get("statements");
+        Assertions.assertNotNull(statements, "the console should have traced 
the sql: send");
+        Assertions.assertEquals(1, statements.size());
+        return (String) ((JsonObject) statements.get(0)).get("query");
+    }
+
+    /**
+     * Stands in for camel-sql, which camel-console must not depend on.
+     */
+    private static final class FakeSqlComponent extends DefaultComponent {
+
+        private final PropertyConfigurer configurer;
+
+        private FakeSqlComponent(boolean allowQueryFromHeader) {
+            this.configurer = new 
AllowQueryFromHeaderConfigurer(allowQueryFromHeader);
+        }
+
+        @Override
+        public PropertyConfigurer getEndpointPropertyConfigurer() {
+            return configurer;
+        }
+
+        @Override
+        protected Endpoint createEndpoint(String uri, String remaining, 
Map<String, Object> parameters) {
+            return new FakeSqlEndpoint(uri, this);
+        }
+    }
+
+    private static final class FakeSqlEndpoint extends DefaultEndpoint {
+
+        private FakeSqlEndpoint(String uri, DefaultComponent component) {
+            super(uri, component);
+        }
+
+        @Override
+        public Producer createProducer() {
+            return new DefaultProducer(this) {
+                @Override
+                public void process(Exchange exchange) {
+                    // the statement is not actually executed; only the traced 
metadata matters here
+                }
+            };
+        }
+
+        @Override
+        public Consumer createConsumer(Processor processor) {
+            throw new UnsupportedOperationException("Consumer not supported");
+        }
+    }
+
+    private static final class AllowQueryFromHeaderConfigurer implements 
PropertyConfigurer, PropertyConfigurerGetter {
+
+        private final boolean allow;
+
+        private AllowQueryFromHeaderConfigurer(boolean allow) {
+            this.allow = allow;
+        }
+
+        @Override
+        public boolean configure(CamelContext camelContext, Object target, 
String name, Object value, boolean ignoreCase) {
+            return false;
+        }
+
+        @Override
+        public Class<?> getOptionType(String name, boolean ignoreCase) {
+            return "allowQueryFromHeader".equals(name) ? boolean.class : null;
+        }
+
+        @Override
+        public Object getOptionValue(Object target, String name, boolean 
ignoreCase) {
+            return "allowQueryFromHeader".equals(name) ? allow : null;
+        }
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 1a642e457dce..aa72b55e2601 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -2340,6 +2340,13 @@ stored-procedure template text and no longer resolves it 
through `SqlHelper.reso
 as a resource, consistent with how `camel-sql` already treats the body under 
`useMessageBodyForSql=true`. A route
 that relied on the body being a resource location must resolve it to the 
template text before the `sql-stored`
 endpoint.
+
+Observability follows the gate. The `db.statement` span tag set by the `sql` 
span decorator (camel-telemetry and
+the deprecated camel-tracing) is now taken from the `CamelSqlQuery` header 
only when the endpoint sets
+`allowQueryFromHeader=true`; otherwise the tag is omitted rather than 
reporting a statement that was never
+executed. A route that reads `db.statement` from a `sql:` span and relies on 
the header value must set
+`allowQueryFromHeader=true`.
+
 === camel-core - the inheritErrorHandler attribute on circuitBreaker and 
failoverLoadBalancer is now a String
 
 `CircuitBreakerDefinition.inheritErrorHandler` and 
`FailoverLoadBalancerDefinition.inheritErrorHandler` are now

Reply via email to