From ecc60289b6cf60c8f1f9913959ebba31fa8b57d4 Mon Sep 17 00:00:00 2001 From: Thiyagu K Date: Tue, 9 Jun 2026 13:54:30 +0000 Subject: [PATCH 1/6] fix(deps): update yargs to v17.7.2 for Node 18 and 26 compatibility --- handwritten/storage/package.json | 6 ++--- handwritten/storage/scripts/preload-yargs.cjs | 27 +++++++++++++++++++ handwritten/storage/src/file.ts | 11 ++++++++ handwritten/storage/test/file.ts | 14 ++++++++-- 4 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 handwritten/storage/scripts/preload-yargs.cjs diff --git a/handwritten/storage/package.json b/handwritten/storage/package.json index 7e68d03dc8ac..3ea3b0fb2644 100644 --- a/handwritten/storage/package.json +++ b/handwritten/storage/package.json @@ -71,7 +71,7 @@ "samples-test": "npm link && cd samples/ && npm link ../ && npm test && cd ../", "system-test:esm": "mocha build/esm/system-test --timeout 600000 --exit", "system-test": "mocha build/cjs/system-test --timeout 600000 --exit", - "test": "cross-env NODE_OPTIONS='--no-deprecation' c8 mocha build/cjs/test" + "test": "cross-env NODE_OPTIONS=\"--require ./scripts/preload-yargs.cjs --no-deprecation\" c8 mocha build/cjs/test" }, "dependencies": { "@google-cloud/paginator": "^5.0.0", @@ -106,7 +106,7 @@ "@types/request": "^2.48.4", "@types/sinon": "^17.0.0", "@types/tmp": "0.2.6", - "@types/yargs": "^17.0.10", + "@types/yargs": "^17.0.35", "c8": "^9.0.0", "form-data": "^4.0.4", "gapic-tools": "^0.4.0", @@ -125,7 +125,7 @@ "path-to-regexp": "6.3.0", "tmp": "^0.2.0", "typescript": "^5.1.6", - "yargs": "^17.3.1", + "yargs": "^17.7.2", "cross-env": "^7.0.3" }, "homepage": "https://github.com/googleapis/google-cloud-node/tree/main/handwritten/storage" diff --git a/handwritten/storage/scripts/preload-yargs.cjs b/handwritten/storage/scripts/preload-yargs.cjs new file mode 100644 index 000000000000..1b52fa824dec --- /dev/null +++ b/handwritten/storage/scripts/preload-yargs.cjs @@ -0,0 +1,27 @@ +const Module = require('module'); +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +const originalResolveFilename = Module._resolveFilename; + +Module._resolveFilename = function(request, parent, isMain, options) { + if (request === 'yargs/yargs') { + const resolved = originalResolveFilename.apply(this, arguments); + // Create a unique shim file in tmpdir based on the exact path to avoid collisions + const safeHash = Buffer.from(resolved).toString('base64').replace(/[^a-zA-Z0-9]/g, ''); + const shimPath = path.join(os.tmpdir(), `yargs-shim-${safeHash}.cjs`); + + if (!fs.existsSync(shimPath)) { + const content = fs.readFileSync(resolved, 'utf8'); + // Replace `./build/index.cjs` with the absolute path + const buildIndexPath = path.join(path.dirname(resolved), 'build', 'index.cjs'); + // We must replace ALL relative requires. Luckily yargs/yargs only requires `./build/index.cjs` + const newContent = content.replace(/require\(['"]\.\/build\/index\.cjs['"]\)/g, `require(${JSON.stringify(buildIndexPath)})`); + fs.writeFileSync(shimPath, newContent); + } + + return shimPath; + } + return originalResolveFilename.apply(this, arguments); +}; \ No newline at end of file diff --git a/handwritten/storage/src/file.ts b/handwritten/storage/src/file.ts index 1e62634e4c64..7e20292c4908 100644 --- a/handwritten/storage/src/file.ts +++ b/handwritten/storage/src/file.ts @@ -2187,6 +2187,17 @@ class File extends ServiceObject { // remove temporary noop listener as we now create a pipeline that handles the errors emitStream.removeListener('error', noop); + if (fileWriteStream.destroyed) { + fileWriteStream.once('error', (err: Error) => { + pipelineCallback(err); + }); + // Call pipelineCallback immediately if there's no error to wait for, + // though duplexify might emit error on next tick. + // Also cleanup emitStream since pipeline won't do it. + emitStream.destroy(); + return; + } + pipeline( emitStream, ...(transformStreams as [Transform]), diff --git a/handwritten/storage/test/file.ts b/handwritten/storage/test/file.ts index 311d5749582d..6cf3d7e69bcd 100644 --- a/handwritten/storage/test/file.ts +++ b/handwritten/storage/test/file.ts @@ -118,7 +118,17 @@ const fakePromisify = { }; const fsCached = fs; -const fakeFs = {...fsCached}; +const safeFs: any = {}; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const fsOriginal = require('fs'); +for (const key of Object.keys(fsOriginal)) { + try { + safeFs[key] = fsOriginal[key]; + } catch (e) { + // Ignore deprecated getters + } +} +const fakeFs = {...safeFs} as typeof fs; const zlibCached = zlib; let createGunzipOverride: Function | null; @@ -223,7 +233,7 @@ describe('File', () => { }); beforeEach(() => { - Object.assign(fakeFs, fsCached); + Object.assign(fakeFs, safeFs); Object.assign(fakeOs, osCached); // eslint-disable-next-line @typescript-eslint/no-explicit-any FakeServiceObject.prototype.request = util.noop as any; From 9296697b585b954eeffa8c3be6aadc1876da1491 Mon Sep 17 00:00:00 2001 From: Thiyagu K Date: Wed, 10 Jun 2026 12:22:17 +0000 Subject: [PATCH 2/6] fix: relocate yargs shim generation to local directory and handle stream error callbacks in File.ts --- handwritten/storage/scripts/preload-yargs.cjs | 8 ++++--- handwritten/storage/src/file.ts | 24 ++++++++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/handwritten/storage/scripts/preload-yargs.cjs b/handwritten/storage/scripts/preload-yargs.cjs index 1b52fa824dec..4c38d0a2fe3d 100644 --- a/handwritten/storage/scripts/preload-yargs.cjs +++ b/handwritten/storage/scripts/preload-yargs.cjs @@ -1,16 +1,18 @@ const Module = require('module'); const fs = require('fs'); const path = require('path'); -const os = require('os'); const originalResolveFilename = Module._resolveFilename; Module._resolveFilename = function(request, parent, isMain, options) { if (request === 'yargs/yargs') { const resolved = originalResolveFilename.apply(this, arguments); - // Create a unique shim file in tmpdir based on the exact path to avoid collisions + if (resolved.endsWith('.mjs')) { + return resolved; + } + // Create a unique shim file in the local scripts directory to avoid shared temp directory vulnerabilities const safeHash = Buffer.from(resolved).toString('base64').replace(/[^a-zA-Z0-9]/g, ''); - const shimPath = path.join(os.tmpdir(), `yargs-shim-${safeHash}.cjs`); + const shimPath = path.join(__dirname, `yargs-shim-${safeHash}.cjs`); if (!fs.existsSync(shimPath)) { const content = fs.readFileSync(resolved, 'utf8'); diff --git a/handwritten/storage/src/file.ts b/handwritten/storage/src/file.ts index 7e20292c4908..27765d935a99 100644 --- a/handwritten/storage/src/file.ts +++ b/handwritten/storage/src/file.ts @@ -2188,13 +2188,25 @@ class File extends ServiceObject { emitStream.removeListener('error', noop); if (fileWriteStream.destroyed) { - fileWriteStream.once('error', (err: Error) => { - pipelineCallback(err); - }); - // Call pipelineCallback immediately if there's no error to wait for, - // though duplexify might emit error on next tick. - // Also cleanup emitStream since pipeline won't do it. + let callbackCalled = false; + const onError = (err: Error) => { + if (!callbackCalled) { + callbackCalled = true; + pipelineCallback(err); + } + }; + fileWriteStream.once('error', onError); emitStream.destroy(); + + process.nextTick(() => { + fileWriteStream.removeListener('error', onError); + if (!callbackCalled) { + callbackCalled = true; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const err = (fileWriteStream as any).errored || new Error('Write stream destroyed'); + pipelineCallback(err); + } + }); return; } From fb70baded106358a073ae716e8bdc282f8effba9 Mon Sep 17 00:00:00 2001 From: Thiyagu K Date: Wed, 10 Jun 2026 15:52:46 +0000 Subject: [PATCH 3/6] fix: update safeFs reference in tests and add yargs shim generation with cleanup on process exit --- handwritten/storage/scripts/preload-yargs.cjs | 10 ++++++++++ handwritten/storage/test/file.ts | 6 ++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/handwritten/storage/scripts/preload-yargs.cjs b/handwritten/storage/scripts/preload-yargs.cjs index 4c38d0a2fe3d..9b5d56a1eef7 100644 --- a/handwritten/storage/scripts/preload-yargs.cjs +++ b/handwritten/storage/scripts/preload-yargs.cjs @@ -22,6 +22,16 @@ Module._resolveFilename = function(request, parent, isMain, options) { const newContent = content.replace(/require\(['"]\.\/build\/index\.cjs['"]\)/g, `require(${JSON.stringify(buildIndexPath)})`); fs.writeFileSync(shimPath, newContent); } + + global.__yargsShimCleanups = global.__yargsShimCleanups || new Set(); + if (!global.__yargsShimCleanups.has(shimPath)) { + global.__yargsShimCleanups.add(shimPath); + process.on('exit', () => { + try { + fs.unlinkSync(shimPath); + } catch (e) {} + }); + } return shimPath; } diff --git a/handwritten/storage/test/file.ts b/handwritten/storage/test/file.ts index 6cf3d7e69bcd..593ed591f126 100644 --- a/handwritten/storage/test/file.ts +++ b/handwritten/storage/test/file.ts @@ -119,11 +119,9 @@ const fakePromisify = { const fsCached = fs; const safeFs: any = {}; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const fsOriginal = require('fs'); -for (const key of Object.keys(fsOriginal)) { +for (const key of Object.keys(fsCached)) { try { - safeFs[key] = fsOriginal[key]; + safeFs[key] = (fsCached as any)[key]; } catch (e) { // Ignore deprecated getters } From 2e95d6820625bb0b6573a8c03e92d42e75d9f606 Mon Sep 17 00:00:00 2001 From: Thiyagu K Date: Fri, 12 Jun 2026 12:35:46 +0000 Subject: [PATCH 4/6] fix: update fs mocking to use property descriptors for safer member access --- handwritten/storage/test/file.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/handwritten/storage/test/file.ts b/handwritten/storage/test/file.ts index 593ed591f126..26823995b907 100644 --- a/handwritten/storage/test/file.ts +++ b/handwritten/storage/test/file.ts @@ -119,11 +119,11 @@ const fakePromisify = { const fsCached = fs; const safeFs: any = {}; -for (const key of Object.keys(fsCached)) { - try { - safeFs[key] = (fsCached as any)[key]; - } catch (e) { - // Ignore deprecated getters +const descriptors = Object.getOwnPropertyDescriptors(fsCached); +for (const key of Object.keys(descriptors)) { + const desc = descriptors[key]; + if (desc && !desc.get) { + Object.defineProperty(safeFs, key, desc); } } const fakeFs = {...safeFs} as typeof fs; From 8dbd83bc2868b494fd530ba0c32df1b08bf1c5fa Mon Sep 17 00:00:00 2001 From: Thiyagu K Date: Fri, 12 Jun 2026 12:37:00 +0000 Subject: [PATCH 5/6] refactor: simplify yargs resolution by targeting the build index directly instead of using shim files --- handwritten/storage/scripts/preload-yargs.cjs | 27 ++----------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/handwritten/storage/scripts/preload-yargs.cjs b/handwritten/storage/scripts/preload-yargs.cjs index 9b5d56a1eef7..500fddfd579f 100644 --- a/handwritten/storage/scripts/preload-yargs.cjs +++ b/handwritten/storage/scripts/preload-yargs.cjs @@ -1,5 +1,4 @@ const Module = require('module'); -const fs = require('fs'); const path = require('path'); const originalResolveFilename = Module._resolveFilename; @@ -10,30 +9,8 @@ Module._resolveFilename = function(request, parent, isMain, options) { if (resolved.endsWith('.mjs')) { return resolved; } - // Create a unique shim file in the local scripts directory to avoid shared temp directory vulnerabilities - const safeHash = Buffer.from(resolved).toString('base64').replace(/[^a-zA-Z0-9]/g, ''); - const shimPath = path.join(__dirname, `yargs-shim-${safeHash}.cjs`); - - if (!fs.existsSync(shimPath)) { - const content = fs.readFileSync(resolved, 'utf8'); - // Replace `./build/index.cjs` with the absolute path - const buildIndexPath = path.join(path.dirname(resolved), 'build', 'index.cjs'); - // We must replace ALL relative requires. Luckily yargs/yargs only requires `./build/index.cjs` - const newContent = content.replace(/require\(['"]\.\/build\/index\.cjs['"]\)/g, `require(${JSON.stringify(buildIndexPath)})`); - fs.writeFileSync(shimPath, newContent); - } - - global.__yargsShimCleanups = global.__yargsShimCleanups || new Set(); - if (!global.__yargsShimCleanups.has(shimPath)) { - global.__yargsShimCleanups.add(shimPath); - process.on('exit', () => { - try { - fs.unlinkSync(shimPath); - } catch (e) {} - }); - } - - return shimPath; + // Directly resolve to the CommonJS build file to avoid disk I/O and cleanup issues + return path.join(path.dirname(resolved), 'build', 'index.cjs'); } return originalResolveFilename.apply(this, arguments); }; \ No newline at end of file From 0562a6e94ce11918a415876b9c17740ee7605549 Mon Sep 17 00:00:00 2001 From: Thiyagu K Date: Fri, 12 Jun 2026 13:12:12 +0000 Subject: [PATCH 6/6] refactor: generate unique local shim files for yargs resolution to improve security and prevent path conflicts --- handwritten/storage/scripts/preload-yargs.cjs | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/handwritten/storage/scripts/preload-yargs.cjs b/handwritten/storage/scripts/preload-yargs.cjs index 500fddfd579f..7810c92273bf 100644 --- a/handwritten/storage/scripts/preload-yargs.cjs +++ b/handwritten/storage/scripts/preload-yargs.cjs @@ -1,4 +1,5 @@ const Module = require('module'); +const fs = require('fs'); const path = require('path'); const originalResolveFilename = Module._resolveFilename; @@ -9,8 +10,30 @@ Module._resolveFilename = function(request, parent, isMain, options) { if (resolved.endsWith('.mjs')) { return resolved; } - // Directly resolve to the CommonJS build file to avoid disk I/O and cleanup issues - return path.join(path.dirname(resolved), 'build', 'index.cjs'); + // Create a unique shim file in the local scripts directory to avoid shared temp directory vulnerabilities + const safeHash = Buffer.from(resolved).toString('base64').replace(/[^a-zA-Z0-9]/g, ''); + const shimPath = path.join(__dirname, `yargs-shim-${safeHash}.cjs`); + + if (!fs.existsSync(shimPath)) { + const content = fs.readFileSync(resolved, 'utf8'); + // Replace `./build/index.cjs` with the absolute path + const buildIndexPath = path.join(path.dirname(resolved), 'build', 'index.cjs'); + // We must replace ALL relative requires. Luckily yargs/yargs only requires `./build/index.cjs` + const newContent = content.replace(/require\(['"]\.\/build\/index\.cjs['"]\)/g, `require(${JSON.stringify(buildIndexPath)})`); + fs.writeFileSync(shimPath, newContent); + } + + global.__yargsShimCleanups = global.__yargsShimCleanups || new Set(); + if (!global.__yargsShimCleanups.has(shimPath)) { + global.__yargsShimCleanups.add(shimPath); + process.on('exit', () => { + try { + fs.unlinkSync(shimPath); + } catch (e) {} + }); + } + + return shimPath; } return originalResolveFilename.apply(this, arguments); }; \ No newline at end of file