liquidsoap
maintainer wjhandley
· 0 votes
· scanned 2026-08-03 00:08:14.047287
LOW
View on AUR ↗
Why flagged
The package uses opam to fetch OCaml dependencies at build time, which is a normal and expected part of building OCaml projects; the resulting binary is self-contained and only links against standard system libraries at runtime, posing no remote code execution or supply-chain risk.
Triggered rules
LOW
AI review downgraded a static finding
llm_review
The static rules flagged this MEDIUM, but an AI model (qwen/qwen3-235b-a22b-07-25) reviewed the full PKGBUILD and judged it LOW (confidence 95%): The package uses opam to fetch OCaml dependencies at build time, which is a normal and expected part of building OCaml projects; the resulting binary is self-contained and only links against standard system libraries at runtime, posing no remote code execution or supply-chain risk.
-
PKGBUILD:113
opam install --yes "${_opam_packages[@]}"
1 higher static finding superseded - not the current verdict (shown for transparency)
MEDIUM
External install via pipx/uv/poetry/cargo/go/gem
alt_pkg_manager_install
A non-pip/npm package manager (pipx, uv, poetry, cargo install, go install, gem, conda…) fetches and builds an external package at build time, outside source=() and makepkg's checksums.
-
PKGBUILD:114
opam install --yes --deps-only liquidsoap-lang liquidsoap
PKGBUILD
2 offending line(s) highlighted
1
# Maintainer: Will Handley <wh260@cam.ac.uk>
2
#
3
# Liquidsoap is an OCaml-based scripting language and runtime for live audio
4
# composition. Upstream is built with opam (the OCaml package manager), which
5
# pulls in ~80 OCaml libraries. Rather than packaging each OCaml dependency
6
# as its own Arch package, this PKGBUILD uses opam at build time with a
7
# private OPAM root scoped to the build directory. The resulting native
8
# binary is then installed into $pkgdir via `dune install`.
9
#
10
# Build deps come from Arch (opam, ocaml, dune, system C libs); OCaml deps
11
# come from opam-repository at build time. Runtime deps are only the C
12
# shared libraries the binary links against, since `dune build --release`
13
# produces a self-contained native executable.
14
15
pkgname=liquidsoap
16
pkgver=2.4.4
17
pkgrel=1
18
pkgdesc='Swiss-army knife for multimedia streaming — scriptable live-audio engine used by Radio France, AzuraCast, Libretime'
19
arch=('x86_64')
20
url='https://www.liquidsoap.info/'
21
license=('GPL-2.0-or-later')
22
depends=(
23
'glibc'
24
'gcc-libs'
25
'curl'
26
'ffmpeg' # HLS input, audio/video decoding (linked by build)
27
'openssl' # HTTPS sources, TLS output (linked by build)
28
'libsamplerate' # high-quality resampling (linked by build)
29
)
30
makedepends=(
31
'opam'
32
'ocaml'
33
'dune'
34
'pkgconf'
35
'git'
36
'unzip'
37
'python'
38
)
39
source=("https://github.com/savonet/liquidsoap/archive/refs/tags/v${pkgver}.tar.gz")
40
sha256sums=('ed5ce71363f28e4f5a0d11eca2426cd973f9d0d929e1b864de93973a18e2d39c')
41
42
_opam_packages=(
43
# Core deps from liquidsoap.opam
44
'dune-build-info'
45
'dune-site'
46
'mm'
47
're'
48
'curl'
49
'camomile'
50
'uri'
51
'menhirLib'
52
'mem_usage'
53
'metadata'
54
'magic-mime'
55
'ppx_string'
56
'base-bytes'
57
# Optional but wanted for our use case
58
'ffmpeg' # HLS input + decoding
59
'ssl' # HTTPS sources
60
'samplerate' # Resampling
61
'posix-time2'
62
)
63
64
_opam_env() {
65
export OPAMROOT="${srcdir}/.opam"
66
export OPAMYES=1
67
export OPAMCONFIRMLEVEL=unsafe-yes
68
export OPAMVERBOSE=0
69
export OPAMCOLOR=never
70
# Use system OCaml, don't recompile it
71
export OPAMNOEXTERNALSOLVERFETCH=true
72
# opam's sandboxing uses bubblewrap which can clash with makepkg's
73
# restrictions; disable for the build.
74
export OPAMSANDBOXING=false
75
}
76
77
prepare() {
78
cd "${srcdir}/${pkgname}-${pkgver}"
79
80
_opam_env
81
82
if [[ ! -d "${OPAMROOT}" ]]; then
83
opam init \
84
--bare \
85
--no-setup \
86
--disable-shell-hook \
87
--disable-sandboxing \
88
--shell=bash \
89
--reinit
90
fi
91
92
if ! opam switch list --short 2>/dev/null | grep -qx build-switch; then
93
opam switch create build-switch --packages=ocaml-system --no-install
94
fi
95
96
eval "$(opam env --switch=build-switch --set-switch)"
97
98
# Pin local liquidsoap-* opam packages from this source tree so opam
99
# resolves deps against this version and doesn't pull liquidsoap-lang
100
# from opam-repository.
101
cd opam
102
for f in liquidsoap-lang.opam liquidsoap.opam; do
103
opam pin add --no-action --kind=path "${f%.opam}" "${srcdir}/${pkgname}-${pkgver}"
104
done
105
}
106
107
build() {
108
cd "${srcdir}/${pkgname}-${pkgver}"
109
110
_opam_env
111
eval "$(opam env --switch=build-switch --set-switch)"
112
113
opam install --yes "${_opam_packages[@]}"
114
opam install --yes --deps-only liquidsoap-lang liquidsoap
115
116
dune build --release --profile=release
117
}
118
119
package() {
120
cd "${srcdir}/${pkgname}-${pkgver}"
121
122
_opam_env
123
eval "$(opam env --switch=build-switch --set-switch)"
124
125
# dune install lays out bin/, share/liquidsoap/, share/man/, etc.
126
# --docdir is required because dune's default doc target is /usr/doc on Arch.
127
dune install --release \
128
--destdir="${pkgdir}" \
129
--prefix=/usr \
130
--docdir=/usr/share/doc \
131
liquidsoap-lang liquidsoap
132
133
install -Dm644 COPYING "${pkgdir}/usr/share/licenses/${pkgname}/COPYING"
134
135
# OCaml source files leak into the installed lib dir; only the compiled
136
# .cmxs/.cmi are needed at runtime.
137
find "${pkgdir}/usr/lib/liquidsoap-lang" \( -name '*.ml' -o -name '*.mli' \) -delete
138
139
# Camomile (Unicode lib) uses dune-site to encode its data directory at
140
# link time, pointing into the opam build switch. Ship the data under our
141
# own namespace (a future Arch camomile package would own /usr/share/camomile)
142
# and patch the encoded path in the binary in place.
143
local cam_src="${OPAMROOT}/build-switch/share/camomile"
144
install -d "${pkgdir}/usr/share/liquidsoap/camomile"
145
cp -a "${cam_src}/." "${pkgdir}/usr/share/liquidsoap/camomile/"
146
147
# dune-site encodes "=<len>:<path><padding>" with a fixed total field
148
# width. Replace the srcdir path with /usr/share/liquidsoap/camomile,
149
# preserving the total byte count by adjusting length prefix and padding.
150
python3 - "${pkgdir}/usr/bin/liquidsoap" <<'PY'
151
import sys, re
152
path = sys.argv[1]
153
with open(path, 'rb') as f:
154
data = f.read()
155
# Match =<len>:<srcdir>/.opam/build-switch/share/camomile<spaces>
156
pat = re.compile(rb'=(\d+):(/[^\x00]*?\.opam/build-switch/share/camomile)( +)', re.DOTALL)
157
def sub(m):
158
new = b'/usr/share/liquidsoap/camomile'
159
orig_total = len(m.group(0))
160
new_prefix = b'=%d:%s' % (len(new), new)
161
pad = orig_total - len(new_prefix)
162
assert pad >= 0, f"camomile path replacement overflow: {orig_total} vs {len(new_prefix)}"
163
return new_prefix + b' ' * pad
164
new_data, n = pat.subn(sub, data)
165
if n == 0:
166
sys.exit("camomile encoded path not found in binary")
167
with open(path, 'wb') as f:
168
f.write(new_data)
169
print(f"patched {n} camomile path(s)")
170
PY
171
172
# Two more residual srcdir paths embedded in the binary:
173
# - a ctypes -I include flag (NUL-terminated string) used by runtime FFI
174
# stub compilation in Ctypes.Foreign, almost never invoked by scripts;
175
# - the OCaml library search path that dune-site bakes into an `=N:hardcoded
176
# \0/usr/lib/ocaml\0<srcdir>/lib<padding>` block used by Dynlink for
177
# plugin discovery. /usr/lib/ocaml is already in that list, so blanking
178
# the srcdir entry leaves a working search path on the user system.
179
# Blank both to space-padded empty fields so any caller sees a no-op rather
180
# than a stale build-host path.
181
python3 - "${pkgdir}/usr/bin/liquidsoap" "${srcdir}" <<'PY'
182
import sys, re
183
binpath, srcdir = sys.argv[1], sys.argv[2].encode()
184
with open(binpath, 'rb') as f:
185
data = f.read()
186
escaped = re.escape(srcdir + b'/.opam/build-switch/lib')
187
# Match the path followed by trailing C-string NULs OR dune-site padding spaces.
188
pat = re.compile(escaped + rb'(/ctypes)?( +|\x00+)')
189
def sub(m):
190
return b' ' * len(m.group(0)) if m.group(2).startswith(b' ') else b'\x00' * len(m.group(0))
191
data, n = pat.subn(sub, data)
192
if n == 0:
193
sys.exit("expected residual lib/ctypes srcdir paths in binary, found none")
194
with open(binpath, 'wb') as f:
195
f.write(data)
196
print(f"nulled {n} residual path(s)")
197
PY
198
# Remaining $srcdir hits at this point live in DWARF debug sections (OCaml
199
# _build/default paths). makepkg's default strip pass removes them after
200
# package() returns. A final check would have to run post-strip, which is
201
# outside this hook's reach; rely on makepkg's own "reference to $srcdir"
202
# check for that final assertion.
203
}
204
Scan history
| Scanned at (UTC) | Severity | Rules |
|---|---|---|
| 2026-08-03 00:08:14 | LOW | 2 |
| 2026-08-02 00:16:08 | LOW | 2 |
| 2026-08-01 00:11:18 | LOW | 2 |
| 2026-07-31 00:14:10 | LOW | 2 |
| 2026-07-30 00:17:23 | LOW | 2 |
| 2026-07-29 00:25:53 | LOW | 2 |
| 2026-07-28 00:07:28 | LOW | 2 |
| 2026-07-27 00:24:32 | LOW | 2 |
| 2026-07-26 00:07:32 | LOW | 2 |
| 2026-07-25 00:13:44 | LOW | 2 |
| 2026-07-24 00:02:28 | LOW | 2 |
| 2026-07-23 00:14:47 | LOW | 2 |
| 2026-07-22 00:29:32 | LOW | 2 |
| 2026-07-21 00:24:15 | LOW | 2 |
| 2026-07-20 00:19:49 | LOW | 2 |
| 2026-07-19 00:17:08 | LOW | 2 |
| 2026-07-18 00:14:48 | LOW | 2 |
| 2026-07-17 00:06:16 | LOW | 2 |
| 2026-07-16 00:05:41 | LOW | 2 |
| 2026-07-15 00:09:25 | LOW | 2 |