This is an automated email from the ASF dual-hosted git repository.
Croway 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 6e3e4ec510b4 CAMEL-24450: camel-jetty - do not grant CORS credentials
to an origin the operator did not name (#25829)
6e3e4ec510b4 is described below
commit 6e3e4ec510b4966c70ad99b3c032e27d6adbef9d
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Aug 28 11:35:09 2026 +0200
CAMEL-24450: camel-jetty - do not grant CORS credentials to an origin the
operator did not name (#25829)
enableCORS=true added new CrossOriginFilter() with no init parameters, so
Jetty's own
defaults applied. Confirmed against jetty-ee10-servlets 12.1.12:
DEFAULT_ALLOWED_ORIGINS
is "*" and credentials default to true. The filter reflects the request's
origin rather
than sending "*", so that pairing is the credentialed any-origin
configuration the fetch
specification refuses to express - reflecting the origin being the usual
way around that
rule. An option named "enable CORS" should not mean "every origin, with
credentials".
Default allowCredentials to false when CORS is enabled. The origin is still
reflected, so
enabling CORS keeps working for requests that carry no credentials; an
operator who needs
credentialed cross-origin requests sets filterInit.allowCredentials=true
and names the
origins in filterInit.allowedOrigins. Asking for credentials while leaving
the origins at
"*" is logged as a warning, since that combination reproduces the original
behaviour.
The defaults are applied where the init parameter map is built, not where
the filter is
added: the map is handed to the endpoint earlier and only when it is
non-empty, so
applying them later would drop them in exactly the case that matters -
enableCORS on its
own, with no filterInit parameters at all.
EnableCORSTest.testCORSenabled asserted that credentials are granted, so it
encoded the
previous behaviour; it now asserts the opposite, and a second test covers
the opt-in.
Matches the change made to camel-platform-http-vertx under CAMEL-24436.
Signed-off-by: Andrea Cosentino <[email protected]>
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
Co-authored-by: Federico Mariani <[email protected]>
---
.../camel/component/jetty/JettyHttpComponent.java | 34 ++++++++++++++++++
.../camel/component/jetty/EnableCORSTest.java | 42 ++++++++++++++++++++--
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 25 +++++++++++++
3 files changed, 99 insertions(+), 2 deletions(-)
diff --git
a/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java
b/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java
index a82c5217795e..d99f7883407d 100644
---
a/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java
+++
b/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java
@@ -201,6 +201,10 @@ public abstract class JettyHttpComponent extends
HttpCommonComponent
// extract filterInit. parameters
Map filterInitParameters =
PropertiesHelper.extractProperties(parameters, "filterInit.");
+ if (Boolean.TRUE.equals(enableCors)) {
+ // has to happen before the map is handed to the endpoint below,
which is skipped when it is empty
+ applyCorsDefaults(filterInitParameters);
+ }
URI addressUri = new
URI(UnsafeUriCharactersEncoder.encodeHttpURI(remaining));
URI endpointUri = URISupport.createRemainingURI(addressUri,
parameters);
@@ -427,6 +431,36 @@ public abstract class JettyHttpComponent extends
HttpCommonComponent
}
}
+ /**
+ * Supplies the CORS defaults Camel wants, for the init parameters the
operator did not set.
+ * <p>
+ * {@code new CrossOriginFilter()} with no init parameters takes Jetty's
own defaults, which are
+ * {@code allowedOrigins=*} together with {@code allowCredentials=true}.
Since the filter reflects the request's
+ * origin rather than sending {@code *}, that is the credentialed
any-origin configuration the fetch specification
+ * refuses to express - reflection being the usual way around that rule.
An option named "enable CORS" should not
+ * mean "every origin, with credentials".
+ * <p>
+ * Credentials therefore default to off. Reflection of the origin is left
as it was, so enabling CORS keeps working
+ * for requests that carry no credentials; an operator who needs
credentialed cross-origin requests sets
+ * {@code filterInit.allowCredentials=true} and is expected to name the
origins in {@code filterInit.allowedOrigins}
+ * at the same time, which is warned about here if they do not.
+ */
+ @SuppressWarnings("unchecked")
+ private void applyCorsDefaults(Map<String, Object> filterInitParameters) {
+ Object configuredCredentials =
filterInitParameters.get(CrossOriginFilter.ALLOW_CREDENTIALS_PARAM);
+ if (configuredCredentials == null) {
+
filterInitParameters.put(CrossOriginFilter.ALLOW_CREDENTIALS_PARAM, "false");
+ return;
+ }
+ Object configuredOrigins =
filterInitParameters.get(CrossOriginFilter.ALLOWED_ORIGINS_PARAM);
+ if (Boolean.parseBoolean(configuredCredentials.toString())
+ && (configuredOrigins == null ||
"*".equals(configuredOrigins.toString().trim()))) {
+ LOG.warn("enableCORS is configured with {}=true and no specific
{}."
+ + " Every origin will be able to make credentialed
cross-origin requests to this endpoint.",
+ CrossOriginFilter.ALLOW_CREDENTIALS_PARAM,
CrossOriginFilter.ALLOWED_ORIGINS_PARAM);
+ }
+ }
+
private void setFilters(JettyHttpEndpoint endpoint, Server server) {
ServletContextHandler context =
server.getDescendant(ServletContextHandler.class);
List<Filter> filters = endpoint.getFilters();
diff --git
a/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/EnableCORSTest.java
b/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/EnableCORSTest.java
index 5d067835054d..43786d86cad7 100644
---
a/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/EnableCORSTest.java
+++
b/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/EnableCORSTest.java
@@ -17,11 +17,13 @@
package org.apache.camel.component.jetty;
import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.AvailablePortFinder;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -29,6 +31,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class EnableCORSTest extends BaseJettyTest {
+ @RegisterExtension
+ static AvailablePortFinder.Port port3 = AvailablePortFinder.find();
+
+ private static int getPort3() {
+ return port3.getPort();
+ }
+
@Test
public void testCORSdisabled() throws Exception {
HttpGet httpMethod = new HttpGet("http://localhost:" + getPort() +
"/test1");
@@ -44,19 +53,45 @@ public class EnableCORSTest extends BaseJettyTest {
}
}
+ /**
+ * enableCORS on its own reflects the request origin, which is what makes
CORS work at all, but must not also grant
+ * credentials: reflecting the origin is the usual way around the fetch
specification's refusal to pair "*" with
+ * credentials, so the two together are the credentialed any-origin
configuration.
+ */
@Test
- public void testCORSenabled() throws Exception {
+ public void testCORSenabledDoesNotGrantCredentials() throws Exception {
HttpGet httpMethod = new HttpGet("http://localhost:" + getPort2() +
"/test2");
httpMethod.addHeader("Origin", "http://localhost:9000");
httpMethod.addHeader("Referer", "http://localhost:9000");
+ try (CloseableHttpClient client = HttpClients.createDefault();
+ CloseableHttpResponse response = client.execute(httpMethod)) {
+
+ assertEquals(200, response.getCode(), "Get a wrong response
status");
+
+ // the origin is still reflected, so CORS itself keeps working
+ assertEquals("http://localhost:9000",
response.getFirstHeader("Access-Control-Allow-Origin").getValue());
+
+ Object credentials =
response.getFirstHeader("Access-Control-Allow-Credentials");
+ assertTrue(credentials == null
+ ||
!Boolean.parseBoolean(response.getFirstHeader("Access-Control-Allow-Credentials").getValue()),
+ "credentials must not be granted to an origin the operator
did not name");
+ }
+ }
+
+ @Test
+ public void testCORSCredentialsCanBeAskedFor() throws Exception {
+ HttpGet httpMethod = new HttpGet("http://localhost:" + getPort3() +
"/test3");
+ httpMethod.addHeader("Origin", "http://localhost:9000");
+ httpMethod.addHeader("Referer", "http://localhost:9000");
+
try (CloseableHttpClient client = HttpClients.createDefault();
CloseableHttpResponse response = client.execute(httpMethod)) {
assertEquals(200, response.getCode(), "Get a wrong response
status");
String responseHeader =
response.getFirstHeader("Access-Control-Allow-Credentials").getValue();
- assertTrue(Boolean.parseBoolean(responseHeader), "CORS not
enabled");
+ assertTrue(Boolean.parseBoolean(responseHeader), "credentials
should be granted when configured");
}
}
@@ -66,6 +101,9 @@ public class EnableCORSTest extends BaseJettyTest {
public void configure() {
from("jetty://http://localhost:{{port}}/test1?enableCORS=false").transform(simple("OK"));
from("jetty://http://localhost:{{port2}}/test2?enableCORS=true").transform(simple("OK"));
+ from("jetty://http://localhost:" + getPort3() +
"/test3?enableCORS=true"
+ + "&filterInit.allowedOrigins=http://localhost:9000"
+ +
"&filterInit.allowCredentials=true").transform(simple("OK"));
}
};
}
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 4efe1e0bc545..a83d5cfaef0a 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
@@ -548,6 +548,31 @@ is truncated rather than recreated and would otherwise
keep its original permiss
Deployments where another account legitimately reads these files — a sidecar
or a backup agent running as
a different user — need to run as the owner, or use a group-aware key store
instead.
+=== camel-jetty
+
+`enableCORS=true` added `new CrossOriginFilter()` with no init parameters, so
Jetty's own defaults
+applied: `allowedOrigins=*` together with `allowCredentials=true`. Since the
filter reflects the request's
+origin rather than sending `*`, that is the credentialed any-origin
configuration the fetch specification
+refuses to express — reflecting the origin being the usual way around that
rule. An option named "enable
+CORS" should not mean "every origin, with credentials".
+
+`allowCredentials` now defaults to `false` when CORS is enabled. The origin is
still reflected, so
+enabling CORS keeps working for requests that carry no credentials.
+
+Deployments that need credentialed cross-origin requests must ask for them
explicitly:
+
+[source,text]
+----
+jetty://http://0.0.0.0:8080/api?enableCORS=true
+ &filterInit.allowedOrigins=https://app.example
+ &filterInit.allowCredentials=true
+----
+
+Setting `filterInit.allowCredentials=true` while leaving
`filterInit.allowedOrigins` unset or `*` is
+logged as a warning at startup, because that combination lets any origin make
credentialed requests.
+
+The same change was made to camel-platform-http-vertx.
+
=== camel-mllp
`logPhi` now defaults to `false`. It previously defaulted to `true`, so
message content — which for MLLP