Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,15 @@ test-complete-app-mlDeploy/.gradle

# Compiled TypeScript test files
test-typescript/*.js

# AI generated documents
documents

# Generated proxy stubs (created by gulp setupProxyTests / generateProxyTests)
# Files in the generated directory are fully generated
test-basic-proxy/lib/generated/
# the proxy stubs (non-Test.js) are generated; the *Test.js files are hand-written and must be kept
test-basic-proxy/lib/positive/*.js
!test-basic-proxy/lib/positive/*Test.js
test-basic-proxy/lib/negative/*.js
!test-basic-proxy/lib/negative/*Test.js
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# CHANGELOG

## 4.2.0

#### Security Fix

- Session IDs generated by `SessionState` now use `crypto.randomBytes()` instead of `Math.random()`, replacing a
non-cryptographic PRNG (xorshift128+) with a cryptographically secure source of randomness (CWE-338).
The session ID is now a 16-character hex string (8 random bytes), matching the format MarkLogic uses for its own
server-generated session IDs. The return type of `SessionState.sessionId()` changes from `number` to `string`.
Callers that only use the session ID for logging or cookie headers are unaffected.

## 4.1.0

- Added TypeScript typings for core client/document/connection APIs, with better autocomplete and compile-time checks to catch mistakes earlier.
Expand Down
9 changes: 5 additions & 4 deletions lib/session-state.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/*
* Copyright (c) 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
* Copyright (c) 2015-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
*/
'use strict';

const crypto = require('crypto');

/**
* Identifies a server state for sharing across multiple calls to server endpoints.
*
Expand All @@ -14,13 +16,12 @@ class SessionState {
* Constructs an identifier for session state on the server.
*/
constructor() {
// TODO: use BigInt?
this._sessionId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
this._sessionId = crypto.randomBytes(8).toString('hex');
}

/**
* Provides the identifier used for the server state (for instance, for use in logging).
* @returns {number} the session identifier
* @returns {string} the session identifier
*/
sessionId() {
return this._sessionId;
Expand Down
38 changes: 38 additions & 0 deletions test-basic/session-state-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2015-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
*/
'use strict';

const assert = require('assert');
const SessionState = require('../lib/session-state.js');

describe('SessionState', function () {
it('sessionId() returns a non-empty string', function () {
const session = new SessionState();
const id = session.sessionId();
assert.strictEqual(typeof id, 'string');
assert.ok(id.length > 0, 'sessionId should not be empty');
});

it('sessionId() returns a 16-character hex string (8 random bytes)', function () {
const session = new SessionState();
const id = session.sessionId();
assert.match(id, /^[0-9a-f]{16}$/, 'sessionId should be a 16-character lowercase hex string');
});

it('sessionId() returns the same value on repeated calls to the same instance', function () {
const session = new SessionState();
const id1 = session.sessionId();
const id2 = session.sessionId();
const id3 = session.sessionId();
assert.strictEqual(id1, id2, 'sessionId should be stable across calls');
assert.strictEqual(id2, id3, 'sessionId should be stable across calls');
});

it('two instances created in rapid succession produce different session IDs', function () {
const session1 = new SessionState();
const session2 = new SessionState();
assert.notStrictEqual(session1.sessionId(), session2.sessionId(),
'session IDs from two distinct instances should not be equal');
});
});
Loading