Skip to content

feat: add sanitizePackageJson option to strip dev-only package.json fields#1931

Open
claude[bot] wants to merge 2 commits into
mainfrom
feat/sanitize-package-json-hook
Open

feat: add sanitizePackageJson option to strip dev-only package.json fields#1931
claude[bot] wants to merge 2 commits into
mainfrom
feat/sanitize-package-json-hook

Conversation

@claude

@claude claude Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Requested by Samuel Attard · Slack thread

Fixes #1184

  • I have read the contribution documentation for this project.
  • I agree to follow the code of conduct that this project follows, as appropriate.
  • The changes are appropriately documented (if applicable).
  • The changes have sufficient test coverage (if applicable).
  • The testsuite passes successfully on my local machine (if applicable).

Summarize your changes:

Before: the entire developer package.json is copied into every packaged app verbatim — including devDependencies, scripts, workspace/package-manager settings, publishing config, and tooling configuration. Anyone unpacking the app can read all of it.

After: the bundled app's package.json is sanitized by default. Dev-only fields are stripped while runtime-relevant fields (main, name, productName, version, type, imports, exports, dependencies, optionalDependencies, peerDependencies, and everything else not on the denylist) are kept. Users can pass a new sanitizePackageJson option — an array of functions, consistent with the other hook options — to fully replace the default. The functions run serially, each receiving the object returned by the previous one, and defaultSanitizePackageJson is exported so a custom array can extend the default instead:

import { packager, defaultSanitizePackageJson } from '@electron/packager'

packager({
  // ...
  sanitizePackageJson: [
    defaultSanitizePackageJson,
    (packageJson) => {
      delete packageJson.myCustomField
      return packageJson
    },
  ],
})

Default denylist: devDependencies, scripts, workspaces, packageManager, resolutions, overrides, pnpm, private, publishConfig, devEngines, jest, eslintConfig, prettier, browserslist, lint-staged, nano-staged, husky, commitlint, mocha, ava, nyc, c8, tap, xo, standard.

How:

  • src/sanitize-package-json.ts (new): defaultSanitizePackageJson() plus sanitizeAppPackageJson(), which reads /package.json, runs the user-provided sanitizer functions (or the default when the option is unset or an empty array) serially — each function receives the object returned by the previous one — and writes the final result back with 2-space indentation. If no package.json was copied, it does nothing so that app validation can report its friendlier error.
  • src/platform.ts: App.copyTemplate() runs the sanitize step after all user afterCopy hooks complete and before the afterPrune hooks, so afterCopy hooks still see the original manifest and nothing after that phase ever sees the unsanitized one. sanitizePackageJson is also added to the copyPrebuiltAsar() incompatibility list, matching the other copy-phase hooks.
  • src/types.ts: new SanitizePackageJsonFunction type and sanitizePackageJson?: SanitizePackageJsonFunction[] option. Each function receives (packageJson, electronVersion, platform, arch) — the same context args as the other hooks — and returns (or resolves to) the object to pass on / write back; it transforms a value instead of observing a directory, hence no buildPath. A non-empty array replaces the default sanitizer entirely; an empty or unset array runs the default. Full TSDoc on the option (denylist, replace-not-extend semantics, serial chaining, ordering, prune interaction) for the generated API docs.
  • src/index.ts: exports defaultSanitizePackageJson.
  • test/packager.spec.ts: the expected ElectronAsarIntegrity hash in the macOS packaging test is updated, since sanitizing the fixture's bundled package.json deterministically changes the app.asar header (verified locally by reproducing both the old and new header hashes byte-for-byte).
  • test/sanitize-package-json.spec.ts (new): covers denylist stripping, keep-list preservation, on-disk write format, array-replaces-default semantics, empty-array-runs-default, serial chaining of two hooks (ordering, chained results, and context args), missing-manifest no-op, afterCopy → sanitize → afterPrune ordering through App.copyTemplate(), and the prebuiltAsar incompatibility error.

Prune safety: pruning is unaffected by design — the Pruner walks the source directory's package.json (userPathFilter constructs it from opts.dir), not the bundled copy, so stripping devDependencies from the bundled manifest cannot change which modules are pruned. validateElectronApp runs after sanitization and still passes, since the default keeps main.

Behavior change (please weigh in on semver): the default sanitizer is intentionally on by default, so this changes the packaged output for every consumer that doesn't set the new option. Apps that read stripped fields from their own bundled package.json at runtime (e.g. scripts or devDependencies) would need to opt out with a pass-through sanitizePackageJson: [(pkg) => pkg]. Flagging this clearly so maintainers can decide whether this ships as a major.

Local test note: yarn lint and yarn test pass locally except for pre-existing failures in test/packager.spec.ts / test/unzip.spec.ts that require downloading Electron binaries, which is blocked in my environment (HTTP 403 from the network proxy). Those failures are identical on main; relying on CI for full coverage.


Generated by Claude Code

claude added 2 commits July 3, 2026 19:40
…ields

Packaged apps currently ship the developer's entire package.json,
including devDependencies, scripts, and tooling configuration. The
bundled app's package.json is now rewritten after the afterCopy hooks
run and before the afterPrune hooks: a default sanitizer strips
dev-only fields while keeping runtime-relevant ones such as main,
dependencies, and exports.

Consistent with the other hook options, sanitizePackageJson is an
array of functions. They run serially, each receiving the package.json
object returned by the previous one plus the Electron version and the
target platform and arch. A non-empty array replaces the default
sanitizer entirely; the default is exported as
defaultSanitizePackageJson so custom arrays can include it.

Sanitizing the bundled copy cannot affect pruning, since the Pruner
reads the source directory's package.json.

The expected ElectronAsarIntegrity hash in the macOS packaging test
changes because the default sanitizer changes the bundled
package.json, and therefore the app.asar header, for the test fixture.

Fixes #1184
@claude claude Bot force-pushed the feat/sanitize-package-json-hook branch from 30b15bd to 514c023 Compare July 3, 2026 19:41
@claude claude Bot marked this pull request as ready for review July 3, 2026 19:51
@claude claude Bot requested a review from a team as a code owner July 3, 2026 19:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

electron-packager unexpectedly copies entire package.json into distribution

1 participant