opencode-desktop-bin
The package downloads prebuilt .deb binaries from a non-whitelisted host (opencode.ai), which are then extracted and repackaged; while the checksums are verified, the source is a third-party binary distribution not built from publicly auditable source, creating a supply-chain risk if the host is compromised.
Triggered rules
external_download_not_in_source
curl/wget fetches a URL on a non-allowlisted host that is not part of source=(), so it is not checksum-verified by makepkg.
-
PKGBUILD:29
curl -fsSL https://opencode.ai/update/api/latest/desktop/opencode | jq -r '.version'
source_untrusted_domain
One or more source=() URLs point to a host outside the trusted allowlist (github.com, gitlab.com, codeberg.org, pypi.org, …).
-
PKGBUILD:33
source_x86_64=("opencode-desktop-${pkgver}-linux-amd64.deb::https://opencode.ai/files/bin/${pkgver}/opencode-desktop-linux-amd64.deb")
llm_review
An AI model (qwen/qwen3-235b-a22b-2507) reviewed this and agrees it is MEDIUM (confidence 90%): The package downloads prebuilt .deb binaries from a non-whitelisted host (opencode.ai), which are then extracted and repackaged; while the checksums are verified, the source is a third-party binary distribution not built from publicly auditable source, creating a supply-chain risk if the host is compromised.
PKGBUILD
2 offending line(s) highlighted# Maintainer: Yakov Till <yakov.till@gmail.com>
pkgname=opencode-desktop-bin
pkgver=2.2.5
pkgrel=1
pkgdesc="OpenCode desktop client"
arch=('x86_64' 'aarch64')
url="https://opencode.ai"
license=('MIT')
provides=('opencode-desktop')
conflicts=('opencode-desktop')
# Arch Linux ARM ships no electron package at all (which is also why official
# code is absent there), so only x86_64 can run on a system runtime.
_electron=electron44
# Upstream names the bundled Electron binary after the app id and has already
# renamed it once (ai.opencode.desktop -> ai.opencode), so keep the name in a
# single place; the desktop Exec rewrite matches by path, not by name.
_app_bin=ai.opencode
depends=('ripgrep')
depends_x86_64=("${_electron}")
depends_aarch64=('gtk3' 'nss' 'libxss' 'libxtst' 'alsa-lib' 'libsecret' 'libnotify' 'xdg-utils')
makedepends_x86_64=('asar')
optdepends=('libappindicator-gtk3: tray icon support')
# opencode-cli is a Bun standalone executable: stripping discards the embedded
# bundle and leaves a bare Bun runtime that no longer knows the "serve" command.
options=('!strip' '!debug')
latestver() {
curl -fsSL https://opencode.ai/update/api/latest/desktop/opencode | jq -r '.version'
}
source=("LICENSE::https://raw.githubusercontent.com/anomalyco/opencode/v${pkgver}/LICENSE")
source_x86_64=("opencode-desktop-${pkgver}-linux-amd64.deb::https://opencode.ai/files/bin/${pkgver}/opencode-desktop-linux-amd64.deb")
source_aarch64=("opencode-desktop-${pkgver}-linux-arm64.deb::https://opencode.ai/files/bin/${pkgver}/opencode-desktop-linux-arm64.deb")
sha256sums=('625f0f619133f89bbbb2abe37369613dfa1885eba1e50d02170deb62bb42cb6b')
sha256sums_x86_64=('c827e714220de744c829fdea55540ed814b1372061e7a905fde0978f9f819de8')
sha256sums_aarch64=('7517f7ef9c0a9e88cf0060911de21f1c3dfbfd796f535ca62fec6c968d0ec7e3')
package() {
local _debarch=amd64
[[ "${CARCH}" == aarch64 ]] && _debarch=arm64
bsdtar -xf "${srcdir}/opencode-desktop-${pkgver}-linux-${_debarch}.deb" data.tar.xz
bsdtar -xf data.tar.xz -C "${pkgdir}"
local _appdir _exec
if [[ "${CARCH}" == x86_64 ]]; then
# The app only has to work on the Electron it was built against, so refuse
# to package a runtime pairing upstream never shipped.
local _bundled
_bundled=$(grep -aoP 'Electron/\K[0-9]+' "${pkgdir}/opt/OpenCode/${_app_bin}" | head -1)
if [[ "electron${_bundled}" != "${_electron}" ]]; then
echo "Upstream now bundles Electron ${_bundled:-<undetected>}; set _electron=electron${_bundled}" >&2
exit 1
fi
# Keep the app payload only; the bundled Chromium/Node runtime is replaced
# by the system electron, which ships its own sandbox, codecs and ICU data.
_appdir="${pkgdir}/usr/lib/opencode-desktop"
install -d "${_appdir}"
mv "${pkgdir}/opt/OpenCode/resources/"* "${_appdir}/"
rm -rf "${pkgdir}/opt"
# The app resolves opencode-cli relative to process.resourcesPath, which the
# system electron reports as its own directory. A shim app repoints it here
# and then loads the untouched asar.
asar extract-file "${_appdir}/app.asar" package.json
sed -i 's|"main": "./out/main/index.js"|"main": "./main.mjs"|' package.json
grep -q '"main": "./main.mjs"' package.json
install -Dm644 package.json "${_appdir}/package.json"
# The app stages its own copy of the bundled CLI under <userData>/cli/<version>
# and never prunes it, but skips the copy when that path already exists, so the
# shim pre-seeds a symlink and pacman keeps owning the only copy of the binary.
[[ -s "${_appdir}/opencode-cli.version" ]] || {
echo "upstream dropped opencode-cli.version; the CLI symlink seed has no version source" >&2
exit 1
}
install -Dm644 /dev/stdin "${_appdir}/main.mjs" <<'EOF'
import { app } from "electron"
import { existsSync, mkdirSync, readFileSync, symlinkSync } from "node:fs"
import { dirname, join } from "node:path"
Object.defineProperty(process, "resourcesPath", { value: import.meta.dirname, configurable: true })
const linkCli = (userData) => {
try {
const version = readFileSync(join(import.meta.dirname, "opencode-cli.version"), "utf8").trim()
if (!version) return
const link = join(userData, "cli", version.replace(/[^a-zA-Z0-9._-]/g, "-"), "opencode-cli")
if (existsSync(link)) return
mkdirSync(dirname(link), { recursive: true })
symlinkSync(join(import.meta.dirname, "opencode-cli"), link)
} catch {}
}
const setPath = app.setPath.bind(app)
app.setPath = (name, value) => {
setPath(name, value)
if (name === "userData") linkCli(value)
}
app.whenReady().then(() => linkCli(app.getPath("userData")))
await import("./app.asar/out/main/index.js")
EOF
_exec="${_electron} /usr/lib/opencode-desktop"
else
_appdir="${pkgdir}/opt/OpenCode/resources"
_exec="/opt/OpenCode/${_app_bin}"
fi
# Prune Debian/Ubuntu-specific and dead files
rm -rf "${pkgdir}/usr/share/doc"
rm -f "${_appdir}/apparmor-profile"
rm -f "${_appdir}/app-update.yml"
# Prune musl native modules (useless on glibc Arch)
find "${_appdir}" -name '*.musl.node' -delete
find "${_appdir}" -depth -type d -name '*-musl' -exec rm -rf {} +
# Launcher script (supports user flags and Wayland)
install -Dm755 /dev/stdin "${pkgdir}/usr/bin/opencode-desktop" <<'EOF'
#!/bin/bash
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
if [[ -f "$XDG_CONFIG_HOME/opencode-desktop-flags.conf" ]]; then
OPENCODE_USER_FLAGS="$(grep -v '^#' "$XDG_CONFIG_HOME/opencode-desktop-flags.conf")"
fi
exec @EXEC@ $OPENCODE_USER_FLAGS "$@"
EOF
sed -i "s|@EXEC@|${_exec}|" "${pkgdir}/usr/bin/opencode-desktop"
# Point desktop files at the wrapper (icons/StartupWMClass already match upstream names)
sed -i 's|Exec=/opt/OpenCode/[^[:space:]]*|Exec=opencode-desktop|' \
"${pkgdir}"/usr/share/applications/*.desktop
install -Dm644 "${srcdir}/LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
}
Changes since previous scan
--- PKGBUILD @ 2026-10-31 00:07+++ PKGBUILD @ 2026-11-04 00:25@@ -1,7 +1,7 @@ # Maintainer: Yakov Till <yakov.till@gmail.com> pkgname=opencode-desktop-bin-pkgver=2.2.3+pkgver=2.2.5 pkgrel=1 pkgdesc="OpenCode desktop client" arch=('x86_64' 'aarch64')@@ -33,8 +33,8 @@ source_x86_64=("opencode-desktop-${pkgver}-linux-amd64.deb::https://opencode.ai/files/bin/${pkgver}/opencode-desktop-linux-amd64.deb") source_aarch64=("opencode-desktop-${pkgver}-linux-arm64.deb::https://opencode.ai/files/bin/${pkgver}/opencode-desktop-linux-arm64.deb") sha256sums=('625f0f619133f89bbbb2abe37369613dfa1885eba1e50d02170deb62bb42cb6b')-sha256sums_x86_64=('98b6cccb1649e3c439bb1a5cf654c8310c6cf8b294a2444c540fd24cb781e99e')-sha256sums_aarch64=('592153e7ad851dc5429e4961c25cf6562d2be1e4764dbdcbcb6f71807bd9f45b')+sha256sums_x86_64=('c827e714220de744c829fdea55540ed814b1372061e7a905fde0978f9f819de8')+sha256sums_aarch64=('7517f7ef9c0a9e88cf0060911de21f1c3dfbfd796f535ca62fec6c968d0ec7e3') package() { local _debarch=amd64Scan history
| Scanned at (UTC) | Severity | Rules |
|---|---|---|
| 2026-11-04 00:25:23 | Medium | 3 |
| 2026-11-03 00:01:00 | Medium | 3 |
| 2026-11-02 00:26:00 | Medium | 3 |
| 2026-11-01 00:21:48 | Medium | 3 |
| 2026-10-31 01:57:41 | Medium | 3 |
| 2026-10-31 00:07:41 | Medium | 3 |
| 2026-10-30 15:56:05 | Medium | 3 |
| 2026-10-30 00:29:30 | Medium | 3 |
| 2026-10-29 15:52:51 | Medium | 3 |
| 2026-10-29 00:13:55 | Medium | 3 |
| 2026-10-28 00:06:54 | Medium | 3 |
| 2026-10-27 17:48:24 | Medium | 3 |
| 2026-10-27 00:23:28 | Medium | 3 |
| 2026-10-26 00:12:40 | Medium | 3 |
| 2026-10-25 00:20:48 | Medium | 3 |
| 2026-10-24 01:38:37 | Medium | 3 |
| 2026-10-24 00:14:55 | Medium | 3 |
| 2026-10-23 00:02:19 | Medium | 3 |
| 2026-10-22 17:35:31 | Medium | 3 |
| 2026-10-22 00:13:10 | Medium | 3 |