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

kenhuuu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 28d8e021e462454f504564b60970b76d48c7238b
Author: Ken Hu <[email protected]>
AuthorDate: Thu Jun 4 21:23:04 2026 -0700

    Add setting to set CORS allowed origin CTR.
    
    Assisted-by: Claude Code:claude-opus-4-6
---
 CHANGELOG.asciidoc                                 |  1 +
 gremlin-server/conf/gremlin-server.yaml            |  2 ++
 .../apache/tinkerpop/gremlin/server/Settings.java  | 16 ++++++++++
 .../gremlin/server/channel/HttpChannelizer.java    | 21 ++++++++++++-
 .../server/GremlinServerHttpIntegrateTest.java     | 34 ++++++++++++++++++++++
 5 files changed, 73 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 424408b53d..e418c5e4db 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -25,6 +25,7 @@ 
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 [[release-4-0-0]]
 === TinkerPop 4.0.0 (Release Date: NOT OFFICIALLY RELEASED YET)
 
+* Added configurable CORS `allowedOrigins` setting to Gremlin Server; warns 
when wildcard origin is used alongside authentication.
 * Fixed `ByteBuf` leak in `GraphBinaryMessageSerializerV4` when serialization 
throws an `IOException`.
 * Added typed numeric wrappers and `preciseNumbers` connection option to 
`gremlin-javascript` for explicit control over numeric type serialization and 
deserialization.
 * Added `NextN(n)` to `Traversal` in `gremlin-go` for batched result 
iteration, providing API parity with `next(n)` in the Java, Python, and .NET 
GLVs, and updated the Go translators in `gremlin-core` and `gremlin-javascript` 
to emit `NextN(n)` for the batched form.
diff --git a/gremlin-server/conf/gremlin-server.yaml 
b/gremlin-server/conf/gremlin-server.yaml
index 163d807849..bb93d8e1f3 100644
--- a/gremlin-server/conf/gremlin-server.yaml
+++ b/gremlin-server/conf/gremlin-server.yaml
@@ -41,5 +41,7 @@ maxAccumulationBufferComponents: 1024
 resultIterationBatchSize: 64
 writeBufferLowWaterMark: 32768
 writeBufferHighWaterMark: 65536
+cors: {
+  allowedOrigins: ["*"]}
 ssl: {
   enabled: false}
\ No newline at end of file
diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java
 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java
index 5412960d1f..8d6111ef27 100644
--- 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java
@@ -298,6 +298,11 @@ public class Settings {
 
     public AuthorizationSettings authorization = new AuthorizationSettings();
 
+    /**
+     * Configures CORS (Cross-Origin Resource Sharing) for the HTTP endpoint.
+     */
+    public CorsSettings cors = new CorsSettings();
+
     /**
      * Enable audit logging of authenticated users and gremlin evaluation 
requests.
      */
@@ -557,6 +562,17 @@ public class Settings {
         public Map<String, Object> config = null;
     }
 
+    /**
+     * Settings to configure CORS (Cross-Origin Resource Sharing) for the HTTP 
endpoint.
+     */
+    public static class CorsSettings {
+        /**
+         * List of allowed origins. Defaults to {@code ["*"]} which permits 
any origin.
+         * Set to specific origins (e.g. {@code ["https://myapp.com"]}) to 
restrict access.
+         */
+        public List<String> allowedOrigins = new 
ArrayList<>(Collections.singletonList("*"));
+    }
+
     /**
      * Settings to configure SSL support.
      */
diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/channel/HttpChannelizer.java
 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/channel/HttpChannelizer.java
index e5643f5154..c25bc3bfcc 100644
--- 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/channel/HttpChannelizer.java
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/channel/HttpChannelizer.java
@@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.server.channel;
 
 import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.handler.codec.http.HttpServerKeepAliveHandler;
+import io.netty.handler.codec.http.cors.CorsConfig;
 import io.netty.handler.codec.http.cors.CorsConfigBuilder;
 import io.netty.handler.codec.http.cors.CorsHandler;
 import org.apache.tinkerpop.gremlin.server.AbstractChannelizer;
@@ -45,6 +46,8 @@ import 
org.apache.tinkerpop.gremlin.server.util.ServerGremlinExecutor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.List;
+
 /**
  * Constructs a {@link Channelizer} that exposes an HTTP endpoint in Gremlin 
Server.
  *
@@ -78,7 +81,7 @@ public class HttpChannelizer extends AbstractChannelizer {
 
         pipeline.addLast("http-requestid-handler", httpRequestIdHandler);
         pipeline.addLast("http-keepalive-handler", new 
HttpServerKeepAliveHandler());
-        pipeline.addLast("http-cors-handler", new 
CorsHandler(CorsConfigBuilder.forAnyOrigin().build()));
+        pipeline.addLast("http-cors-handler", new 
CorsHandler(buildCorsConfig()));
 
         final HttpObjectAggregator aggregator = new 
HttpObjectAggregator(settings.maxRequestContentLength);
         
aggregator.setMaxCumulationBufferComponents(settings.maxAccumulationBufferComponents);
@@ -119,4 +122,20 @@ public class HttpChannelizer extends AbstractChannelizer {
             return createAuthenticationHandler(settings);
         }
     }
+
+    private CorsConfig buildCorsConfig() {
+        final List<String> allowedOrigins = settings.cors.allowedOrigins;
+        final boolean isWildcard = allowedOrigins.size() == 1 && 
allowedOrigins.get(0).equals("*");
+
+        if (isWildcard) {
+            final boolean hasRealAuth = 
!AllowAllAuthenticator.class.getName().equals(settings.authentication.authenticator);
+            if (hasRealAuth) {
+                logger.warn("CORS is configured to allow any origin while 
authentication is enabled. " +
+                        "Consider setting cors.allowedOrigins to specific 
origins for browser-facing deployments.");
+            }
+            return CorsConfigBuilder.forAnyOrigin().build();
+        }
+
+        return CorsConfigBuilder.forOrigins(allowedOrigins.toArray(new 
String[0])).build();
+    }
 }
diff --git 
a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java
 
b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java
index 7d724975b9..de5dea5a66 100644
--- 
a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java
+++ 
b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpIntegrateTest.java
@@ -142,6 +142,10 @@ public class GremlinServerHttpIntegrateTest extends 
AbstractGremlinServerIntegra
                 settings.evaluationTimeout = 5000;
                 settings.gremlinPool = 1;
                 break;
+            case "shouldRespectCorsAllowedOrigins":
+            case "shouldRejectDisallowedCorsOrigin":
+                settings.cors.allowedOrigins = 
java.util.Arrays.asList("https://allowed.gremlinexample.com";);
+                break;
             case "should200OnPOSTWithChunkedResponse":
             case "shouldHandleErrorsInFirstChunkPOSTWithChunkedResponse":
             case 
"shouldHandleErrorsInFirstChunkPOSTWithChunkedResponseUsingTextPlain":
@@ -1345,6 +1349,36 @@ public class GremlinServerHttpIntegrateTest extends 
AbstractGremlinServerIntegra
         }
     }
 
+    @Test
+    public void shouldRespectCorsAllowedOrigins() throws Exception {
+        final CloseableHttpClient httpclient = HttpClients.createDefault();
+        final HttpPost httppost = new 
HttpPost(TestClientFactory.createURLString());
+        httppost.addHeader("Content-Type", "application/json");
+        httppost.addHeader("Origin", "https://allowed.gremlinexample.com";);
+        httppost.setEntity(new StringEntity("{\"gremlin\": \"g.inject(1)\"}", 
Consts.UTF_8));
+
+        try (final CloseableHttpResponse response = 
httpclient.execute(httppost)) {
+            assertEquals(200, response.getStatusLine().getStatusCode());
+            final Header corsHeader = 
response.getFirstHeader("Access-Control-Allow-Origin");
+            assertNotNull(corsHeader);
+            assertEquals("https://allowed.gremlinexample.com";, 
corsHeader.getValue());
+        }
+    }
+
+    @Test
+    public void shouldRejectDisallowedCorsOrigin() throws Exception {
+        final CloseableHttpClient httpclient = HttpClients.createDefault();
+        final HttpPost httppost = new 
HttpPost(TestClientFactory.createURLString());
+        httppost.addHeader("Content-Type", "application/json");
+        httppost.addHeader("Origin", "https://notallowed.gremlinexample.com";);
+        httppost.setEntity(new StringEntity("{\"gremlin\": \"g.inject(1)\"}", 
Consts.UTF_8));
+
+        try (final CloseableHttpResponse response = 
httpclient.execute(httppost)) {
+            final Header corsHeader = 
response.getFirstHeader("Access-Control-Allow-Origin");
+            assertNull(corsHeader);
+        }
+    }
+
     @Test
     public void shouldNotContainStatusMessageOrExceptionWith200() throws 
Exception {
         final GraphSONMessageSerializerV4 serializer = new 
GraphSONMessageSerializerV4();

Reply via email to