Skip to content

Complete third-party attribution and pin the electron-builder toolchain - #574

Merged
kdroidFilter merged 2 commits into
nucleus-2.5from
chore/license-attribution-and-npm-lock
Aug 18, 2026
Merged

Complete third-party attribution and pin the electron-builder toolchain#574
kdroidFilter merged 2 commits into
nucleus-2.5from
chore/license-attribution-and-npm-lock

Conversation

@kdroidFilter

Copy link
Copy Markdown
Collaborator

Two independent fixes on the redistribution and release paths, one commit each.

1. Third-party attribution (chore: commit)

The repository is MIT-licensed but redistributes Apache-2.0 code whose attribution was not carried:

  • 127 Kotlin files under plugin-build/plugin/src retain the Copyright 20xx JetBrains s.r.o. … Apache 2.0 header — the Gradle plugin is a fork of the Compose Multiplatform Gradle plugin — yet no Apache-2.0 text accompanied them, and THIRD_PARTY_NOTICES.md did not mention it.
  • The vendored tao fork (Apache-2.0) and the three AccessKit crates (MIT OR Apache-2.0) compile into libnucleus_tao, which ships inside the decorated-window-tao JAR. accesskit_windows was also the only vendored crate not keeping its upstream LICENSE-APACHE / LICENSE-MIT.
  • LICENSE still carried the unfilled template line Copyright (c) 2020 <Source Code Author>.

THIRD_PARTY_NOTICES.md now covers all four components, plus the build-time tools and a pointer to Cargo.lock for the transitive Rust tree. License texts are collected once under licenses/. Apache-2.0 §4(a) requires the license copy to travel with binary distributions, so the jar tasks of plugin-build/plugin and decorated-window-tao stage the notices and licenses/ into META-INF/.

The LICENSE year follows the git history (first commit 2026-02-08, Initial commit immediately followed by the plugin fork). The 2020 came from the JetBrains headers, not from this project.

2. Pinned electron-builder toolchain (fix(plugin): commit)

Packaging invoked npx --yes electron-builder@26.15.5. That pinned only the top-level version: the ~275 transitive packages were re-resolved from the registry on every build, with no lock state to check them against and with install scripts enabled. It runs on the machine holding the code-signing certificates and notarization credentials, so a compromise anywhere in that tree executed with those.

The plugin now embeds a package.json / package-lock.json pair pinning 277 packages (all from registry.npmjs.org, each with a sha512 integrity hash), stages it into a build-local directory, installs with npm ci --ignore-scripts, and runs the CLI directly via node node_modules/electron-builder/cli.js. A tarball whose bytes do not match the recorded hash fails the build, and no pre/postinstall hook from that tree runs at all.

node and npm still come from the build machine — only the dependency tree is pinned. electron-builder's own run-time downloads (app-builder, 7-Zip, NSIS, snap/AppImage templates) stay outside any npm lock file; they are verified by its own checksums.

Also in this commit:

  • NodeJsDetector.detectNpm(), plus a shared detectSibling() helper for the nodePath override. The task requires both node and npm and says what each is for when one is missing.
  • MacDmgLzma.locateAppBuilder searched the npx cache, which no longer exists, and never looked in ELECTRON_BUILDER_CACHE where electron-builder 26 actually downloads app-builder. It checks that first now, so the differential-update blockmap is no longer silently dropped after the DMG is recompressed with LZMA.
  • scripts/update-electron-builder-lock.sh regenerates the lock file, resolving against the public registry explicitly and refusing to run when certificate verification has been switched off — a lock file resolved over an unverified connection would record hashes for bytes nothing authenticated.
  • ElectronBuilderToolchainLockTest guards the boundary offline: version pinned in step with the Kotlin constant, lockfileVersion 3, every package from the public registry, every one carrying a sha512 hash.

Verification

  • JARsplugin-1.0.0.jar and decorated-window-tao.jar rebuilt; both contain META-INF/THIRD_PARTY_NOTICES.md and the three files under META-INF/licenses/.
  • Nominal packaging — plugin published to Maven Local, minimal consumer project applying it with TargetFormat.Zip: packageZip provisions the toolchain and produces a signed 24 MB .app zip plus its .blockmap; build temporaries are cleaned up.
  • Negative test — one integrity hash in the embedded lock file tampered with (abbrev, a single character), plugin republished, task rerun: the build fails with npm error code EINTEGRITY. Lock file restored, nominal path revalidated.
  • Lock file provenance — regenerated with scripts/update-electron-builder-lock.sh on a trusted network; result identical to the committed one.
  • Regression:plugin:check, 331 tests, all six suites touching the changed code green. Two failures are pre-existing and environment-specific (LinuxSignerTest needs a usable gpg; RenameMacAppBundleTest depends on APFS Unicode normalization) — both reproduce identically on a clean worktree at the base commit.

Notes for review

  • The copyright holder and the year in LICENSE are the two judgement calls here; everything else is mechanical.
  • Packaging now needs npm on PATH in addition to node. Node 18+ ships npm 9, so the existing Node version check covers the lockfileVersion 3 floor (npm 7+) in practice. Say the word if you want an explicit npm version check for a cleaner error message.
  • Still open on the release path, out of scope here: separating the packaging step from the signing step, so npm code never runs while the certificates are reachable.

The repository is MIT-licensed but redistributes Apache-2.0 code whose
attribution was not carried:

  - 127 Kotlin files under plugin-build/plugin/src retain the
    "Copyright 20xx JetBrains s.r.o. ... Apache 2.0" header — the Gradle
    plugin is a fork of the Compose Multiplatform Gradle plugin — yet no
    Apache-2.0 text accompanied them and the notices did not mention it.
  - The vendored tao fork (Apache-2.0) and the three AccessKit crates
    (MIT OR Apache-2.0) compile into libnucleus_tao, which ships inside
    the decorated-window-tao JAR. accesskit_windows was also the only
    vendored crate not keeping its upstream LICENSE-APACHE/LICENSE-MIT.
  - LICENSE still carried the unfilled template line
    "Copyright (c) 2020 <Source Code Author>".

THIRD_PARTY_NOTICES.md now covers all four components plus the build-time
tools and a pointer to Cargo.lock for the transitive Rust tree, with the
license texts collected once under licenses/. The LICENSE year follows the
git history (first commit 2026-02-08); the 2020 came from the JetBrains
headers, not from this project.

Apache-2.0 §4(a) requires the license copy to travel with binary
distributions too, so the jar tasks of plugin-build/plugin and
decorated-window-tao now stage the notices and licenses/ into META-INF/.
… of npx

Packaging invoked `npx --yes electron-builder@26.15.5`, which pinned only
the top-level version: the ~275 transitive packages were re-resolved from
the registry on every build, with no lock state to check them against and
with install scripts enabled. That runs on the machine holding the
code-signing certificates and notarization credentials, so a compromise
anywhere in that tree executed with those.

The plugin now embeds a package.json / package-lock.json pair pinning 277
packages (all from registry.npmjs.org, each with a sha512 integrity hash),
stages it into a build-local directory, installs with
`npm ci --ignore-scripts`, and runs the CLI directly through
`node node_modules/electron-builder/cli.js`. A tarball whose bytes do not
match the recorded hash now fails the build (EINTEGRITY), and no
pre/postinstall hook from that tree runs at all.

node and npm still come from the build machine; only the dependency tree is
pinned. electron-builder's own run-time downloads (app-builder, 7-Zip, NSIS,
snap/AppImage templates) remain outside any npm lock file — they are
verified by its own checksums.

Also in this change:

  - NodeJsDetector.detectNpm(), plus a shared detectSibling() helper for the
    nodePath override; the task now requires both node and npm and says
    what each is for when one is missing.
  - MacDmgLzma.locateAppBuilder searched the npx cache, which no longer
    exists, and never looked in ELECTRON_BUILDER_CACHE where
    electron-builder 26 actually downloads app-builder. It now checks that
    first, so the differential-update blockmap is no longer silently
    dropped after the DMG is recompressed with LZMA.
  - scripts/update-electron-builder-lock.sh regenerates the lock file,
    resolving against the public registry explicitly and refusing to run
    when certificate verification has been switched off — a lock file
    resolved over an unverified connection would record hashes for bytes
    nothing authenticated.
  - ElectronBuilderToolchainLockTest guards the boundary offline: version
    pinned in step with the Kotlin constant, lockfileVersion 3, every
    package from the public registry, every one carrying a sha512 hash.

Verified end to end against a minimal consumer project (TargetFormat.Zip):
the nominal path provisions the toolchain and produces the signed .app zip
plus its blockmap, and tampering with a single integrity hash in the
embedded lock file fails the build as intended.
@kdroidFilter
kdroidFilter merged commit f07de8d into nucleus-2.5 Aug 18, 2026
23 checks passed
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.

1 participant