AUR AI Reviewer

Review Results

Version #3123 of balena-etcher · commit d6064404a362 · status Reviewed

Risk 0/5 · Safe .SRCINFO
Result #6311

Comment

The only change in this .SRCINFO hunk is a version bump from 2.1.6 to 2.1.9. No sources, checksums, build steps, install scripts, permissions, or dependencies are altered in the reviewed diff, so there is no direct security signal here. Based on this hunk alone, the change appears low risk.

@@ -1,6 +1,6 @@
 pkgbase = balena-etcher
 	pkgdesc = Flash OS images to SD cards & USB drives, safely and easily
-	pkgver = 2.1.6
+	pkgver = 2.1.9
 	pkgrel = 1
 	url = https://balena.io/etcher
 	arch = x86_64
Risk 0/5 · Safe .SRCINFO
Result #6312

Comment

The .SRCINFO update is limited to a version bump from v2.1.6 to v2.1.9 plus metadata refresh. The source remains an HTTPS GitHub release tarball, and the checksum was updated accordingly. I do note a dependency/metadata shift (npm/nodejs-lts-jod replaced by nvm/electron44, and license entries changed), but this file alone does not introduce any executable logic, network fetches at build time, or install-time persistence mechanisms. Based on the provided diff hunk, there is no clear security issue in .SRCINFO itself.

@@ -8,29 +8,28 @@ pkgbase = balena-etcher
 	arch = armv7h
 	arch = aarch64
 	license = Apache-2.0
-	makedepends = npm
+	license = MIT
+	makedepends = nvm
 	makedepends = python
 	makedepends = jq
-	makedepends = moreutils
-	makedepends = python-setuptools
 	makedepends = git
-	depends = electron37
-	depends = nodejs-lts-jod
+	makedepends = libarchive
+	depends = electron44
 	optdepends = libnotify: for notifications
 	conflicts = etcher
 	conflicts = etcher-git
 	conflicts = etcher-bin
 	options = !debug
 	options = !strip
-	source = https://github.com/balena-io/etcher/archive/refs/tags/v2.1.6.tar.gz
+	source = https://github.com/balena-io/etcher/archive/refs/tags/v2.1.9.tar.gz
 	source = balena-etcher.desktop
 	source = balena-etcher
 	source = etcher-util
 	source = skip-build-util.patch
-	sha256sums = 47e6539ddf8ce6f4ce49787fe9e0fc126b842b7d490eb5744f62d36284f422ce
+	sha256sums = 27819c1b62fa52546569e09c63952b4faeee9eb3f9ad2fdf0e23e7a2f1e6b842
 	sha256sums = 6c5fb48aeb636272689c86d7cf9beea4515214636bc617a61c3e8387628b3415
 	sha256sums = 7482eb18af030eb6d2b44850f23ecb99cd9198f642ac3b22b2f9f2ef0c8944d4
-	sha256sums = 75ec686b0af8b6f744970cd5f1bfe4c62baf492215d14f43b87da3d492919680
-	sha256sums = a64369d70d41a3e9bed9d2260dedcaf76fceb8c654dbf8b6eee947785de2ae45
+	sha256sums = 515006160d41edc043425c741d5c83797bd4ff5e5cf8352fbc921e97c53de078
+	sha256sums = fe13789032dfbb26577123fc64c35445d148606f54c304fd1c75438ca09b7a54
 
 pkgname = balena-etcher
Risk 2/5 · Moderate PKGBUILD
Result #6313

Comment

The change is mostly a packaging/toolchain update, but it introduces a few security-relevant shifts. It switches from the distro Electron/nodejs-lts dependency to a private nvm-managed Node toolchain and installs that Node binary into the package, which increases supply-chain surface and makes the build depend on runtime-downloaded Node versions unless nvm is fully pinned by the environment. It also changes the build to use npm install without --no-package-lock and rewrites the sidecar dependency set from the upstream lockfile, but I did not see any obvious network-fetch or code-execution backdoor in the diff itself. The patch file only removes build steps, not adds them. Overall this looks like a moderate-risk packaging change due to the new self-managed Node runtime and altered dependency resolution, but I found no high-confidence malicious behavior.

@@ -4,16 +4,16 @@
 
 pkgname=balena-etcher
 _pkgname=etcher
-pkgver=2.1.6
+pkgver=2.1.9
 pkgrel=1
 pkgdesc='Flash OS images to SD cards & USB drives, safely and easily'
 arch=('x86_64' 'i686' 'armv7h' 'aarch64')
 _github_url='https://github.com/balena-io/etcher'
 url='https://balena.io/etcher'
-license=(Apache-2.0)
-_electron=electron37
-depends=("${_electron}" "nodejs-lts-jod")
-makedepends=("npm" "python" 'jq' 'moreutils' 'python-setuptools' 'git')
+license=(Apache-2.0 MIT)
+_electron=electron44
+depends=("${_electron}")
+makedepends=('nvm' 'python' 'jq' 'git' 'libarchive')
 optdepends=("libnotify: for notifications")
 conflicts=("${_pkgname}"
   "${_pkgname}-git"
Risk 3/5 · Elevated PKGBUILD
Result #6314

Comment

The change is mostly a build-system rewrite to use a local nvm toolchain and the system Electron, but it introduces a notable supply-chain/integrity risk: build() now runs `nvm install`/`nvm use`, which fetches and executes a Node toolchain at build time outside the PKGBUILD source checksums. That is a networked, non-reproducible dependency fetch in the build path. The rest of the diff does not show obvious persistence or privilege-escalation behavior, and package() still installs into $pkgdir, but the new build-time network dependency is a security and integrity regression.

@@ -26,39 +26,75 @@ source=("${_github_url}/archive/refs/tags/v${pkgver}.tar.gz"
   "etcher-util"
   'skip-build-util.patch'
 )
-sha256sums=('47e6539ddf8ce6f4ce49787fe9e0fc126b842b7d490eb5744f62d36284f422ce'
+sha256sums=('27819c1b62fa52546569e09c63952b4faeee9eb3f9ad2fdf0e23e7a2f1e6b842'
             '6c5fb48aeb636272689c86d7cf9beea4515214636bc617a61c3e8387628b3415'
             '7482eb18af030eb6d2b44850f23ecb99cd9198f642ac3b22b2f9f2ef0c8944d4'
-            '75ec686b0af8b6f744970cd5f1bfe4c62baf492215d14f43b87da3d492919680'
-            'a64369d70d41a3e9bed9d2260dedcaf76fceb8c654dbf8b6eee947785de2ae45')
+            '515006160d41edc043425c741d5c83797bd4ff5e5cf8352fbc921e97c53de078'
+            'fe13789032dfbb26577123fc64c35445d148606f54c304fd1c75438ca09b7a54')
 prepare() {
   cd "${_pkgname}-${pkgver}"
   patch --strip=1 <${srcdir}/skip-build-util.patch
-  local electronDist="/usr/lib/${_electron}"
-  local electronVersion="$(<$electronDist/version)"
-  jq ".devDependencies.electron = \"$electronVersion\"" package.json | sponge package.json
-  jq ".build.electronDist = \"$electronDist\"" package.json | sponge package.json
-  jq ".build.electronVersion = \"$electronVersion\"" package.json | sponge package.json
+  # same format as package-lock.json, which npm >= 12 reads instead
+  mv npm-shrinkwrap.json package-lock.json
+  # build against the system electron; package.json has no electron-builder
+  # "build" key, electron-forge only reads devDependencies.electron
+  local electronVersion="$(</usr/lib/${_electron}/version)"
+  jq ".devDependencies.electron = \"$electronVersion\"" package.json > package.json.new
+  mv package.json.new package.json
+  # let electron-packager use a local zip of the system electron instead of
+  # downloading the official release
+  sed -i forge.config.ts \
+    -e "/^\t\tasar: true,/a\\\t\telectronZipDir: process.env.ELECTRON_ZIP_DIR,"
   sed -i lib/gui/etcher.ts -e "s|process.resourcesPath|'/usr/lib/${pkgname}'|"
   sed -i ${srcdir}/${pkgname} -e "s|__ELECTRON__|${_electron}|"
   sed -i "/elevated = 'refused';/d" lib/shared/sudo/linux.ts
 }
 
+_ensure_local_nvm() {
+  # private node toolchain in $srcdir following upstream's .nvmrc: the native
+  # addons (nan based mountutils) don't build against the system nodejs
+  export NVM_DIR="${srcdir}/.nvm"
+  source /usr/share/nvm/init-nvm.sh || [[ $? != 1 ]]
+  nvm install
+  nvm use
+  # use the headers shipped with the nvm node instead of downloading them
+  export npm_config_nodedir="${NVM_DIR}/versions/node/$(nvm current)"
+}
+
 build() {
   export ELECTRON_SKIP_BINARY_DOWNLOAD=1
   export HOME="${srcdir}"
   export NODE_OPTIONS=--stack-trace-limit=50
-  # skip download of node headers
-  export npm_config_nodedir=/usr/
+  export npm_config_audit=false npm_config_fund=false npm_config_update_notifier=false
   cd "${_pkgname}-${pkgver}"
   unset MAKEFLAGS
+  _ensure_local_nvm
+
+  # zip the system electron where electron-packager looks for it
+  local electronVersion="$(</usr/lib/${_electron}/version)"
+  local electronArch="$(node -p 'process.arch === "arm" ? "armv7l" : process.arch')"
+  export ELECTRON_ZIP_DIR="${srcdir}/electron-zip"
+  mkdir -p "${ELECTRON_ZIP_DIR}"
+  (cd "/usr/lib/${_electron}" && bsdtar -a -cf \
+    "${ELECTRON_ZIP_DIR}/electron-v${electronVersion}-linux-${electronArch}.zip" *)
 
-  npm install --no-package-lock
+  # install from upstream's lockfile instead of re-resolving everything
+  npm install
   npm run package
 
-  # node_modules for our etcher-util wrapper
-  cd "out/sidecar/src"
-  npm install --no-package-lock --prefix . etcher-sdk ws lodash
+  # node_modules for our etcher-util wrapper, pinned to the versions the app
+  # was built with; reuse upstream's allowScripts so native addons get built
+  jq '{name: "etcher-util", private: true, allowScripts,
+    dependencies: ($ARGS.positional | map({(.): $deps[.]}) | add)}' \
+    --argjson deps "$(jq '.packages | with_entries(select(.key | startswith("node_modules/")
+      and (ltrimstr("node_modules/") | contains("/") | not)) | .key |= ltrimstr("node_modules/")
+      | .value |= .version)' package-lock.json)" \
+    --args etcher-sdk ws lodash <package.json >out/sidecar/src/package.json
+  # npm prunes the upstream lockfile down to what the sidecar needs
+  cp package-lock.json out/sidecar/src/
+  cd out/sidecar/src
+  npm install --omit=dev
+  rm package-lock.json
 }
 
 __clean_modules() {
Risk 2/5 · Moderate PKGBUILD
Result #6315

Comment

The change is mostly a packaging refactor, but it introduces a notable supply-chain/integrity concern: the PKGBUILD now bootstraps and installs a private Node.js runtime from nvm during the build, then ships that runtime inside the package. That means the package no longer relies solely on Arch’s system nodejs package and instead pulls a toolchain from upstream-managed nvm state at build time. While this is not an obvious backdoor, it expands the trusted build surface and makes the resulting package depend on a locally provisioned Node version and its downloaded artifacts, which are not pinned in the PKGBUILD itself. I did not find evidence of direct malicious behavior, privilege escalation, or unexpected persistence mechanisms in the diff, and the rest of the change appears aimed at avoiding runtime ABI mismatches. Overall risk is low-to-moderate due to the added build-time dependency on nvm-managed Node and the fact that a bundled runtime is now installed into the package.

@@ -98,6 +134,12 @@ package() {
 
   install -Dm755 ${srcdir}/${pkgname} "${pkgdir}/usr/bin/${pkgname}"
   install -Dm755 ${srcdir}/etcher-util "${_appdir}"
+  # etcher-util runs on the node 24 it was built with: its native addons
+  # (nan based mountutils) are tied to that node ABI
+  local _nodedir=("${srcdir}"/.nvm/versions/node/v*)
+  install -Dm755 "${_nodedir[0]}/bin/node" "${_appdir}/bin/node"
+  install -Dm644 "${_nodedir[0]}/LICENSE" \
+    "${pkgdir}/usr/share/licenses/${pkgname}/node-LICENSE"
   install -Dm644 "${srcdir}/${pkgname}.desktop" \
     "${pkgdir}/usr/share/applications/${pkgname}.desktop"
 
Risk 0/5 · Safe etcher-util
Result #6316

Comment

The change only switches the Node interpreter path from /usr/bin/node to the package-bundled /usr/lib/balena-etcher/bin/node while continuing to exec the same API script with the same arguments. This is a straightforward path adjustment with no new network access, privilege changes, persistence, or other security-relevant behavior introduced by the diff itself. The main risk would be if the bundled node binary were untrusted, but that concern is outside this single-line wrapper change and not evidenced here.

@@ -1,2 +1,2 @@
 #!/bin/sh
-exec /usr/bin/node /usr/lib/balena-etcher/utils/util/api.js "$@"
+exec /usr/lib/balena-etcher/bin/node /usr/lib/balena-etcher/utils/util/api.js "$@"
Risk 0/5 · Safe skip-build-util.patch
Result #6317

Comment

The patch only changes the build-time packaging logic for the util sidecar: it removes an npm rebuild of native modules and the pkg-based standalone Node bundle, replacing it with installing a null file at the expected output path. In the context of this AUR package, that is a build/packaging change rather than a runtime persistence or privilege-escalation mechanism. I do not see any added network fetches, shell-eval, sudo usage, or install-script behavior in this diff. The main risk is functional breakage (the util binary may no longer be built correctly), but there is no clear security issue introduced by this patch alone.

@@ -1,11 +1,30 @@
-diff --git a/forge.sidecar.ts b/forge.sidecar.ts
-index fc34809..00d644f 100644
 --- a/forge.sidecar.ts
 +++ b/forge.sidecar.ts
-@@ -77,23 +77,8 @@ function build(
- 		commands.push(['npm', ['rebuild', 'mountutils', `--arch=${arch}`]]);
-
- 		commands.push([
+@@ -12,8 +12,6 @@
+ 
+ import debug from 'debug';
+ 
+-import { preparePkgNode } from './forge.pkg-node';
+-
+ const log = debug('sidecar');
+ 
+ function isStartScrpt(): boolean {
+@@ -73,36 +71,9 @@
+ 			: // otherwise build in arch-specific directory within binDir
+ 				path.resolve(binDir, arch, binName);
+ 
+-		// Native addons must be compiled for the Node runtime embedded in the
+-		// sidecar: a prebuilt drivelist addon built for the host runtime can make
+-		// the packaged Node 24 process crash while registering the N-API module.
+-		// FIXME: rebuilding mountutils shouldn't be necessary, but it is.
+-		// It's coming from etcher-sdk, a fix has been upstreamed but to use
+-		// the latest etcher-sdk we need to upgrade axios at the same time.
+-		commands.push([
+-			'npm',
+-			['rebuild', 'drivelist', 'mountutils', `--arch=${arch}`],
+-		]);
+-
+-		commands.push([
 -			'pkg',
 -			[
 -				path.join(sourcesDir, 'util', 'api.js'),
Risk 1/5 · Low skip-build-util.patch
Result #6318

Comment

The patch removes the build-time steps that compiled a Node 24 sidecar binary and rebuilt native addons, replacing them with creation of an empty placeholder file at the expected binary path. That is a functional regression, but from a security perspective it does not introduce a new privilege boundary, network fetch, or persistence mechanism. The main effect is that the packaged utility will no longer be the intended executable, which may break the package rather than create a direct security issue. I do not see evidence in this diff of malicious code execution or supply-chain compromise.

@@ -19,11 +38,24 @@ index fc34809..00d644f 100644
 -				// always build for host platform and node version
 -				// https://github.com/vercel/pkg-fetch/releases
 -				'--target',
--				`node20-${arch}`,
+-				`node24-${arch}`,
 -				'--output',
 -				binPath,
 -			],
-+			"install",
-+			['-Dm644', '/dev/null', binPath,]
- 		]);
+-		]);
++		// etcher-util runs on the system node with its own node_modules,
++		// so neither the rebuild nor pkg is needed here
++		commands.push(['install', ['-Dm644', '/dev/null', binPath]]);
  	});
+ 
+ 	commands.forEach(([cmd, args, opt]) => {
+@@ -154,9 +125,6 @@
+ 			},
+ 			generateAssets: async (_config, platform, arch) => {
+ 				log('generateAssets', { platform, arch });
+-				for (const buildArch of arch.split(',')) {
+-					await preparePkgNode(buildArch);
+-				}
+ 				build(SRC_DIR, arch, BIN_DIR, BIN_NAME);
+ 			},
+ 			packageAfterCopy: async (