From 07e8e13ca43841841bdd606d968439d4d5f94a35 Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Mon, 30 Mar 2026 17:08:54 -0700 Subject: [PATCH 1/2] Revert "feat: add integrity digest management commands and functionality (#380)" This reverts commit ccaf57fca8d766b9dec14f272a01022ef6c0b826. --- .github/workflows/test.yml | 3 - README.md | 28 -- bin/asar.mjs | 26 -- package.json | 8 +- src/asar.ts | 10 - src/integrity-digest.ts | 382 ---------------------- test/integrity-digest-integration-spec.ts | 236 ------------- yarn.lock | 278 +--------------- 8 files changed, 4 insertions(+), 967 deletions(-) delete mode 100644 src/integrity-digest.ts delete mode 100644 test/integrity-digest-integration-spec.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 45ece75..05cfa9f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,7 +43,4 @@ jobs: - name: Build run: yarn build - name: Test - env: - ELECTRON_INTEGRITY_DIGEST_TEST_MAJOR_VERSION: '41' - ELECTRON_INTEGRITY_DIGEST_TEST_RELEASE_CHANNEL: 'auto' run: yarn test diff --git a/README.md b/README.md index f5e6030..35f88cd 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,6 @@ $ asar --help extract|e extract archive - integrity-digest|id - manage integrity digest in app binary (macOS only) - Options: @@ -57,7 +54,6 @@ $ asar --help #### Excluding multiple resources from being packed - Given: ```text @@ -90,30 +86,6 @@ Exclude: a, b, d, f, h asar pack app app.asar --unpack-dir "{**/x1,**/x2,z4/w1}" ``` -#### Integrity digest - -The integrity digest is a macOS-specific feature that further hardens apps which utilize [ASAR Integrity](https://www.electronjs.org/docs/latest/tutorial/asar-integrity) from tampering by storing a hash of the integrity info within the code of the app itself. If you modify this hash, you ***must*** re-sign your app afterwards. By default, this feature is off. - -To enable the integrity digest, run: -```bash -asar integrity-digest on /path/to/YourApp.app -``` - -To disable the integrity digest, run: -```bash -asar integrity-digest off /path/to/YourApp.app -``` - -To see the status of the integrity digest, run: -```bash -asar integrity-digest status /path/to/YourApp.app -``` - -To verify the status of the integrity digest, run: -```bash -asar integrity-digest verify /path/to/YourApp.app -``` - ## Programmatic usage For full API usage, see the [API documentation](https://packages.electronjs.org/asar). diff --git a/bin/asar.mjs b/bin/asar.mjs index 67ddd51..9724964 100755 --- a/bin/asar.mjs +++ b/bin/asar.mjs @@ -2,7 +2,6 @@ import packageJSON from '../package.json' with { type: 'json' }; import { createPackageWithOptions, listPackage, extractFile, extractAll } from '../lib/asar.js'; -import { enableIntegrityDigestForApp, disableIntegrityDigestForApp, verifyIntegrityDigestForApp, printStoredIntegrityDigestForApp } from '../lib/integrity-digest.js'; import { program } from 'commander'; import fs from 'node:fs'; import path from 'node:path'; @@ -72,31 +71,6 @@ program.command('extract ') extractAll(archive, dest) }) -program.command('integrity-digest ') - .alias('id') - .description('manage integrity digest in app binary (macOS only)') - .action(async function (command, app) { - // No platform guard just in case users want to run this on other platforms - const allowedCommands = ['on', 'off', 'status', 'verify'] - switch (command) { - case 'on': - await enableIntegrityDigestForApp(app) - break - case 'off': - await disableIntegrityDigestForApp(app) - break - case 'status': - await printStoredIntegrityDigestForApp(app) - break - case 'verify': - await verifyIntegrityDigestForApp(app) - break - default: - console.log('Unknown integrity digest command: %s. Allowed commands are: %s', command, allowedCommands.join(', ')) - process.exit(1) - } - }) - program.command('*', { hidden: true}) .action(function (_cmd, args) { console.log('asar: \'%s\' is not an asar command. See \'asar --help\'.', args[0]) diff --git a/package.json b/package.json index e1fc76d..b00891d 100644 --- a/package.json +++ b/package.json @@ -44,21 +44,15 @@ "dependencies": { "commander": "^13.1.0", "glob": "^13.0.2", - "minimatch": "^10.0.1", - "plist": "^3.1.0" + "minimatch": "^10.0.1" }, "devDependencies": { - "@electron/fuses": "^2.1.0", - "@electron/get": "^4.0.2", "@tsconfig/node22": "^22.0.1", "@types/node": "^22.12.0", - "@types/plist": "^3.0.5", - "@types/semver": "^7.7.1", "electron": "^35.7.5", "husky": "^9.1.7", "lint-staged": "^16.2.7", "prettier": "^3.8.1", - "semver": "^7.7.4", "typedoc": "~0.25.13", "typescript": "^5.5.4", "vitest": "^3.2.4", diff --git a/src/asar.ts b/src/asar.ts index bd703e6..355cd58 100644 --- a/src/asar.ts +++ b/src/asar.ts @@ -443,13 +443,3 @@ export function extractAll(archivePath: string, dest: string) { } export { uncacheAll, uncacheFilesystem as uncache, FileRecord, DirectoryRecord } from './disk.js'; -export { - calculateIntegrityDigestForApp, - getStoredIntegrityDigestForApp, - setStoredIntegrityDigestForApp, - InvalidAppPathError, - InvalidAsarIntegrityError, - MissingIntegrityDigestError, - MultipleDifferentIntegrityDigestsError, - UnknownIntegrityDigestVersionError, -} from './integrity-digest.js'; diff --git a/src/integrity-digest.ts b/src/integrity-digest.ts deleted file mode 100644 index 881b0ba..0000000 --- a/src/integrity-digest.ts +++ /dev/null @@ -1,382 +0,0 @@ -import path from 'node:path'; -import crypto from 'node:crypto'; -import plist from 'plist'; - -import { wrappedFs as fs } from './wrapped-fs.js'; -import { FileRecord } from './disk.js'; - -// Integrity digest type definitions - -/** - * An object that represents an integrity digest of a given version. - */ -type IntegrityDigest = - | { used: false } - | ({ used: true; version: Version } & AdditionalParams); - -/** - * A v1 integrity digest. - */ -type IntegrityDigestV1 = IntegrityDigest<1, { sha256Digest: Buffer }>; - -/** - * A map type of all integrity digest versions. - */ -type DigestByVersion = { - 1: IntegrityDigestV1; - // Add new versions here -}; - -/** - * A union of all integrity digest versions. - */ -type AnyIntegrityDigest = DigestByVersion[keyof DigestByVersion]; - -// Integrity digest calculation functions - -type AsarIntegrity = Record>; - -function isValidAsarIntegrity(asarIntegrity: any): asarIntegrity is AsarIntegrity { - if (typeof asarIntegrity !== 'object' || asarIntegrity === null) return false; - if (Object.keys(asarIntegrity).length === 0) return false; - for (const key of Object.keys(asarIntegrity)) { - if (typeof key !== 'string') return false; - if (typeof asarIntegrity[key] !== 'object' || asarIntegrity[key] === null) return false; - if (typeof asarIntegrity[key].algorithm !== 'string') return false; - if (typeof asarIntegrity[key].hash !== 'string') return false; - } - return true; -} - -/** - * Calculates the v1 integrity digest for the app. - * @see https://github.com/electron/electron/blob/2d5597b1b0fa697905380184e26c9f0947e05c5d/shell/common/asar/integrity_digest.mm#L52-L66 - * @param asarIntegrity - The integrity information for the app. - * @returns The v1 integrity digest for the app. - * - */ -function calculateIntegrityDigestV1(asarIntegrity: AsarIntegrity): IntegrityDigestV1 { - const integrityHash = crypto.createHash('SHA256'); - for (const key of Object.keys(asarIntegrity).sort()) { - const { algorithm, hash } = asarIntegrity[key]; - integrityHash.update(key); - integrityHash.update(algorithm); - integrityHash.update(hash); - } - return { - used: true, - version: 1, - sha256Digest: integrityHash.digest(), - }; -} - -/** - * Reads the integrity information from the Info.plist of the given app - * bundle and calculates the v1 integrity digest for the app. - * @param appPath - The path to the app bundle. - * @returns The v1 integrity digest for the app. - */ -function calculateIntegrityDigestV1ForApp(appPath: string): IntegrityDigestV1 { - const plistPath = path.join(appPath, 'Contents', 'Info.plist'); - const plistBuffer = fs.readFileSync(plistPath); - const plistData = plist.parse(plistBuffer.toString()) as Record; - const asarIntegrity = plistData['ElectronAsarIntegrity']; - if (!isValidAsarIntegrity(asarIntegrity)) { - throw new InvalidAsarIntegrityError(); - } - return calculateIntegrityDigestV1(asarIntegrity); -} - -/// Integrity digest handling errors (API) - -export class InvalidAppPathError extends Error { - constructor() { - super('Invalid app path'); - this.name = 'InvalidAppPathError'; - } -} - -export class InvalidAsarIntegrityError extends Error { - constructor() { - super('Invalid ASAR Integrity information in Info.plist'); - this.name = 'InvalidAsarIntegrityError'; - } -} - -export class MissingIntegrityDigestError extends Error { - constructor() { - super('No integrity digest found in the binary'); - this.name = 'MissingIntegrityDigestError'; - } -} - -export class MultipleDifferentIntegrityDigestsError extends Error { - constructor() { - super('Multiple different integrity digests found in the binary'); - this.name = 'MultipleDifferentIntegrityDigestsError'; - } -} - -export class UnknownIntegrityDigestVersionError extends Error { - constructor(version: number) { - super(`Unknown integrity digest version: ${version}`); - this.name = 'UnknownIntegrityDigestVersionError'; - } -} - -// Integrity digest storage and retrieval helpers - -/** - * @see https://github.com/electron/electron/blob/2d5597b1b0fa697905380184e26c9f0947e05c5d/shell/common/asar/integrity_digest.mm#L24 - */ -const INTEGRITY_DIGEST_SENTINEL = 'AGbevlPCksUGKNL8TSn7wGmJEuJsXb2A'; - -function pathToIntegrityDigestFile(appPath: string) { - if (appPath.endsWith('.app')) { - return path.resolve( - appPath, - 'Contents', - 'Frameworks', - 'Electron Framework.framework', - 'Electron Framework', - ); - } - throw new InvalidAppPathError(); -} - -function forEachSentinelInApp( - appPath: string, - callback: (sentinelIndex: number, integrityFile: Buffer) => void, - writeBack: boolean = false, -) { - const integrityFilePath = pathToIntegrityDigestFile(appPath); - const integrityFile = fs.readFileSync(integrityFilePath); - let searchCursor = 0; - const sentinelAsBuffer = Buffer.from(INTEGRITY_DIGEST_SENTINEL); - do { - const sentinelIndex = integrityFile.indexOf(sentinelAsBuffer, searchCursor); - if (sentinelIndex === -1) break; - callback(sentinelIndex, integrityFile); - searchCursor = sentinelIndex + sentinelAsBuffer.length; - } while (true); - if (writeBack) { - fs.writeFileSync(integrityFilePath, integrityFile); - } -} - -function doDigestsMatch(digestA: AnyIntegrityDigest, digestB: AnyIntegrityDigest): boolean { - if (digestA.used !== digestB.used) return false; - if (digestA.used && digestB.used) { - if (digestA.version !== digestB.version) return false; - switch (digestA.version) { - case 1: - return digestA.sha256Digest.equals(digestB.sha256Digest); - default: - throw new UnknownIntegrityDigestVersionError(digestA.version); - } - } else return true; -} - -function sentinelIndexToDigest( - integrityFile: Buffer, - sentinelIndex: number, -): T { - const used = integrityFile.readUInt8(sentinelIndex + INTEGRITY_DIGEST_SENTINEL.length) === 1; - if (!used) { - return { used: false } as T; - } else { - const version = integrityFile.readUInt8(sentinelIndex + INTEGRITY_DIGEST_SENTINEL.length + 1); - switch (version) { - case 1: { - const sha256Digest = integrityFile.subarray( - sentinelIndex + INTEGRITY_DIGEST_SENTINEL.length + 2, - sentinelIndex + INTEGRITY_DIGEST_SENTINEL.length + 2 + 32, // SHA256 digest size - ); - return { - used: true, - version: 1, - sha256Digest, - } as T; - } - default: - throw new UnknownIntegrityDigestVersionError(version); - } - } -} - -// Integrity digest storage and retrieval functions (API) - -/** - * Calculates the integrity digest for the app. - * @param appPath - The path to the app bundle. - * @param version - The version of the integrity digest to calculate. - * @returns The integrity digest for the app. - */ -export function calculateIntegrityDigestForApp( - appPath: string, - version: Version, -): DigestByVersion[Version] { - if (version !== 1) { - throw new UnknownIntegrityDigestVersionError(version); - } - switch (version) { - case 1: - return calculateIntegrityDigestV1ForApp(appPath); - default: - throw new UnknownIntegrityDigestVersionError(version); - } -} - -/** - * Gets the stored integrity digest for the app. - * @param appPath - The path to the app bundle. - * @returns The stored integrity digest for the app. - */ -export function getStoredIntegrityDigestForApp(appPath: string): T { - let lastDigestFound: T | null = null; - forEachSentinelInApp(appPath, (sentinelIndex, integrityFile) => { - const currentDigest = sentinelIndexToDigest(integrityFile, sentinelIndex); - if (lastDigestFound === null) { - lastDigestFound = currentDigest; - } else if (!doDigestsMatch(currentDigest, lastDigestFound)) { - throw new MultipleDifferentIntegrityDigestsError(); - } - lastDigestFound = currentDigest; - }); - if (lastDigestFound === null) { - throw new MissingIntegrityDigestError(); - } - return lastDigestFound; -} - -/** - * Sets the stored integrity digest for the app. - * @param appPath - The path to the app bundle. - * @param digest - The integrity digest to set. - * @returns The stored integrity digest for the app. - */ -export function setStoredIntegrityDigestForApp( - appPath: string, - digest: T, -): void { - if (digest.used === true && digest.version !== 1) { - throw new UnknownIntegrityDigestVersionError(digest.version); - } - forEachSentinelInApp( - appPath, - (sentinelIndex, integrityFile) => { - integrityFile.writeUInt8( - digest.used ? 1 : 0, - sentinelIndex + INTEGRITY_DIGEST_SENTINEL.length, - ); - const oldVersion = integrityFile.readUInt8( - sentinelIndex + INTEGRITY_DIGEST_SENTINEL.length + 1, - ); - switch (oldVersion) { - case 1: - integrityFile.fill( - 0, - sentinelIndex + INTEGRITY_DIGEST_SENTINEL.length + 2, - sentinelIndex + INTEGRITY_DIGEST_SENTINEL.length + 2 + 32, // SHA256 digest size - ); - break; - } - if (digest.used) { - integrityFile.writeUInt8( - digest.version, - sentinelIndex + INTEGRITY_DIGEST_SENTINEL.length + 1, - ); - switch (digest.version) { - case 1: { - const v1Digest = digest as IntegrityDigestV1 & { used: true }; - v1Digest.sha256Digest.copy( - integrityFile, - sentinelIndex + INTEGRITY_DIGEST_SENTINEL.length + 2, - ); - break; - } - default: - throw new UnknownIntegrityDigestVersionError(digest.version); - } - } - }, - true, - ); -} - -// High-level integrity digest management functions - -function printDigest(digest: AnyIntegrityDigest, prefix: string = '') { - const digestLogger = prefix - ? (s: string, ...args: any[]) => console.log(prefix + s, ...args) - : console.log; - if (!digest.used) { - digestLogger('Integrity digest is OFF'); - return; - } - digestLogger('Integrity digest is ON (version: %d)', digest.version); - switch (digest.version) { - case 1: - digestLogger('\tDigest (SHA256): %s', digest.sha256Digest.toString('hex')); - break; - default: - digestLogger('\tUnexpected digest version: %d. Cannot print digest.', digest.version); - } -} - -export async function enableIntegrityDigestForApp(appPath: string): Promise { - try { - console.log('Calculating integrity digest...'); - const digest = calculateIntegrityDigestForApp(appPath, 1); - console.log('Turning integrity digest ON...'); - await setStoredIntegrityDigestForApp(appPath, digest); - console.log('Integrity digest turned ON'); - } catch (e) { - const errorMessage = e instanceof Error ? e.message : String(e); - console.log('Failed to turn ON integrity digest: %s', errorMessage); - } -} - -export async function disableIntegrityDigestForApp(appPath: string): Promise { - try { - console.log('Turning integrity digest OFF...'); - await setStoredIntegrityDigestForApp(appPath, { used: false }); - console.log('Integrity digest turned OFF'); - } catch (e) { - const errorMessage = e instanceof Error ? e.message : String(e); - console.log('Failed to turn OFF integrity digest: %s', errorMessage); - } -} - -export async function printStoredIntegrityDigestForApp(appPath: string): Promise { - try { - const storedDigest = await getStoredIntegrityDigestForApp(appPath); - printDigest(storedDigest); - } catch (e) { - const errorMessage = e instanceof Error ? e.message : String(e); - console.log('Failed to read integrity digest: %s', errorMessage); - } -} - -export async function verifyIntegrityDigestForApp(appPath: string): Promise { - try { - const storedDigest = await getStoredIntegrityDigestForApp(appPath); - if (!storedDigest.used) { - console.log('Integrity digest is off, verification SKIPPED'); - return; - } - const calculatedDigest = calculateIntegrityDigestForApp(appPath, 1); - if (doDigestsMatch(storedDigest, calculatedDigest)) { - console.log('Integrity digest verification PASSED'); - } else { - console.log('Integrity digest verification FAILED'); - console.log('Expected digest:'); - printDigest(calculatedDigest, '\t'); - console.log('Actual digest:'); - printDigest(storedDigest, '\t'); - } - } catch (e) { - const errorMessage = e instanceof Error ? e.message : String(e); - console.log('Failed to verify integrity digest: %s', errorMessage); - } -} diff --git a/test/integrity-digest-integration-spec.ts b/test/integrity-digest-integration-spec.ts deleted file mode 100644 index 00673b5..0000000 --- a/test/integrity-digest-integration-spec.ts +++ /dev/null @@ -1,236 +0,0 @@ -import path from 'node:path'; -import crypto from 'node:crypto'; -import { spawn } from 'node:child_process'; -import { setTimeout as sleep } from 'node:timers/promises'; -import { describe, expect, it } from 'vitest'; -import plist from 'plist'; -import semver from 'semver'; -import { downloadArtifact } from '@electron/get'; -import { FuseV1Options, FuseVersion, flipFuses } from '@electron/fuses'; - -import { - calculateIntegrityDigestForApp, - getRawHeader, - getStoredIntegrityDigestForApp, - setStoredIntegrityDigestForApp, -} from '../src/asar.js'; -import { wrappedFs as fs } from '../src/wrapped-fs.js'; -import { TEST_APPS_DIR } from './util/constants.js'; - -type ReleaseChannel = 'auto' | 'stable' | 'beta' | 'alpha' | 'nightly'; - -async function fetchAllElectronVersions(nightly?: boolean) { - const response = await fetch(`https://registry.npmjs.org/electron${nightly ? '-nightly' : ''}`); - if (!response.ok) { - throw new Error(`Failed to fetch electron versions: HTTP ${response.status}`); - } - const payload = (await response.json()) as { - versions?: Record; - }; - if (!payload.versions || typeof payload.versions !== 'object') { - throw new Error('Unexpected npm registry response for electron versions'); - } - return Object.keys(payload.versions); -} - -async function getTargetElectronVersion(): Promise { - const configuredVersion = process.env.ELECTRON_INTEGRITY_DIGEST_TEST_VERSION?.trim(); - if (configuredVersion) { - return Promise.resolve(configuredVersion); - } - const configuredMajorVersion = process.env.ELECTRON_INTEGRITY_DIGEST_TEST_MAJOR_VERSION?.trim(); - const configuredReleaseChannel = - process.env.ELECTRON_INTEGRITY_DIGEST_TEST_RELEASE_CHANNEL?.trim(); - if (!configuredMajorVersion || !configuredReleaseChannel) { - throw new Error( - 'ELECTRON_INTEGRITY_DIGEST_TEST_MAJOR_VERSION and ELECTRON_INTEGRITY_DIGEST_TEST_RELEASE_CHANNEL must be set', - ); - } - if (!/^\d+$/.test(configuredMajorVersion)) { - throw new Error('ELECTRON_INTEGRITY_DIGEST_TEST_MAJOR_VERSION must be a number'); - } - if (!['auto', 'stable', 'beta', 'alpha', 'nightly'].includes(configuredReleaseChannel)) { - throw new Error( - 'ELECTRON_INTEGRITY_DIGEST_TEST_RELEASE_CHANNEL must be one of auto|stable|beta|alpha|nightly', - ); - } - const allElectronVersions = await fetchAllElectronVersions( - configuredReleaseChannel === 'nightly', - ); - const targetMajorVersion = Number(configuredMajorVersion); - const validVersions = allElectronVersions.filter((version) => semver.valid(version) !== null); - - const latestVersionForChannel = (channel: ReleaseChannel): string | null => { - const matchingVersions = validVersions.filter((version) => { - if (semver.major(version) !== targetMajorVersion) { - return false; - } - const prerelease = semver.prerelease(version); - if (channel === 'stable') { - return prerelease === null; - } - if (prerelease === null || prerelease.length === 0) { - return false; - } - return String(prerelease[0]) === channel; - }); - const [latest] = semver.rsort(matchingVersions); - return latest ?? null; - }; - - const channelsToTry: Array = - configuredReleaseChannel === 'auto' - ? ['stable', 'beta', 'alpha', 'nightly'] - : [configuredReleaseChannel as ReleaseChannel]; - - for (const channel of channelsToTry) { - const latest = latestVersionForChannel(channel); - if (latest) { - return latest; - } - } - - throw new Error( - `Could not find matching Electron version for major=${targetMajorVersion}, channel=${configuredReleaseChannel}`, - ); -} - -function runCommand(command: string, args: string[]) { - return new Promise((resolve, reject) => { - const child = spawn(command, args, { stdio: 'ignore' }); - child.on('error', reject); - child.on('exit', (code) => { - if (code === 0) { - resolve(); - } else { - reject(new Error(`${command} exited with code ${code}`)); - } - }); - }); -} - -describe('integrity digest integration', () => { - it('downloads an app, enables ASAR integrity, and verifies digest survives launch', async ({ - skip, - }) => { - if (process.versions.electron) { - skip(); // Running under Electron, skip. This test only works when running under Node.js. - } - if (process.platform !== 'darwin') { - skip(); // Not macOS, skip. The integrity digest feature is only available on macOS. - } - if (!process.env.CI) { - skip(); // Not CI, skip. We only want to run this test in CI. - } - - const baseDir = path.join(TEST_APPS_DIR, 'integrity-digest-integration'); - const extractedDir = path.join(baseDir, 'extracted'); - - fs.rmSync(baseDir, { recursive: true, force: true }); - await fs.mkdirp(baseDir); - - const electronVersion = await getTargetElectronVersion(); - const parsedElectronVersion = semver.parse(electronVersion); - if (!parsedElectronVersion) { - throw new Error(`Failed to parse Electron version: ${electronVersion}`); - } - if (parsedElectronVersion.major < 41) { - throw new Error( - `The integrity digest is only supported for Electron >=41. Got ${electronVersion}.`, - ); - } - - // Step 1: download and extract the Electron app. - const artifactPath = await downloadArtifact({ - version: electronVersion, - platform: 'darwin', - arch: process.arch, - artifactName: 'electron', - cacheRoot: path.join(baseDir, 'cache'), - tempDirectory: baseDir, - }); - - await fs.mkdirp(extractedDir); - await runCommand('ditto', ['-xk', artifactPath, extractedDir]); - - const appPath = path.join(extractedDir, 'Electron.app'); - const appContentsPath = path.join(appPath, 'Contents'); - const asarPath = path.join(appContentsPath, 'Resources', 'default_app.asar'); - const infoPlistPath = path.join(appContentsPath, 'Info.plist'); - const electronBinaryPath = path.join(appContentsPath, 'MacOS', 'Electron'); - const asarIntegrityPlistKey = path.relative(appContentsPath, asarPath); - - expect(fs.existsSync(appPath)).toBe(true); - expect(fs.existsSync(asarPath)).toBe(true); - - // Step 2: enable runtime ASAR integrity validation. - await flipFuses(electronBinaryPath, { - version: FuseVersion.V1, - [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true, - }); - - // Add ASAR integrity metadata to Info.plist so we can calculate/store digest. - const plistRaw = await fs.readFile(infoPlistPath, 'utf8'); - const plistData = plist.parse(plistRaw) as Record; - const asarHeaderHash = crypto - .createHash('SHA256') - .update(getRawHeader(asarPath).headerString) - .digest('hex'); - plistData.ElectronAsarIntegrity = { - [asarIntegrityPlistKey]: { - algorithm: 'SHA256', - hash: asarHeaderHash, - }, - }; - await fs.writeFile(infoPlistPath, plist.build(plistData as any), 'utf8'); - - // Step 3: calculate digest, save it, and store in artifact. - const calculatedDigest = calculateIntegrityDigestForApp(appPath, 1); - expect(calculatedDigest.used).toBe(true); - if (!calculatedDigest.used) { - throw new Error('Expected calculated digest to be enabled'); - } - expect(calculatedDigest.version).toBe(1); - const calculatedDigestHex = calculatedDigest.sha256Digest.toString('hex'); - setStoredIntegrityDigestForApp(appPath, calculatedDigest); - - // Re-sign after mutating Info.plist and framework binary, otherwise macOS can kill on launch. - await runCommand('codesign', [ - '--sign', - '-', - '--force', - '--preserve-metadata=entitlements,requirements,flags,runtime', - '--deep', - appPath, - ]); - - // Step 4: launch and ensure app is still running after a short wait. - const appProcess = spawn(electronBinaryPath, [], { stdio: 'ignore' }); - try { - await sleep(4_000); - expect(appProcess.exitCode).toBeNull(); - } finally { - if (appProcess.exitCode === null) { - appProcess.kill('SIGTERM'); - await Promise.race([ - new Promise((resolve) => appProcess.once('exit', () => resolve())), - sleep(3000).then(() => { - if (appProcess.exitCode === null) { - appProcess.kill('SIGKILL'); - } - }), - ]); - } - } - - // Step 5: read stored digest and compare with saved digest value. - const storedDigest = getStoredIntegrityDigestForApp<{ - used: true; - version: 1; - sha256Digest: Buffer; - }>(appPath); - expect(storedDigest.used).toBe(true); - expect(storedDigest.version).toBe(1); - expect(storedDigest.sha256Digest.toString('hex')).toBe(calculatedDigestHex); - }, 600_000); -}); diff --git a/yarn.lock b/yarn.lock index 672cf7a..fa0cc7b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,21 +9,15 @@ __metadata: version: 0.0.0-use.local resolution: "@electron/asar@workspace:." dependencies: - "@electron/fuses": "npm:^2.1.0" - "@electron/get": "npm:^4.0.2" "@tsconfig/node22": "npm:^22.0.1" "@types/node": "npm:^22.12.0" - "@types/plist": "npm:^3.0.5" - "@types/semver": "npm:^7.7.1" commander: "npm:^13.1.0" electron: "npm:^35.7.5" glob: "npm:^13.0.2" husky: "npm:^9.1.7" lint-staged: "npm:^16.2.7" minimatch: "npm:^10.0.1" - plist: "npm:^3.1.0" prettier: "npm:^3.8.1" - semver: "npm:^7.7.4" typedoc: "npm:~0.25.13" typescript: "npm:^5.5.4" vitest: "npm:^3.2.4" @@ -36,15 +30,6 @@ __metadata: languageName: unknown linkType: soft -"@electron/fuses@npm:^2.1.0": - version: 2.1.0 - resolution: "@electron/fuses@npm:2.1.0" - bin: - electron-fuses: dist/bin.js - checksum: 10c0/a88f3ada0e93678589107d5782219dee2a6543b5889ce890fe5b33f443952f58670a679f337a65826553e9d937f430f751b9396240d1d0a6189e744816ad0ac9 - languageName: node - linkType: hard - "@electron/get@npm:^2.0.0": version: 2.0.2 resolution: "@electron/get@npm:2.0.2" @@ -64,25 +49,6 @@ __metadata: languageName: node linkType: hard -"@electron/get@npm:^4.0.2": - version: 4.0.2 - resolution: "@electron/get@npm:4.0.2" - dependencies: - debug: "npm:^4.1.1" - env-paths: "npm:^3.0.0" - global-agent: "npm:^3.0.0" - got: "npm:^14.4.5" - graceful-fs: "npm:^4.2.11" - progress: "npm:^2.0.3" - semver: "npm:^7.6.3" - sumchecker: "npm:^3.0.1" - dependenciesMeta: - global-agent: - optional: true - checksum: 10c0/21c6fd66f552a66c5ac2b8e06c684be2a1cfc2124c9e60f2708dfbdf32d719013946b5492d755d9830c833b421b61ba9d56a800abc3bae2fbd3fabfd80b33d14 - languageName: node - linkType: hard - "@esbuild/aix-ppc64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/aix-ppc64@npm:0.25.8" @@ -302,13 +268,6 @@ __metadata: languageName: node linkType: hard -"@keyv/serialize@npm:^1.1.1": - version: 1.1.1 - resolution: "@keyv/serialize@npm:1.1.1" - checksum: 10c0/b0008cae4a54400c3abf587b8cc2474c6f528ee58969ce6cf9cb07a04006f80c73c85971d6be6544408318a2bc40108236a19a82aea0a6de95aae49533317374 - languageName: node - linkType: hard - "@npmcli/agent@npm:^3.0.0": version: 3.0.0 resolution: "@npmcli/agent@npm:3.0.0" @@ -513,13 +472,6 @@ __metadata: languageName: node linkType: hard -"@sec-ant/readable-stream@npm:^0.4.1": - version: 0.4.1 - resolution: "@sec-ant/readable-stream@npm:0.4.1" - checksum: 10c0/64e9e9cf161e848067a5bf60cdc04d18495dc28bb63a8d9f8993e4dd99b91ad34e4b563c85de17d91ffb177ec17a0664991d2e115f6543e73236a906068987af - languageName: node - linkType: hard - "@sindresorhus/is@npm:^4.0.0": version: 4.6.0 resolution: "@sindresorhus/is@npm:4.6.0" @@ -527,13 +479,6 @@ __metadata: languageName: node linkType: hard -"@sindresorhus/is@npm:^7.0.1": - version: 7.2.0 - resolution: "@sindresorhus/is@npm:7.2.0" - checksum: 10c0/0040c17d7826414363f99f5d56077c200789d51e6dfe5542920bfb29ab3828ec0ebf2845e8bae796bee461debb646b5e4c0a623140131cf3143471e915b50b54 - languageName: node - linkType: hard - "@szmarczak/http-timer@npm:^4.0.5": version: 4.0.6 resolution: "@szmarczak/http-timer@npm:4.0.6" @@ -592,13 +537,6 @@ __metadata: languageName: node linkType: hard -"@types/http-cache-semantics@npm:^4.0.4": - version: 4.2.0 - resolution: "@types/http-cache-semantics@npm:4.2.0" - checksum: 10c0/82dd33cbe7d4843f1e884a251c6a12d385b62274353b9db167462e7fbffdbb3a83606f9952203017c5b8cabbd7b9eef0cf240a3a9dedd20f69875c9701939415 - languageName: node - linkType: hard - "@types/keyv@npm:^3.1.4": version: 3.1.4 resolution: "@types/keyv@npm:3.1.4" @@ -633,16 +571,6 @@ __metadata: languageName: node linkType: hard -"@types/plist@npm:^3.0.5": - version: 3.0.5 - resolution: "@types/plist@npm:3.0.5" - dependencies: - "@types/node": "npm:*" - xmlbuilder: "npm:>=11.0.1" - checksum: 10c0/2a929f4482e3bea8c3288a46ae589a2ae2d01df5b7841ead7032d7baa79d79af6c875a5798c90705eea9306c2fb1544d7ed12ab3c905c5626d5dd5dc9f464b94 - languageName: node - linkType: hard - "@types/responselike@npm:^1.0.0": version: 1.0.0 resolution: "@types/responselike@npm:1.0.0" @@ -652,13 +580,6 @@ __metadata: languageName: node linkType: hard -"@types/semver@npm:^7.7.1": - version: 7.7.1 - resolution: "@types/semver@npm:7.7.1" - checksum: 10c0/c938aef3bf79a73f0f3f6037c16e2e759ff40c54122ddf0b2583703393d8d3127130823facb880e694caa324eb6845628186aac1997ee8b31dc2d18fafe26268 - languageName: node - linkType: hard - "@types/yauzl@npm:^2.9.1": version: 2.10.0 resolution: "@types/yauzl@npm:2.10.0" @@ -751,13 +672,6 @@ __metadata: languageName: node linkType: hard -"@xmldom/xmldom@npm:^0.8.8": - version: 0.8.11 - resolution: "@xmldom/xmldom@npm:0.8.11" - checksum: 10c0/e768623de72c95d3dae6b5da8e33dda0d81665047811b5498d23a328d45b13feb5536fe921d0308b96a4a8dd8addf80b1f6ef466508051c0b581e63e0dc74ed5 - languageName: node - linkType: hard - "abbrev@npm:^3.0.0": version: 3.0.1 resolution: "abbrev@npm:3.0.1" @@ -841,13 +755,6 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.5.1": - version: 1.5.1 - resolution: "base64-js@npm:1.5.1" - checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf - languageName: node - linkType: hard - "boolean@npm:^3.0.1": version: 3.2.0 resolution: "boolean@npm:3.2.0" @@ -880,13 +787,6 @@ __metadata: languageName: node linkType: hard -"byte-counter@npm:^0.1.0": - version: 0.1.0 - resolution: "byte-counter@npm:0.1.0" - checksum: 10c0/2e7b9cf902d06a6601f8ab893964a8b6b9e2b2dfc60fcee0d340e50b95aa3dc77c4d34ddf3e63cc374b4e5b1d0d694a942de6fbe8ee95d39418f3fdff666b6a4 - languageName: node - linkType: hard - "cac@npm:^6.7.14": version: 6.7.14 resolution: "cac@npm:6.7.14" @@ -921,28 +821,6 @@ __metadata: languageName: node linkType: hard -"cacheable-lookup@npm:^7.0.0": - version: 7.0.0 - resolution: "cacheable-lookup@npm:7.0.0" - checksum: 10c0/63a9c144c5b45cb5549251e3ea774c04d63063b29e469f7584171d059d3a88f650f47869a974e2d07de62116463d742c287a81a625e791539d987115cb081635 - languageName: node - linkType: hard - -"cacheable-request@npm:^13.0.12": - version: 13.0.18 - resolution: "cacheable-request@npm:13.0.18" - dependencies: - "@types/http-cache-semantics": "npm:^4.0.4" - get-stream: "npm:^9.0.1" - http-cache-semantics: "npm:^4.2.0" - keyv: "npm:^5.5.5" - mimic-response: "npm:^4.0.0" - normalize-url: "npm:^8.1.1" - responselike: "npm:^4.0.2" - checksum: 10c0/251d4831a00d9d9a10a2875c2e653e00b7392ba289de2744f2e0f873839a3bc05f88267ce74dc734558a0d6ad21b360b45b43f075cc807b544c7fc676b62ff78 - languageName: node - linkType: hard - "cacheable-request@npm:^7.0.2": version: 7.0.2 resolution: "cacheable-request@npm:7.0.2" @@ -1094,15 +972,6 @@ __metadata: languageName: node linkType: hard -"decompress-response@npm:^10.0.0": - version: 10.0.0 - resolution: "decompress-response@npm:10.0.0" - dependencies: - mimic-response: "npm:^4.0.0" - checksum: 10c0/e8ce13b3f790fbac1e75a7be9ce4f77be62a6e5fcccfd9bd73e9d8b48b9a3b6c1b7b918ecd321095f3839b3bc9b6f6af2b1bd9c905eeddc0d1177d297b073232 - languageName: node - linkType: hard - "decompress-response@npm:^6.0.0": version: 6.0.0 resolution: "decompress-response@npm:6.0.0" @@ -1209,13 +1078,6 @@ __metadata: languageName: node linkType: hard -"env-paths@npm:^3.0.0": - version: 3.0.0 - resolution: "env-paths@npm:3.0.0" - checksum: 10c0/76dec878cee47f841103bacd7fae03283af16f0702dad65102ef0a556f310b98a377885e0f32943831eb08b5ab37842a323d02529f3dfd5d0a40ca71b01b435f - languageName: node - linkType: hard - "environment@npm:^1.0.0": version: 1.1.0 resolution: "environment@npm:1.1.0" @@ -1439,13 +1301,6 @@ __metadata: languageName: node linkType: hard -"form-data-encoder@npm:^4.0.2": - version: 4.1.0 - resolution: "form-data-encoder@npm:4.1.0" - checksum: 10c0/cbd655aa8ffff6f7c2733b1d8e95fa9a2fe8a88a90bde29fb54b8e02c9406e51f32a014bfe8297d67fbac9f77614d14a8b4bbc4fd0352838e67e97a881d06332 - languageName: node - linkType: hard - "fs-extra@npm:^8.1.0": version: 8.1.0 resolution: "fs-extra@npm:8.1.0" @@ -1519,16 +1374,6 @@ __metadata: languageName: node linkType: hard -"get-stream@npm:^9.0.1": - version: 9.0.1 - resolution: "get-stream@npm:9.0.1" - dependencies: - "@sec-ant/readable-stream": "npm:^0.4.1" - is-stream: "npm:^4.0.1" - checksum: 10c0/d70e73857f2eea1826ac570c3a912757dcfbe8a718a033fa0c23e12ac8e7d633195b01710e0559af574cbb5af101009b42df7b6f6b29ceec8dbdf7291931b948 - languageName: node - linkType: hard - "glob@npm:^10.2.2": version: 10.5.0 resolution: "glob@npm:10.5.0" @@ -1598,26 +1443,6 @@ __metadata: languageName: node linkType: hard -"got@npm:^14.4.5": - version: 14.6.6 - resolution: "got@npm:14.6.6" - dependencies: - "@sindresorhus/is": "npm:^7.0.1" - byte-counter: "npm:^0.1.0" - cacheable-lookup: "npm:^7.0.0" - cacheable-request: "npm:^13.0.12" - decompress-response: "npm:^10.0.0" - form-data-encoder: "npm:^4.0.2" - http2-wrapper: "npm:^2.2.1" - keyv: "npm:^5.5.3" - lowercase-keys: "npm:^3.0.0" - p-cancelable: "npm:^4.0.1" - responselike: "npm:^4.0.2" - type-fest: "npm:^4.26.1" - checksum: 10c0/dab4dbd35deac5634450cd745187ba68cfb9fd8d9236bec4861b633c7dc54f6383fde04cf504b16148625c307a229ff8cccf35d6622824ab13243c9d0af0fcc1 - languageName: node - linkType: hard - "graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0": version: 4.2.10 resolution: "graceful-fs@npm:4.2.10" @@ -1625,7 +1450,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.6": +"graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 @@ -1664,7 +1489,7 @@ __metadata: languageName: node linkType: hard -"http-cache-semantics@npm:^4.1.1, http-cache-semantics@npm:^4.2.0": +"http-cache-semantics@npm:^4.1.1": version: 4.2.0 resolution: "http-cache-semantics@npm:4.2.0" checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 @@ -1691,16 +1516,6 @@ __metadata: languageName: node linkType: hard -"http2-wrapper@npm:^2.2.1": - version: 2.2.1 - resolution: "http2-wrapper@npm:2.2.1" - dependencies: - quick-lru: "npm:^5.1.1" - resolve-alpn: "npm:^1.2.0" - checksum: 10c0/7207201d3c6e53e72e510c9b8912e4f3e468d3ecc0cf3bf52682f2aac9cd99358b896d1da4467380adc151cf97c412bedc59dc13dae90c523f42053a7449eedb - languageName: node - linkType: hard - "https-proxy-agent@npm:^7.0.1": version: 7.0.6 resolution: "https-proxy-agent@npm:7.0.6" @@ -1769,13 +1584,6 @@ __metadata: languageName: node linkType: hard -"is-stream@npm:^4.0.1": - version: 4.0.1 - resolution: "is-stream@npm:4.0.1" - checksum: 10c0/2706c7f19b851327ba374687bc4a3940805e14ca496dc672b9629e744d143b1ad9c6f1b162dece81c7bfbc0f83b32b61ccc19ad2e05aad2dd7af347408f60c7f - languageName: node - linkType: hard - "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -1868,15 +1676,6 @@ __metadata: languageName: node linkType: hard -"keyv@npm:^5.5.3, keyv@npm:^5.5.5": - version: 5.6.0 - resolution: "keyv@npm:5.6.0" - dependencies: - "@keyv/serialize": "npm:^1.1.1" - checksum: 10c0/c3ea795b6e03593ca57c8f70928a69bad14c13389a7fb75649a115ff55615244b04d8902798d841c17f0bb4a8a8866c97133b543b93f151b440170bba09176db - languageName: node - linkType: hard - "lint-staged@npm:^16.2.7": version: 16.2.7 resolution: "lint-staged@npm:16.2.7" @@ -1942,13 +1741,6 @@ __metadata: languageName: node linkType: hard -"lowercase-keys@npm:^3.0.0": - version: 3.0.0 - resolution: "lowercase-keys@npm:3.0.0" - checksum: 10c0/ef62b9fa5690ab0a6e4ef40c94efce68e3ed124f583cc3be38b26ff871da0178a28b9a84ce0c209653bb25ca135520ab87fea7cd411a54ac4899cb2f30501430 - languageName: node - linkType: hard - "lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": version: 10.4.3 resolution: "lru-cache@npm:10.4.3" @@ -2056,13 +1848,6 @@ __metadata: languageName: node linkType: hard -"mimic-response@npm:^4.0.0": - version: 4.0.0 - resolution: "mimic-response@npm:4.0.0" - checksum: 10c0/761d788d2668ae9292c489605ffd4fad220f442fbae6832adce5ebad086d691e906a6d5240c290293c7a11e99fbdbbef04abbbed498bf8699a4ee0f31315e3fb - languageName: node - linkType: hard - "minimatch@npm:^10.0.1, minimatch@npm:^10.1.2": version: 10.2.4 resolution: "minimatch@npm:10.2.4" @@ -2248,13 +2033,6 @@ __metadata: languageName: node linkType: hard -"normalize-url@npm:^8.1.1": - version: 8.1.1 - resolution: "normalize-url@npm:8.1.1" - checksum: 10c0/1beb700ce42acb2288f39453cdf8001eead55bbf046d407936a40404af420b8c1c6be97a869884ae9e659d7b1c744e40e905c875ac9290644eec2e3e6fb0b370 - languageName: node - linkType: hard - "object-keys@npm:^1.1.1": version: 1.1.1 resolution: "object-keys@npm:1.1.1" @@ -2287,13 +2065,6 @@ __metadata: languageName: node linkType: hard -"p-cancelable@npm:^4.0.1": - version: 4.0.1 - resolution: "p-cancelable@npm:4.0.1" - checksum: 10c0/12636623f46784ba962b6fe7a1f34d021f1d9a2cc12c43e270baa715ea872d5c8c7d9f086ed420b8b9817e91d9bbe92c14c90e5dddd4a9968c81a2a7aef7089d - languageName: node - linkType: hard - "p-map@npm:^7.0.2": version: 7.0.3 resolution: "p-map@npm:7.0.3" @@ -2386,17 +2157,6 @@ __metadata: languageName: node linkType: hard -"plist@npm:^3.1.0": - version: 3.1.0 - resolution: "plist@npm:3.1.0" - dependencies: - "@xmldom/xmldom": "npm:^0.8.8" - base64-js: "npm:^1.5.1" - xmlbuilder: "npm:^15.1.1" - checksum: 10c0/db19ba50faafc4103df8e79bcd6b08004a56db2a9dd30b3e5c8b0ef30398ef44344a674e594d012c8fc39e539a2b72cb58c60a76b4b4401cbbc7c8f6b028d93d - languageName: node - linkType: hard - "postcss@npm:^8.5.6": version: 8.5.6 resolution: "postcss@npm:8.5.6" @@ -2458,7 +2218,7 @@ __metadata: languageName: node linkType: hard -"resolve-alpn@npm:^1.0.0, resolve-alpn@npm:^1.2.0": +"resolve-alpn@npm:^1.0.0": version: 1.2.1 resolution: "resolve-alpn@npm:1.2.1" checksum: 10c0/b70b29c1843bc39781ef946c8cd4482e6d425976599c0f9c138cec8209e4e0736161bf39319b01676a847000085dfdaf63583c6fb4427bf751a10635bd2aa0c4 @@ -2474,15 +2234,6 @@ __metadata: languageName: node linkType: hard -"responselike@npm:^4.0.2": - version: 4.0.2 - resolution: "responselike@npm:4.0.2" - dependencies: - lowercase-keys: "npm:^3.0.0" - checksum: 10c0/8366407fc7f12466dd52682483a31dd6ca892481365caadea9a380196d8a6238650e064531087bebd25d7e9393f491efc2dad723fadc54db7a2b442dba8ef588 - languageName: node - linkType: hard - "restore-cursor@npm:^5.0.0": version: 5.1.0 resolution: "restore-cursor@npm:5.1.0" @@ -2654,15 +2405,6 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.6.3, semver@npm:^7.7.4": - version: 7.7.4 - resolution: "semver@npm:7.7.4" - bin: - semver: bin/semver.js - checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2 - languageName: node - linkType: hard - "serialize-error@npm:^7.0.1": version: 7.0.1 resolution: "serialize-error@npm:7.0.1" @@ -2975,13 +2717,6 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^4.26.1": - version: 4.41.0 - resolution: "type-fest@npm:4.41.0" - checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4 - languageName: node - linkType: hard - "typedoc@npm:~0.25.13": version: 0.25.13 resolution: "typedoc@npm:0.25.13" @@ -3275,13 +3010,6 @@ __metadata: languageName: node linkType: hard -"xmlbuilder@npm:>=11.0.1, xmlbuilder@npm:^15.1.1": - version: 15.1.1 - resolution: "xmlbuilder@npm:15.1.1" - checksum: 10c0/665266a8916498ff8d82b3d46d3993913477a254b98149ff7cff060d9b7cc0db7cf5a3dae99aed92355254a808c0e2e3ec74ad1b04aa1061bdb8dfbea26c18b8 - languageName: node - linkType: hard - "xvfb-maybe@npm:^0.2.1": version: 0.2.1 resolution: "xvfb-maybe@npm:0.2.1" From d48544d7778a5c12009e90afbfd7479789319105 Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Tue, 31 Mar 2026 15:56:18 -0700 Subject: [PATCH 2/2] revert the picomatch change --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6ab2d76..a305ba9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2416,9 +2416,9 @@ __metadata: linkType: hard "picomatch@npm:^2.3.1": - version: 2.3.1 - resolution: "picomatch@npm:2.3.1" - checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be + version: 2.3.2 + resolution: "picomatch@npm:2.3.2" + checksum: 10c0/a554d1709e59be97d1acb9eaedbbc700a5c03dbd4579807baed95100b00420bc729335440ef15004ae2378984e2487a7c1cebd743cfdb72b6fa9ab69223c0d61 languageName: node linkType: hard