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

xiazcy pushed a commit to branch TINKERPOP-3276
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 88414a0917ceb9d64ff381bc53cf7839433a1ec6
Author: Yang Xia <[email protected]>
AuthorDate: Wed Jul 22 13:28:56 2026 -0700

    fix(gremlin-javascript): Reject out-of-range OffsetDateTime values
    
    Validate the constructed Date in the GraphBinary and GraphSON
    OffsetDateTime readers and throw when it falls outside the range
    representable by a JavaScript Date, instead of silently returning an
    invalid Date.
    
    https://issues.apache.org/jira/browse/TINKERPOP-3276
    
    Assisted-by: Kiro:Claude-Opus-4.8
---
 CHANGELOG.asciidoc                                 |   1 +
 .../binary/internals/OffsetDateTimeSerializer.js   |   9 ++
 .../lib/structure/io/type-serializers.js           |  10 +-
 .../graphbinary/OffsetDateTimeSerializer-test.js   | 118 +++++++++++++++++++++
 .../gremlin-javascript/test/unit/graphson-test.js  |  16 +++
 5 files changed, 153 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index a5ecd33da3..41873227ad 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -32,6 +32,7 @@ This release also includes changes from prior 3.7.x releases.
 * Bumped Hadoop to 3.4.3 (and Kerby to 2.0.3) to enable `hadoop-gremlin` to 
build and run on Java 25.
 * Add missing `Configuring` interface to `GraphStepPlaceholder` and 
`VertexStepPlaceholder`
 * Fixed bug in `group()` value traversal where keys were retained with stale 
barrier state instead of being filtered when steps following a `Barrier` in the 
second `by()` produced no output (e.g. `by(values("age").fold().unfold())` or 
`by(__.out().fold().count(local).is(P.gt(0)))` for vertices with no out-edges).
+* Fixed bug in `gremlin-javascript` GraphBinary and GraphSON deserialization 
where `OffsetDateTime` values outside the JavaScript `Date` range were silently 
returned as invalid `Date` objects instead of failing deserialization.
 
 [[release-3-8-1]]
 === TinkerPop 3.8.1 (Release Date: April 1, 2026)
diff --git 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/OffsetDateTimeSerializer.js
 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/OffsetDateTimeSerializer.js
index 30045ffd0f..0a30855d29 100644
--- 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/OffsetDateTimeSerializer.js
+++ 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/OffsetDateTimeSerializer.js
@@ -143,6 +143,15 @@ module.exports = class OffsetDateTimeSerializer {
       // use UTC time calculated with offset above
       const v = new Date(Date.UTC(year, month, date, h, m, s, ms));
 
+      // The GraphBinary DateTime/OffsetDateTime wire format can carry values 
(e.g. extreme years near
+      // +/-999999999) that fall outside the range representable by a 
JavaScript Date. In those cases
+      // Date.UTC(...) returns NaN and new Date(NaN) yields an invalid Date 
without throwing. Reject such
+      // values here so unsupported boundary date-times fail deserialization 
instead of silently producing
+      // an unusable Date instance.
+      if (Number.isNaN(v.getTime())) {
+        throw new Error('{value} is outside the range supported by JavaScript 
Date');
+      }
+
       return { v, len };
     } catch (err) {
       throw this.ioc.utils.des_error({ serializer: this, args: arguments, 
cursor, err });
diff --git 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/type-serializers.js
 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/type-serializers.js
index 7f5a392fd6..a3a683660c 100644
--- 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/type-serializers.js
+++ 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/type-serializers.js
@@ -103,7 +103,15 @@ class OffsetDateTimeSerializer extends TypeSerializer {
   }
 
   deserialize(obj) {
-    return new Date(obj[valueKey]);
+    // An OffsetDateTime value can carry a date-time (e.g. extreme years) that 
falls outside the range
+    // representable by a JavaScript Date. In those cases new Date(...) yields 
an invalid Date without
+    // throwing. Reject such values so unsupported boundary date-times fail 
deserialization instead of
+    // silently producing an unusable Date instance. Mirrors the GraphBinary 
OffsetDateTime reader.
+    const value = new Date(obj[valueKey]);
+    if (Number.isNaN(value.getTime())) {
+      throw new Error('OffsetDateTime value is outside the range supported by 
JavaScript Date');
+    }
+    return value;
   }
 
   canBeUsedFor(value) {
diff --git 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/OffsetDateTimeSerializer-test.js
 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/OffsetDateTimeSerializer-test.js
new file mode 100644
index 0000000000..f854fad351
--- /dev/null
+++ 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/OffsetDateTimeSerializer-test.js
@@ -0,0 +1,118 @@
+/*
+ *  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.
+ */
+
+'use strict';
+
+const utils = require('./utils');
+const assert = require('assert');
+const ioc = require('../../../lib/structure/io/binary/GraphBinary');
+
+const { from, concat } = Buffer;
+
+const ID = 0x88; // OFFSETDATETIME
+
+describe('GraphBinary.OffsetDateTimeSerializer', () => {
+
+  const type_code = from([ID]);
+  const value_flag = from([0x00]);
+
+  const serializer = ioc.serializers[ID];
+
+  const cases = [
+    { v: undefined, fq: 1, b: [ID, 0x01], av: null },
+    { v: null,      fq: 1, b: [ID, 0x01] },
+
+    // year=2022(0x07e6), month=5, day=1, ns=0x000042c277bd8e00, offset=0
+    { v: new Date(1651436603000),
+      b: [0x00,0x00,0x07,0xe6, 0x05, 0x01, 
0x00,0x00,0x42,0xc2,0x77,0xbd,0x8e,0x00, 0x00,0x00,0x00,0x00] },
+
+    { des: 1, err: /buffer is missing/,         fq: 1, b: undefined },
+    { des: 1, err: /buffer is empty/,           fq: 1, b: [] },
+    { des: 1, err: /unexpected {type_code}/,    fq: 1, b: [ID - 1] },
+    { des: 1, err: /{value_flag} is missing/,   fq: 1, b: [ID] },
+    { des: 1, err: /unexpected {value_flag}/,   fq: 1, b: [ID, 0x02] },
+    { des: 1, err: /unexpected {value} length/, fq: 1, b: [ID, 0x00] },
+
+    // Boundary values that fall outside the JavaScript Date range 
(year=999999999=0x3B9AC9FF)
+    // must be rejected rather than silently deserialized into an invalid 
Date. See TINKERPOP-3276.
+    { des: 1, err: /outside the range supported by JavaScript Date/, fq: 0,
+      b: [0x3B,0x9A,0xC9,0xFF, 0x01, 0x01, 
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00] },
+    // year=-999999999=0xC4653601
+    { des: 1, err: /outside the range supported by JavaScript Date/, fq: 0,
+      b: [0xC4,0x65,0x36,0x01, 0x01, 0x01, 
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00] },
+  ];
+
+  describe('#serialize', () =>
+    cases
+    .filter(({ des }) => !des)
+    .forEach(({ v, fq, b }, i) => it(utils.ser_title({ i, v }), () => {
+      b = from(b);
+
+      if (fq !== undefined) {
+        assert.deepEqual(serializer.serialize(v, fq), b);
+        return;
+      }
+
+      assert.deepEqual(serializer.serialize(v, true),  concat([type_code, 
value_flag, b]));
+      assert.deepEqual(serializer.serialize(v, false), concat([b]));
+    }))
+  );
+
+  describe('#deserialize', () =>
+    cases.forEach(({ v, fq, b, av, err }, i) => it(utils.des_title({ i, b }), 
() => {
+      if (Array.isArray(b))
+        b = from(b);
+
+      if (err !== undefined) {
+        if (fq !== undefined)
+          assert.throws(() => serializer.deserialize(b, fq), { message: err });
+        else {
+          assert.throws(() => serializer.deserialize(concat([type_code, 
value_flag, b]), true),  { message: err });
+          assert.throws(() => serializer.deserialize(concat([b]), false), { 
message: err });
+        }
+        return;
+      }
+
+      if (av !== undefined)
+        v = av;
+      const len = b.length;
+
+      if (fq !== undefined) {
+        assert.deepStrictEqual(serializer.deserialize(b, fq), { v, len });
+        return;
+      }
+
+      assert.deepStrictEqual(serializer.deserialize(concat([type_code, 
value_flag, b]), true),  { v, len: len + 2 });
+      assert.deepStrictEqual(serializer.deserialize(concat([b]), false), { v, 
len: len + 0 });
+    }))
+  );
+
+  describe('#canBeUsedFor', () =>
+    [
+      { v: null,       e: false },
+      { v: undefined,  e: false },
+      { v: {},         e: false },
+      { v: [],         e: false },
+      { v: new Date(), e: true  },
+    ].forEach(({ v, e }, i) => it(utils.cbuf_title({ i, v }), () =>
+      assert.strictEqual(serializer.canBeUsedFor(v), e)
+    ))
+  );
+
+});
diff --git 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphson-test.js
 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphson-test.js
index 7769306704..f968c35230 100644
--- 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphson-test.js
+++ 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphson-test.js
@@ -90,6 +90,22 @@ describe('GraphSONReader', function () {
     const result = reader.read(obj);
     assert.ok(result instanceof Date);
   });
+  it('should parse OffsetDateTime', function() {
+    const obj = { "@type" : "gx:OffsetDateTime", "@value" : 
"2016-12-14T21:14:36.295Z" };
+    const reader = new GraphSONReader();
+    const result = reader.read(obj);
+    assert.ok(result instanceof Date);
+    assert.strictEqual(result.getTime(), 1481750076295);
+  });
+  it('should reject OffsetDateTime outside the JavaScript Date range', 
function() {
+    const reader = new GraphSONReader();
+    [
+      { "@type" : "gx:OffsetDateTime", "@value" : "+999999-01-01T00:00:00Z" },
+      { "@type" : "gx:OffsetDateTime", "@value" : "-999999-01-01T00:00:00Z" },
+    ].forEach(function (obj) {
+      assert.throws(() => reader.read(obj), /outside the range supported by 
JavaScript Date/);
+    });
+  });
   it('should parse vertices from GraphSON', function () {
     const obj = {
       
"@type":"g:Vertex","@value":{"id":{"@type":"g:Int32","@value":1},"label":"person",

Reply via email to