paseo
The npx usage in build() is for rebuilding a local native addon (node-pty) from the project's own source, not executing arbitrary remote code; the package builds from a verifiable upstream source tarball and does not run untrusted remote payloads.
Triggered rules
llm_review
The static rules flagged this MEDIUM, but an AI model (qwen/qwen3-235b-a22b-2507) reviewed the full PKGBUILD and judged it LOW (confidence 95%): The npx usage in build() is for rebuilding a local native addon (node-pty) from the project's own source, not executing arbitrary remote code; the package builds from a verifiable upstream source tarball and does not run untrusted remote payloads.
1 higher static finding superseded - not the current verdict (shown for transparency)
remote_code_tool
`npx`/`bunx`/`pnpm dlx`/`deno run <url>` downloads AND runs a remote package at build time — the moral equivalent of piping a download into a shell. Severity downgraded: Node.js consumer context.
-
PKGBUILD:112
(cd "$(_node_pty_dir)" && npx node-gyp rebuild) -
PKGBUILD:119
(cd packages/app && PASEO_WEB_PLATFORM=electron npx expo export --platform web)
PKGBUILD
2 offending line(s) highlighted# Maintainer: jinzhongjia <mail@nvimer.org>
pkgname=paseo
pkgver=0.8.0
pkgrel=1
pkgdesc="One interface for all your Claude Code, Codex and OpenCode agents (built from source, runs on system Electron)"
arch=('x86_64')
url="https://paseo.sh"
_github_url="https://github.com/getpaseo/paseo"
# Keep in sync with packages/desktop/package.json devDependencies.electron —
# prepare() fails the build when the majors drift apart.
_electron_pkg=electron44
license=('Apache-2.0')
depends=("${_electron_pkg}" 'gcc-libs' 'glibc' 'hicolor-icon-theme' 'nodejs>=22')
makedepends=('npm' 'python' 'git')
optdepends=('git: agent worktree management')
conflicts=('paseo-bin' 'paseo-desktop-bin' 'paseo-appimage')
options=('!debug')
install=paseo.install
source=(
"${pkgname}-${pkgver}.tar.gz::${_github_url}/archive/refs/tags/v${pkgver}.tar.gz"
'paseo.sh'
'paseo.desktop'
'paseo.service'
'paseo-daemon-session.sh'
'trace-desktop.mjs'
'system-electron-paths.patch'
)
sha256sums=('8b57de8635834de7fc2de15cdc49df9387f438a6ccff14c358150ef5995feb3d'
'f9e194a879a87d87021ad06f489a9e4197ded629055a38e2da3557a423785de4'
'6ae9c520668f639a22f17df7814548056ee46aa99a2886639405297a7b1ef212'
'df0d01b98ac405c5c25edbb91d61bb9e05355a57e0e652e00823d6331618d686'
'0bd531415e7504c4bbff0ce137a5541a4ba7d0c29281139b29d94ee537fde307'
'9c76df40b274123e128228dc841f44f018e4ffd8473a97f0a6b7a9c8a4c2e4fa'
'a30964e6b5767a12af0ccaa1a67b325a54990ebd386fef8a8326847cc54b955d')
# Repo-relative path of the installed node-pty. npm hoists it to the root
# node_modules in some releases and nests it under packages/server in others
# (0.3.1 nests it), so resolve it instead of hardcoding either location.
_node_pty_dir() {
node -p "const p=require('node:path');p.relative(process.cwd(),p.dirname(require.resolve('node-pty/package.json',{paths:['packages/server']})))"
}
prepare() {
cd "${pkgname}-${pkgver}"
# Refuse to build against the wrong Electron major: the app would compile
# fine but hit runtime API drift. Bump _electron_pkg= (and pkgrel) when
# upstream moves on.
local _upstream_electron
_upstream_electron="$(node -p "require('./packages/desktop/package.json').devDependencies.electron.match(/\d+/)[0]")"
if [[ "electron${_upstream_electron}" != "${_electron_pkg}" ]]; then
printf 'ERROR: upstream now pins electron %s but this package uses %s; update _electron_pkg= in the PKGBUILD\n' \
"${_upstream_electron}" "${_electron_pkg}" >&2
return 1
fi
# Arch's system electron reports app.isPackaged=true, so the app takes
# its packaged code paths; point the electron-builder resource lookups
# (process.resourcesPath / app.asar*) at /usr/lib/paseo instead, and
# leave updates to pacman rather than electron-updater.
#
# The patch used to carry a sixth hunk redirecting getBundledSkillsDir()
# in packages/desktop/src/integrations/skills/paths.ts. 0.5.1 deleted that
# file: skills resolution moved to packages/server's
# orchestration-skills/internal/paths.ts, which resolves relative to its
# own compiled location and takes the first candidate that exists instead
# of reading process.resourcesPath. Its first candidate lands on
# packages/server/dist/server/skills, which the trace already installs
# (byte-identical to the top-level skills/ that package() checks for), so
# nothing needs redirecting and the hunk was dropped rather than ported.
#
# A hunk for features/editor-targets/runtime.ts was added instead:
# iconPath() reads the editor-target icons from process.resourcesPath, and
# listEditorTargets() awaits describe() -> loadIcon() for every installed
# editor with no try/catch anywhere in the chain. Under system Electron
# that path resolves into electron*/resources, so the read threw ENOENT and
# the whole editor-target list failed for anyone with a supported editor
# installed — not just a missing icon. Point it at the assets we ship.
patch -Np1 -i "${srcdir}/system-electron-paths.patch"
# Keep npm state inside $srcdir; skip lifecycle scripts (no electron /
# onnxruntime binary downloads — mirrors upstream nix/desktop-package.nix).
export npm_config_cache="${srcdir}/npm-cache"
export npm_config_update_notifier=false
# Survive transient registry ECONNRESETs (npm defaults to only 2 retries).
export npm_config_fetch_retries=5
export npm_config_fetch_retry_mintimeout=20000
export npm_config_fetch_retry_maxtimeout=120000
npm ci --ignore-scripts --no-audit --no-fund
# Root postinstall applies patches/ via patch-package.
npm run postinstall
}
build() {
cd "${pkgname}-${pkgver}"
export npm_config_cache="${srcdir}/npm-cache"
export npm_config_update_notifier=false
# build() still hits the network (node-gyp headers, expo); same retry bump.
export npm_config_fetch_retries=5
export npm_config_fetch_retry_mintimeout=20000
export npm_config_fetch_retry_maxtimeout=120000
export EXPO_NO_TELEMETRY=1
export CI=1
# node-pty is the only native addon the daemon needs (N-API, so it stays
# ABI-compatible with the Electron runtime). Compiled directly with
# node-gyp: `npm rebuild` is skipped by npm 11's allowScripts gate and by
# the npm-shipped prebuild.
(cd "$(_node_pty_dir)" && npx node-gyp rebuild)
# Mirrors upstream nix/desktop-package.nix — tsc workspaces, Expo web
# export for the renderer, then the desktop main process. No
# electron-builder involved.
npm run build:server
npm run build --workspace=@getpaseo/expo-two-way-audio
(cd packages/app && PASEO_WEB_PLATFORM=electron npx expo export --platform web)
npm run build:main --workspace=@getpaseo/desktop
}
package() {
cd "${pkgname}-${pkgver}"
# Runtime closure by static module-graph tracing: upstream's daemon/CLI
# trace (PASEO_TRACE_DESKTOP=1 also pulls in the Electron main process and
# its preloads) plus our supplement, instead of the 2 GB node_modules.
PASEO_TRACE_DESKTOP=1 node scripts/trace-daemon.mjs > "${srcdir}/runtime-files.txt" \
2> "${srcdir}/trace-stderr.log" \
|| { cat "${srcdir}/trace-stderr.log" >&2; return 1; }
node "${srcdir}/trace-desktop.mjs" >> "${srcdir}/runtime-files.txt" \
2>> "${srcdir}/trace-stderr.log" \
|| { cat "${srcdir}/trace-stderr.log" >&2; return 1; }
# Known-benign trace noise is dropped; anything unexpected still prints.
# ws/node-fetch probe optional native accelerators (bufferutil,
# utf-8-validate, encoding) that the lockfile never installs — pure-JS
# fallbacks are used. nft also tries to parse non-JS assets it carries
# (a stray src/*.ts, zsh shell-integration dotfiles) and logs the
# resulting token errors.
grep -vE "utf-8-validate|bufferutil|[\"']encoding[\"']|Failed to parse .*(packages/server/src/|shell-integration)|^Unexpected token" \
"${srcdir}/trace-stderr.log" >&2 || true
local _libdir="${pkgdir}/usr/lib/paseo"
install -d "${_libdir}"
# Root manifest lets node resolve the workspace layout at runtime.
cp package.json "${_libdir}/"
# Copy file-by-file, preserving workspace symlinks
# (node_modules/@getpaseo/* -> packages/*). Directory entries are skipped:
# their contents are listed individually, and copying a directory onto a
# path that already exists would nest instead of merge.
# node-pty's npm-shipped prebuilds are dropped: its loader prefers the
# build/Release addon we compile ourselves (lib/utils.js search order).
sort -u "${srcdir}/runtime-files.txt" | grep -v 'node_modules/node-pty/prebuilds/' | \
while IFS= read -r f; do
if [ ! -e "$f" ] && [ ! -L "$f" ]; then
continue
fi
if [ -d "$f" ] && [ ! -L "$f" ]; then
continue
fi
install -d "${_libdir}/$(dirname "$f")"
cp -a "$f" "${_libdir}/$f"
done
# Under `electron <dir>` (default_app), Electron derives the Wayland app_id
# from the loaded package.json "name" *before* main.js runs, so the in-app
# app.setName("Paseo") is too late and the window reports "getpaseo-desktop"
# (sanitised @getpaseo/desktop) — GNOME then can't match it to paseo.desktop
# and shows no icon. Rename the installed manifest so the app_id is "Paseo",
# matching StartupWMClass= (and paseo-bin). Runtime module resolution is
# path-based via the node_modules/@getpaseo/* symlinks, so the name field is
# never read at runtime; npm tooling that does isn't present post-install.
local _desktop_manifest="${_libdir}/packages/desktop/package.json"
sed -i 's#"name": "@getpaseo/desktop"#"name": "Paseo"#' "${_desktop_manifest}"
if grep -q '"name": "@getpaseo/desktop"' "${_desktop_manifest}"; then
printf 'ERROR: failed to rewrite desktop package.json name for the Wayland app_id\n' >&2
return 1
fi
# Launcher tracks _electron_pkg so an Electron bump is a one-line change.
install -d "${pkgdir}/usr/bin"
sed "s/@ELECTRON@/${_electron_pkg}/g" "${srcdir}/paseo.sh" > "${pkgdir}/usr/bin/paseo"
chmod 755 "${pkgdir}/usr/bin/paseo"
install -Dm644 "${srcdir}/paseo.desktop" \
"${pkgdir}/usr/share/applications/paseo.desktop"
# Session-scoped daemon launcher (node CLI entry, full login-shell env) and
# the static user unit that runs it. Started via XDG autostart, not enabled
# — see paseo.install.
install -Dm755 "${srcdir}/paseo-daemon-session.sh" \
"${pkgdir}/usr/bin/paseo-daemon-session"
install -Dm644 "${srcdir}/paseo.service" \
"${pkgdir}/usr/lib/systemd/user/paseo.service"
install -Dm644 packages/app/dist/pwa-icon-192.png \
"${pkgdir}/usr/share/icons/hicolor/192x192/apps/paseo.png"
install -Dm644 packages/app/dist/pwa-icon-512.png \
"${pkgdir}/usr/share/icons/hicolor/512x512/apps/paseo.png"
install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
# Guard against trace regressions: every path here is load-bearing for
# the CLI, the daemon spawn chain, or the renderer. A miss fails the
# build instead of surfacing as a runtime error after install.
local _required=(
usr/bin/paseo
usr/bin/paseo-daemon-session
usr/lib/paseo/package.json
usr/lib/paseo/packages/cli/bin/paseo
usr/lib/paseo/packages/desktop/dist/main.js
usr/lib/paseo/packages/desktop/dist/preload.js
usr/lib/paseo/packages/desktop/dist/features/browser-keyboard/guest-preload.js
usr/lib/paseo/packages/desktop/dist/daemon/node-entrypoint-runner.js
usr/lib/paseo/packages/desktop/package.json
usr/lib/paseo/packages/cli/dist/index.js
usr/lib/paseo/packages/cli/dist/run.js
usr/lib/paseo/packages/server/dist/scripts/supervisor-entrypoint.js
usr/lib/paseo/node_modules/@getpaseo/cli
usr/lib/paseo/node_modules/@getpaseo/server
"usr/lib/paseo/$(_node_pty_dir)/build/Release/pty.node"
usr/lib/paseo/packages/app/dist/index.html
usr/lib/paseo/skills
# Hardcoded by system-electron-paths.patch: the window icon and the
# editor-target icons. Upstream reads both out of process.resourcesPath,
# which under system Electron points into electron*/resources instead of
# here, so the patch redirects them and these must ship.
usr/lib/paseo/packages/desktop/assets/icon.png
usr/lib/paseo/packages/desktop/assets/editor-targets/vscode.png
)
local _f
for _f in "${_required[@]}"; do
if [[ ! -e "${pkgdir}/${_f}" ]]; then
printf 'ERROR: runtime closure check failed: missing %s\n' "${_f}" >&2
return 1
fi
done
}
Changes since previous scan
--- PKGBUILD @ 2026-09-12 00:25+++ PKGBUILD @ 2026-09-17 00:27@@ -1,7 +1,7 @@ # Maintainer: jinzhongjia <mail@nvimer.org> pkgname=paseo-pkgver=0.7.2+pkgver=0.8.0 pkgrel=1 pkgdesc="One interface for all your Claude Code, Codex and OpenCode agents (built from source, runs on system Electron)" arch=('x86_64')@@ -9,10 +9,10 @@ _github_url="https://github.com/getpaseo/paseo" # Keep in sync with packages/desktop/package.json devDependencies.electron — # prepare() fails the build when the majors drift apart.-_electron_pkg=electron41-license=('AGPL-3.0-or-later')-depends=("${_electron_pkg}" 'gcc-libs' 'glibc' 'hicolor-icon-theme')-makedepends=('nodejs' 'npm' 'python' 'git')+_electron_pkg=electron44+license=('Apache-2.0')+depends=("${_electron_pkg}" 'gcc-libs' 'glibc' 'hicolor-icon-theme' 'nodejs>=22')+makedepends=('npm' 'python' 'git') optdepends=('git: agent worktree management') conflicts=('paseo-bin' 'paseo-desktop-bin' 'paseo-appimage') options=('!debug')@@ -26,13 +26,13 @@ 'trace-desktop.mjs' 'system-electron-paths.patch' )-sha256sums=('8ff41592068771394cbcd9276d89ac7c1d8332f9f2660515dd62bd0d2130279f'- '5f744a24a3605f78ee30348e1d705f47d803f915e58e076ea6e11f151d678407'+sha256sums=('8b57de8635834de7fc2de15cdc49df9387f438a6ccff14c358150ef5995feb3d'+ 'f9e194a879a87d87021ad06f489a9e4197ded629055a38e2da3557a423785de4' '6ae9c520668f639a22f17df7814548056ee46aa99a2886639405297a7b1ef212' 'df0d01b98ac405c5c25edbb91d61bb9e05355a57e0e652e00823d6331618d686' '0bd531415e7504c4bbff0ce137a5541a4ba7d0c29281139b29d94ee537fde307' '9c76df40b274123e128228dc841f44f018e4ffd8473a97f0a6b7a9c8a4c2e4fa'- '94bd85443217eb476eeac64a35e2cd2ccd2c192643fe35ad12078b15f4768c06')+ 'a30964e6b5767a12af0ccaa1a67b325a54990ebd386fef8a8326847cc54b955d') # Repo-relative path of the installed node-pty. npm hoists it to the root # node_modules in some releases and nests it under packages/server in others@@ -183,7 +183,7 @@ # Launcher tracks _electron_pkg so an Electron bump is a one-line change. install -d "${pkgdir}/usr/bin"- sed "s/electron41/${_electron_pkg}/g" "${srcdir}/paseo.sh" > "${pkgdir}/usr/bin/paseo"+ sed "s/@ELECTRON@/${_electron_pkg}/g" "${srcdir}/paseo.sh" > "${pkgdir}/usr/bin/paseo" chmod 755 "${pkgdir}/usr/bin/paseo" install -Dm644 "${srcdir}/paseo.desktop" \Scan history
| Scanned at (UTC) | Severity | Rules |
|---|---|---|
| 2026-09-17 00:27:14 | Low | 2 |
| 2026-09-16 00:03:17 | Low | 2 |
| 2026-09-15 00:25:31 | Low | 2 |
| 2026-09-14 00:27:57 | Low | 2 |
| 2026-09-13 00:19:54 | Low | 2 |
| 2026-09-12 21:12:39 | Medium | 1 |
| 2026-09-12 00:25:17 | Low | 2 |
| 2026-09-11 00:19:22 | Low | 2 |
| 2026-09-10 00:22:44 | Low | 2 |
| 2026-09-09 00:04:09 | Low | 2 |
| 2026-09-08 00:18:08 | Low | 2 |
| 2026-09-07 00:30:15 | Low | 2 |
| 2026-09-06 00:17:06 | Low | 2 |
| 2026-09-05 00:16:27 | Low | 2 |
| 2026-09-04 00:03:13 | Low | 2 |
| 2026-09-03 03:52:21 | Medium | 1 |
| 2026-09-03 00:15:47 | Low | 2 |
| 2026-09-02 00:02:31 | Low | 2 |
| 2026-09-01 00:11:19 | Low | 2 |
| 2026-08-31 00:19:57 | Low | 2 |