From e7af654b31167b3625a99dde85fce717dc4bbe73 Mon Sep 17 00:00:00 2001 From: ethernet Date: Tue, 8 Sep 2026 00:45:29 -0400 Subject: [PATCH] fix(signing): bound binary scanning during macOS packaging The osx-sign dependency accepts protobuf-like file lengths beyond its 512-byte sample and keeps allocating after EOF. That crashes the native macOS signing walk before codesign can finish. Pin the scanner used by osx-sign to the fixed version already in the build closure. Keep signature checks enabled. A real signing-walk test reproduces the allocation failure before the override and passes after it. --- apps/desktop/scripts/osx-sign-inputs.test.mjs | 39 +++++++++++++++++++ package-lock.json | 13 ------- package.json | 3 ++ 3 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 apps/desktop/scripts/osx-sign-inputs.test.mjs diff --git a/apps/desktop/scripts/osx-sign-inputs.test.mjs b/apps/desktop/scripts/osx-sign-inputs.test.mjs new file mode 100644 index 0000000000..f562a941a4 --- /dev/null +++ b/apps/desktop/scripts/osx-sign-inputs.test.mjs @@ -0,0 +1,39 @@ +import assert from 'node:assert/strict' +import { spawnSync } from 'node:child_process' +import fs from 'node:fs' +import { createRequire } from 'node:module' +import os from 'node:os' +import path from 'node:path' +import { pathToFileURL } from 'node:url' + +import { test } from 'vitest' + +const require = createRequire(import.meta.url) +const signerUtil = path.join(path.dirname(require.resolve('@electron/osx-sign')), 'util.js') + +test('the signing walk stays bounded on a truncated protobuf-like resource', () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-sign-input-')) + + try { + // The byte sample declares a large length, but the file ends immediately. + const resource = Buffer.alloc(512, 0x41) + Buffer.from([0x0a, 0xff, 0xff, 0xff, 0x7f]).copy(resource) + fs.writeFileSync(path.join(root, 'resource.dat'), resource) + fs.writeFileSync(path.join(root, 'readme.txt'), 'ordinary text\n') + const binary = path.join(root, 'library.dylib') + fs.writeFileSync(binary, Buffer.from([0xcf, 0xfa, 0xed, 0xfe, 0, 0, 0, 0])) + + // Isolate the scanner: a regression must fail the test, not exhaust its runner. + const code = `import(${JSON.stringify(pathToFileURL(signerUtil).href)}) + .then(async ({ walk }) => console.log(JSON.stringify(await walk(${JSON.stringify(root)})))) + .catch(error => { console.error(error); process.exitCode = 1 })` + const result = spawnSync(process.execPath, ['--max-old-space-size=64', '-e', code], { + encoding: 'utf8', timeout: 10000, maxBuffer: 64 * 1024, windowsHide: true + }) + + assert.equal(result.status, 0, result.error?.message || result.stderr) + assert.deepEqual(JSON.parse(result.stdout), [binary]) + } finally { + fs.rmSync(root, { recursive: true, force: true }) + } +}, 15000) diff --git a/package-lock.json b/package-lock.json index 67a21bac8e..f0952d3350 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1823,19 +1823,6 @@ "node": ">=22.12.0" } }, - "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } - }, "node_modules/@electron/rebuild": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.2.0.tgz", diff --git a/package.json b/package.json index 0ea4b21a3c..2170d11554 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,9 @@ "typescript-eslint": "8.64.0" }, "overrides": { + "@electron/osx-sign": { + "isbinaryfile": "5.0.7" + }, "lodash": "4.18.1", "yauzl": "^3.3.1", "protobufjs": "^8.7.1",