Tags: electron/packager
Tags
feat: asar integrity digest (#1890) * feat: asar integrity digest Co-authored-by: Noah Gregory <noahmgregory@gmail.com> * test: assert sentinel exists in asar-disabled digest test The "does not write digest when asar is disabled" test wrapped its only assertion in `if (sentinelIndex !== -1)`, silently passing with zero assertions if the sentinel was missing. Both sibling tests in the same describe block explicitly assert the sentinel exists — this one drifted. With test/config.json on 41.1.0 the sentinel is guaranteed present, so the guard only masked failures. * perf: use chunked scan + positional writes for integrity digest Previously setIntegrityDigest loaded the entire Electron Framework binary (150-500 MB) into memory, scanned it, modified 34 bytes, and wrote the whole thing back. Now it scans in 4 MB chunks with two concurrent workers (I/O overlaps Buffer.indexOf CPU) and patches only the 34-byte digest slot(s) via handle.write at the found position(s). Each chunk overreads sentinel.length-1 bytes so a sentinel straddling a boundary is still detected. Peak memory drops from ~binary-size to ~8 MB. Same approach as electron/fuses#96, which benched 10-15x faster on 150-300 MB binaries. Adds two synthetic-binary tests: one plants the sentinel across a 4 MB boundary (verified load-bearing via mutation), one plants a sentinel in each of two chunks to cover the universal-binary multi-write path. Both skip packager() and run in ~10ms. * refactor: extract writeIntegrityDigest helper setIntegrityDigest had grown to ~155 lines after the chunked-scan change. Pull the scan/validate/write block out to a module-level helper alongside isAsarIntegrity; the method now just gates on version, resolves integrity, computes the hash, and calls through. Sentinel is passed as a parameter rather than referenced via MacApp.INTEGRITY_DIGEST_SENTINEL to avoid a no-use-before-define disable comment. * fix lint * fix lint 2 * chore: debug logging and comments * fix(mac): restore ad-hoc framework signature after writing integrity digest Patching the integrity digest into the Electron Framework binary invalidates the ad-hoc code signature that official Electron builds ship with, so unsigned darwin/mas arm64 apps were killed at launch on Apple Silicon ("code signature invalid"). Re-sign the framework with codesign --force --sign - --deep after the patch, mirroring @electron/fuses' resetAdHocDarwinSignature. The re-sign also runs when osxSign is configured, since osx-sign failures can be reduced to a warning via continueOnError and would otherwise ship a broken framework. Universal builds skip the digest + re-sign for the intermediate x64 and arm64 slices: bundle re-signing creates a per-arch _CodeSignature/CodeResources file that @electron/universal rejects when merging, and the slice frameworks are replaced by the lipo merge anyway. The merged app gets its own digest + re-sign instead. --deep is required because Electron's darwin-x64 dists ship unsigned binaries, which otherwise fail nested code validation when sealing the framework bundle. On non-darwin hosts, where codesign is unavailable, a warning is emitted when no osxSign option is set. * fix: skip asar integrity digest on non-darwin hosts Patching the Electron Framework binary invalidates the ad-hoc signature official Electron builds ship with, and only codesign (macOS-only) can restore it. Apple Silicon refuses to launch binaries with an invalid signature, so cross-packaged unsigned apps would be killed at startup where previous Packager versions produced launchable apps. Electron fails open when the digest slot is unwritten, so skipping the patch off-macOS preserves existing behavior at no security cost — those apps simply don't get digest enforcement, and a warning says so. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177rcB5TuvnmWgHiy5AYUH7 * test: read framework binary via Versions/A so digest test passes on Windows The non-darwin integrity digest test read the Electron Framework binary through the bundle root's 'Electron Framework' symlink. On Windows hosts the extracted bundle's 'Versions/Current' link is a file-type symlink that cannot be traversed as a directory, so resolving the root symlink fails with ENOENT. Read the concrete binary at Versions/A instead, which exists as a regular file on every host platform. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SWNEg8hKKnkdBJw5Phj6Dh * fix: review feedback on integrity digest diagnostics and validation - Check process.platform before any filesystem work in setIntegrityDigest, so non-darwin hosts get the accurate 'package on macOS' warning instead of a misleading 'binary not found' one (the framework path isn't even resolvable on Windows). - Make sentinel slot validation all-or-nothing as the comment promises: throw on any slot without room for the payload instead of patching the remaining slices, which would leave one arch enforcing the digest while another fails open. - Drop the dead 'renamedAppPath ?? electronAppPath' fallback (the getter is never nullish) and document the after-rename precondition instead. - Deduplicate the expected-digest computation in tests into a computeExpectedDigest helper in test/utils.ts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UmT8nnHCKN6H87SVD5kinq * feat: add asarIntegrityDigest option to opt out of digest embedding Embedding the digest ad-hoc re-signs the Electron Framework bundle, which adds a per-arch _CodeSignature/CodeResources seal that Electron dists don't ship. That breaks users who package x64 and arm64 separately and merge them with @electron/universal themselves (makeUniversalApp requires non-binary files to be identical across arches), and surprises pipelines that modify app.asar after packaging. asarIntegrityDigest (CLI: --no-asar-integrity-digest) defaults to true and, when false, skips the digest patch and re-sign entirely, leaving the framework byte-identical to what Electron ships. The unwritten slot fails open at runtime: apps stay launchable and per-file asar integrity checks still apply; only the plist tamper-protection layer is absent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UmT8nnHCKN6H87SVD5kinq * docs: restructure asarIntegrityDigest option docs Lead with what the option does, list the exact conditions under which the digest is embedded, and give concrete guidance on when to disable it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UmT8nnHCKN6H87SVD5kinq --------- Co-authored-by: Noah Gregory <noahmgregory@gmail.com> Co-authored-by: Samuel Attard <sattard@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com>
fix: declare osxSign.continueOnError in PackagerOsxSignOptions (#1951) createSignOpts has read osxSign.continueOnError since #1579 (and defaults it to true), but PackagerOsxSignOptions is a straight Omit of osx-sign's options so TypeScript users can only set it through a cast. Declare it, mirroring PackagerWindowsSignOptions, and document the (different) default on both. No-Verification-Needed: type declarations and docs only
fix: set mtimes to extraction time after extracting Electron zips (#1940 ) Electron's dist zips have every entry's timestamp zeroed to the DOS epoch (1980-01-01). The Rust-based extractor used since v20.0.1 faithfully restores archive mtimes, so packaged apps ended up with every file dated 1980. Reset timestamps to the extraction time after extracting, matching the observable behavior of packager 18 and earlier, and preserve timestamps in the EXDEV copy fallbacks so both staging paths behave like rename. Co-authored-by: Claude <noreply@anthropic.com>
fix: throw a descriptive error when the out dir would exclude the app… … from packaging (#1928) * fix: throw a descriptive error when the out dir would exclude the app from packaging Out directories are automatically excluded from the packaged app. When the out dir is the app dir itself, or is a subdirectory of the app dir that contains the app's main entry point, packaging failed with a misleading "Application manifest was not found" or "The main entry point to your app was not found" error that blamed the ignore option. validateElectronApp now receives the resolved ignored out dirs and throws a targeted error explaining that the out dir is excluded from packaging and must live outside the app source. Fixes #1685 * fix: canonicalize paths when detecting an out dir that excludes the app Use realpath-canonicalized paths and a path.relative-based containment check so the out-dir detection works across symlinked paths (e.g. /var vs /private/var in macOS tmpdirs) and case-insensitive filesystems. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RX3wGLrrpx1fBfqXCZt1Tx --------- Co-authored-by: Claude <noreply@anthropic.com>
fix: support tmpdir: false when the out dir is inside the project dir (… …#1927) Node's fs.cp rejects copying a directory into a subdirectory of itself before the copy filter runs, so packaging with tmpdir: false and an out dir inside the project dir (e.g. Electron Forge's default ./out) failed with ERR_FS_CP_EINVAL. When the staging path is inside the app dir, copy each top-level entry individually instead, still applying the same filter so ignored paths (including the out dir itself) are skipped. Fixes #1679 Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: ianho <anho.zhang@gmail.com>
fix: add Electron 44 version gates for win32/ia32 and linux/armv7l (#… …1935) * fix: add Electron 44 version gates for win32/ia32 and linux/armv7l Electron removed win32/ia32 and linux/armv7l builds starting with v44.0.0-alpha.4 (electron/electron#51816). Add upper bounds to the buildVersions map so that: - arch=all no longer expands to ia32/armv7l for Electron >= 44.0.0-alpha.4 - an explicit ia32/armv7l target produces the friendly "official support only exists" warning instead of a download 404 The bound is anchored at 44.0.0-alpha.4 (not 44.0.0) because officialBuildExists uses semver.satisfies with includePrerelease: true, so '<44.0.0' would still match the 44.0.0 prereleases that no longer ship these builds. Electron <= 43 behavior is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEEZg64JKFrSDAc7u4V4Na * docs: use <= instead of ≤ in README note Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEEZg64JKFrSDAc7u4V4Na * docs: use GFM footnote syntax in README Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEEZg64JKFrSDAc7u4V4Na --------- Co-authored-by: Claude <noreply@anthropic.com>
fix: embed custom application manifests byte-for-byte instead of thei… …r entire backing ArrayBuffer (#1934) * test: verify custom application manifests are embedded byte-for-byte Adds regression tests for #1933: builds a minimal fixture executable in-memory with pe-library/resedit, runs the resedit() step with win32metadata's application-manifest and requested-execution-level options, and reads the RT_MANIFEST resource back out of the executable. The manifest read is forced to be a Buffer view at a nonzero byteOffset into a larger backing ArrayBuffer (the normal shape for pooled Buffers in Node), so these tests fail on the current implementation, which embeds the entire backing ArrayBuffer instead of the Buffer's actual byte range. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016E9GNG9AcxUyvtEmRzovbj * fix: slice manifest ArrayBuffers to the Buffer's actual byte range Buffer.buffer returns the entire backing ArrayBuffer. Node Buffers are frequently views into a larger shared ArrayBuffer (e.g. the internal buffer pool), so embedding buffer.buffer as the RT_MANIFEST resource can embed unrelated garbage bytes around the manifest, producing executables that fail to launch with side-by-side configuration errors. Slice the backing ArrayBuffer with byteOffset/length, mirroring the existing ASAR integrity resource handling in the same file. Applies to both the application-manifest and requested-execution-level branches. Fixes #1933 Diagnosis and fix proposed by @cristiammercado in the issue report. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016E9GNG9AcxUyvtEmRzovbj * refactor: use TextEncoder for execution-level manifest bytes * test: simplify readFile mock typing --------- Co-authored-by: Claude <noreply@anthropic.com>
ci: pin macOS test runners to macos-15 (#1929) GitHub is rolling out a new macos-26-arm64 image behind the macos-latest label. On that image, `codesign --verify` rejects app bundles signed by the test suite's self-signed certificate with "code has no resources but signature indicates they must be present", so the `packager > macOS > codesign > can sign the app` test fails deterministically. The same commits pass on macos-15-arm64. Pin the test matrix to macos-15 until @electron/osx-sign is compatible with macOS 26's codesign behavior. Co-authored-by: Claude <noreply@anthropic.com>
PreviousNext