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

Croway pushed a commit to branch camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git

commit b0399b181d26cd8b136fa98b54ec8e5c283338da
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Aug 31 10:08:26 2026 +0200

    CAMEL-24436: camel-platform-http-vertx - only allow CORS credentials for a 
configured origin (#25820)
    
    createCorsHandler() set Access-Control-Allow-Credentials: true outside the 
origin
    check, so it went out on every response to a request carrying an Origin 
header -
    including responses to origins the handler had just decided not to allow. 
And when
    camel.server.cors.origins is unset, allowsOrigin is true for every origin 
and the
    caller's own Origin is echoed back as Access-Control-Allow-Origin. Together 
those
    produce the credentialed any-origin configuration the fetch specification 
refuses to
    express as "*", which is why reflecting the origin is the usual way around 
that rule.
    
    Send Access-Control-Allow-Credentials only when the request origin matched 
an origin
    the operator actually configured. With no origin list the origin is still 
reflected,
    as before, but credentials are not granted. Also add Vary: Origin whenever 
the origin
    is reflected, so a shared cache cannot serve one origin's response to 
another.
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    
    (cherry picked from commit 4b557e4e82048515b0141f4e3594fbecf7dc741c)
    
    Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
 .../http/vertx/VertxPlatformHttpServerSupport.java | 15 +++--
 .../http/vertx/VertxPlatformHttpEngineTest.java    | 78 ++++++++++++++++++++++
 2 files changed, 89 insertions(+), 4 deletions(-)

diff --git 
a/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpServerSupport.java
 
b/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpServerSupport.java
index aaca7dc7e62a..ab5224550110 100644
--- 
a/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpServerSupport.java
+++ 
b/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpServerSupport.java
@@ -114,14 +114,21 @@ public final class VertxPlatformHttpServerSupport {
                             corsConfig.getHeaders());
                 }
 
-                final boolean allowsOrigin
-                        = ObjectHelper.isEmpty(corsConfig.getOrigins()) || 
corsConfig.getOrigins().contains(origin);
+                // With no origin list configured the request origin is simply 
reflected back. That is the
+                // standard way around the fetch spec's rule that "*" and 
credentials are mutually exclusive,
+                // so credentials are only allowed when the operator actually 
named the origins.
+                final boolean explicitOrigins = 
ObjectHelper.isNotEmpty(corsConfig.getOrigins());
+                final boolean allowsOrigin = !explicitOrigins || 
corsConfig.getOrigins().contains(origin);
                 if (allowsOrigin) {
                     
response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
+                    // The response body varies with the request origin, so it 
must not be cached against
+                    // one origin and served to another.
+                    response.headers().add(HttpHeaders.VARY, 
HttpHeaders.ORIGIN);
+                    if (explicitOrigins) {
+                        
response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
+                    }
                 }
 
-                
response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
-
                 if (ObjectHelper.isNotEmpty(corsConfig.getExposedHeaders())) {
                     
response.headers().set(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS,
                             String.join(",", corsConfig.getExposedHeaders()));
diff --git 
a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpEngineTest.java
 
b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpEngineTest.java
index 2bfafb0db0fa..d81479098864 100644
--- 
a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpEngineTest.java
+++ 
b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpEngineTest.java
@@ -73,8 +73,10 @@ import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.emptyOrNullString;
 import static org.hamcrest.Matchers.emptyString;
 import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.equalToIgnoringCase;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
 import static org.hamcrest.Matchers.startsWith;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -425,6 +427,82 @@ public class VertxPlatformHttpEngineTest {
         }
     }
 
+    @Test
+    public void testEngineCORSNoOriginListDoesNotAllowCredentials() throws 
Exception {
+        // With no origins configured the request origin is reflected back. 
Reflection plus
+        // Access-Control-Allow-Credentials is exactly what the fetch spec 
forbids expressing as "*",
+        // so credentials must not be granted to an origin the operator never 
named.
+        final CamelContext context = createCamelContextForTest(configuration 
-> {
+            configuration.getCors().setEnabled(true);
+            configuration.getCors().setMethods(Arrays.asList("GET", "POST"));
+        });
+
+        try {
+            context.addRoutes(new RouteBuilder() {
+                @Override
+                public void configure() {
+                    from("platform-http:/").transform().constant("cors");
+                }
+            });
+            context.start();
+
+            final String origin = "http://attacker.example";;
+
+            given()
+                    .header("Origin", origin)
+                    .when()
+                    .get("/")
+                    .then()
+                    .statusCode(200)
+                    .header("Access-Control-Allow-Origin", origin)
+                    .header("Vary", equalToIgnoringCase("origin"))
+                    .header("Access-Control-Allow-Credentials", nullValue());
+        } finally {
+            context.stop();
+        }
+    }
+
+    @Test
+    public void testEngineCORSAllowsCredentialsOnlyForAConfiguredOrigin() 
throws Exception {
+        final String allowed = "https://app.example";;
+        final CamelContext context = createCamelContextForTest(configuration 
-> {
+            configuration.getCors().setEnabled(true);
+            configuration.getCors().setOrigins(Arrays.asList(allowed));
+            configuration.getCors().setMethods(Arrays.asList("GET", "POST"));
+        });
+
+        try {
+            context.addRoutes(new RouteBuilder() {
+                @Override
+                public void configure() {
+                    from("platform-http:/").transform().constant("cors");
+                }
+            });
+            context.start();
+
+            given()
+                    .header("Origin", allowed)
+                    .when()
+                    .get("/")
+                    .then()
+                    .statusCode(200)
+                    .header("Access-Control-Allow-Origin", allowed)
+                    .header("Access-Control-Allow-Credentials", "true");
+
+            // An origin outside the configured list gets neither header
+            given()
+                    .header("Origin", "https://other.example";)
+                    .when()
+                    .get("/")
+                    .then()
+                    .statusCode(200)
+                    .header("Access-Control-Allow-Origin", nullValue())
+                    .header("Access-Control-Allow-Credentials", nullValue());
+        } finally {
+            context.stop();
+        }
+    }
+
     @Test
     public void testMatchOnUriPrefix() throws Exception {
         final CamelContext context = createCamelContextForTest();

Reply via email to