From b4e6a9ef9da8a361ca1ffeee48db00d6ee1b0f84 Mon Sep 17 00:00:00 2001 From: "sasjs-dev[bot]" Date: Thu, 15 Oct 2026 19:16:45 +0000 Subject: [PATCH 1/2] fix(formatter): read the platform defensively, and guard the platform helpers `newLine()`, `isWindows()` and `isLinux()` read `process.platform` directly. That is fine in Node and a ReferenceError in a browser, and the browser case is not hypothetical: `newLine` is part of the formatter, which runs in browsers to build request bodies. Found downstream. The adapter's browser build has been carrying a Node stdlib polyfill tree - shimmed crypto, stream, os, path and buffer - whose only job in practice was to answer reads like this one. Guarding them here lets that tree go: with these three functions fixed, the adapter's browser bundle needs a twenty-line process stub rather than thousands of lines of bundled Node internals. - `newLine()` returns the unix ending where there is no process. - `isWindows()` and `isLinux()` answer false there. Both are exported from the package root and callable from browser code. - The FIXME about `isWindows` is resolved by keeping the formatter self-contained rather than importing it: that module imports `path` and is loaded from the root, which is what pulled Logger in and broke cli.spec.ts. Tests, because the failure mode is invisible in Node: each spec now deletes the process global and asserts the fallback, and restores it afterwards. The existing specs mutated `process.platform` without restoring, so they are pinned down the same way. Verified by reintroducing the bug: the new case fails with `ReferenceError: process is not defined`, and passes with the guard in place. --- src/formatter/regexp.spec.ts | 15 +++++++++++++++ src/formatter/regexp.ts | 18 ++++++++++++++---- src/utils/utils.spec.ts | 34 ++++++++++++++++++++++++++++++++++ src/utils/utils.ts | 11 +++++++++-- 4 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 src/utils/utils.spec.ts diff --git a/src/formatter/regexp.spec.ts b/src/formatter/regexp.spec.ts index 0c14571..b72d52b 100644 --- a/src/formatter/regexp.spec.ts +++ b/src/formatter/regexp.spec.ts @@ -1,6 +1,12 @@ import { newLine } from './' describe('newLine', () => { + const realProcess = global.process + + afterEach(() => { + global.process = realProcess + }) + it('should return correct new line regexp entry for windows', () => { Object.defineProperty(process, 'platform', { value: 'win32' @@ -16,4 +22,13 @@ describe('newLine', () => { expect(newLine()).toEqual('\\n') }) + + it('should return the unix new line when there is no process', () => { + // The formatter runs in browsers too, where process does not exist. This + // is the case that used to throw a ReferenceError. + // @ts-expect-error - simulating a browser, which has no process object + delete global.process + + expect(newLine()).toEqual('\\n') + }) }) diff --git a/src/formatter/regexp.ts b/src/formatter/regexp.ts index 6fc362e..8e839d4 100644 --- a/src/formatter/regexp.ts +++ b/src/formatter/regexp.ts @@ -1,5 +1,15 @@ -// FIXME: if isWindows is used, it breaks cli.spec.ts because Logger is undefined -// import { isWindows } from '../' -// export const newLine = () => (isWindows() ? '\\r\\n' : '\\n') +// Kept self-contained rather than importing isWindows from '../': that module +// imports path and is loaded from the package root, which pulls Logger into +// this module and breaks cli.spec.ts with an undefined Logger. -export const newLine = () => (process.platform === 'win32' ? '\\r\\n' : '\\n') +/** + * The line ending to join formatted rows with. + * + * process may not exist - the formatter runs in browsers, to build request + * bodies - so the platform is read defensively and a browser gets the unix + * ending. + */ +export const newLine = () => + typeof process !== 'undefined' && process.platform === 'win32' + ? '\\r\\n' + : '\\n' diff --git a/src/utils/utils.spec.ts b/src/utils/utils.spec.ts new file mode 100644 index 0000000..9100dc9 --- /dev/null +++ b/src/utils/utils.spec.ts @@ -0,0 +1,34 @@ +import { isLinux, isWindows } from './utils' + +describe('platform helpers', () => { + const realProcess = global.process + + afterEach(() => { + global.process = realProcess + }) + + it('reports windows from process.platform', () => { + Object.defineProperty(process, 'platform', { value: 'win32' }) + + expect(isWindows()).toBe(true) + expect(isLinux()).toBe(false) + }) + + it('reports linux from process.platform', () => { + Object.defineProperty(process, 'platform', { value: 'linux' }) + + expect(isWindows()).toBe(false) + expect(isLinux()).toBe(true) + }) + + it('answers false for both when there is no process', () => { + // These helpers are exported from the package root and called from browser + // code, where process does not exist. This is the case that used to throw + // a ReferenceError. + // @ts-expect-error - simulating a browser, which has no process object + delete global.process + + expect(isWindows()).toBe(false) + expect(isLinux()).toBe(false) + }) +}) diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 2ad1924..aeed08f 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -19,9 +19,16 @@ export const uuidv4 = () => export const uniqArray = (data: any[]) => Array.from(new Set(data)) -export const isWindows = () => process.platform === 'win32' +/** + * Whether the host is Windows. process may not exist - these helpers are + * exported from the package root and called from browser code - so the + * platform is read defensively and a browser answers false. + */ +export const isWindows = () => + typeof process !== 'undefined' && process.platform === 'win32' -export const isLinux = () => process.platform === 'linux' +export const isLinux = () => + typeof process !== 'undefined' && process.platform === 'linux' /** * replace single backslash with double backslash From c52af19a4964c27ced33807e42a90551225b9f59 Mon Sep 17 00:00:00 2001 From: "sasjs-dev[bot]" Date: Thu, 15 Oct 2026 19:20:20 +0000 Subject: [PATCH 2/2] fix(ci): bump handlebars in the release toolchain past the injection advisories The toolchain audit fails on three handlebars advisories, all fixed in 4.7.10: critical GHSA-8r5x-fm3f-whwj JavaScript Injection via AST Type Confusion critical GHSA-p8wg-vrv2-v86f JavaScript Injection via Own Property Check Bypass moderate GHSA-xw65-4hp5-5hc7 JavaScript Injection via Unsafe Inline Embedding Not this PR's subject matter, but CI cannot be green without it, and the advisories are new: main's last run of this workflow passed before they were published. `release/audit.mjs` says fix it or exempt it with a reason, and 4.7.10 is available, so this fixes it rather than exempting a critical injection. release/package-lock.json: handlebars 4.7.9 -> 4.7.10. Verified: npm ci --prefix release --ignore-scripts, then node release/audit.mjs - "no advisory in the release toolchain outside the documented exemptions (13 exempt)", exit 0. --- release/package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/release/package-lock.json b/release/package-lock.json index 4dbcb3f..46d3963 100644 --- a/release/package-lock.json +++ b/release/package-lock.json @@ -1961,13 +1961,13 @@ "license": "ISC" }, "node_modules/handlebars": { - "version": "4.7.9", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", - "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "version": "4.7.10", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.10.tgz", + "integrity": "sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==", "dev": true, "license": "MIT", "dependencies": { - "minimist": "^1.2.5", + "minimist": "^1.2.8", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0"