Skip to content

Commit 3edb7b7

Browse files
committed
refactor: drop AppArmor profile install, tighten wrapper fallback regex
Phoenix Code's Electron renderer is already locked down (contextIsolation + nodeIntegration:false + local-only phtauri:// protocol). The kernel-side AppArmor profile only adds defense-in-depth against renderer V8 RCE → raw-syscall escape, which is a narrow class. The IPC-bridge attack surface (electronFSAPI, keytar via electronAPI) is not gated by the kernel sandbox either way. Removing the profile install matches what every other Electron AppImage in the wild does (Cursor, Obsidian, Discord, VSCode-AppImage), removes ~70 LOC, and stops requesting sudo to write under /etc/. Wrapper-script fallbacks stay (verified load-bearing by reading Chromium and AppImage type-2 runtime source): - Sandbox: Chromium LOG(FATAL)s when AppArmor blocks the userns clone on Ubuntu 24.04+ / Debian 13+; it does NOT auto-fall-back. Our --no-sandbox re-exec is the actual recovery and now the only one. - FUSE: the AppImage runtime self-recovers only when libfuse.so.2 can't dlopen; when /dev/fuse is missing or fusermount is denied (Docker/Podman containers, older WSL2, SELinux-restricted kernels), it prints "Cannot mount AppImage, please check your FUSE setup" and exits. Our --appimage-extract-and-run re-exec is the recovery. Wrapper regex tightened: - Drop `dlopen` from the FUSE branch (too broad, could match unrelated library-load failures and route them into a useless retry). - Add `Check failed` to the sandbox branch to catch Chromium's actual NamespaceSandbox / SUID-helper FATAL phrasing. uninstall() reverted to a single behavior (the keep_apparmor mode arg is no longer needed since there's no profile to preserve).
1 parent 36d9815 commit 3edb7b7

1 file changed

Lines changed: 32 additions & 103 deletions

File tree

docs/linux/installer.sh

Lines changed: 32 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,20 @@ set -euo pipefail
99
# is attempted — the system already has every shared library Phoenix Code
1010
# needs at startup, regardless of which package name provides it.
1111
#
12-
# Two things are checked independently of the --version probe:
13-
# - gnome-keyring daemon presence (libsecret needs the daemon for keytar
14-
# credential storage; --version exits before keytar is required).
15-
# - AppArmor unprivileged-userns profile on Ubuntu 24.04+ / Debian 13+
16-
# (required for Chromium's sandbox; --version exits before the zygote).
12+
# Beyond the --version probe, the installer also checks for the gnome-keyring
13+
# daemon binary — libsecret needs the daemon for keytar credential storage,
14+
# and --version exits before keytar is require()d. Sudo is only requested if
15+
# libs or the keyring daemon are actually missing.
1716
#
18-
# Sudo is only requested if at least one of the three checks fails, and only
19-
# the missing piece gets installed. Also wires up a desktop entry + `phcode`
20-
# wrapper script that handles AppImage launch fallbacks.
17+
# On Ubuntu 24.04+ / Debian 13+, Chromium's unprivileged userns sandbox can
18+
# be blocked by the kernel sysctl. The app still launches because the wrapper
19+
# script falls back to --no-sandbox; we no longer install an AppArmor profile
20+
# to keep the kernel sandbox enabled (the renderer-RCE-to-syscall threat the
21+
# kernel sandbox guards against is narrow, and Phoenix Code's actual attack
22+
# surface flows through the IPC bridge which the kernel sandbox doesn't gate).
23+
#
24+
# Also wires up a desktop entry + `phcode` wrapper script that handles
25+
# AppImage launch fallbacks.
2126

2227
DESKTOP_DIR=$HOME/.local/share/applications
2328
UPDATE_JSON_URL="https://updates.phcode.io/tauri/update-latest-experimental-build.json"
@@ -30,7 +35,6 @@ DESKTOP_ENTRY="$DESKTOP_DIR/$DESKTOP_ENTRY_NAME"
3035
SCRIPT_NAME="phcode"
3136
BINARY_NAME="phoenix-code"
3237
APPIMAGE_NAME="phoenix-code.AppImage"
33-
APPARMOR_PROFILE_PATH="/etc/apparmor.d/phoenix-code"
3438

3539
declare -a MIME_TYPES=(
3640
"text/html"
@@ -106,11 +110,10 @@ check_architecture() {
106110
# Runtime-library probe: ask the AppImage to print its version and exit. This
107111
# is handled at src-electron/main.js:23-26, but `require('electron')` runs
108112
# before that handler — which means Chromium initializes its sandbox bits even
109-
# for --version. On Ubuntu 24.04+ / Debian 13+ without our AppArmor profile,
110-
# that init can SIGTRAP. Pass --no-sandbox so the probe ALSO works on systems
111-
# where the AppArmor profile isn't installed yet — this isolates the probe to
112-
# "are the system shared libraries present and loadable?" and decouples it
113-
# from the AppArmor concern (which is handled separately).
113+
# for --version. On Ubuntu 24.04+ / Debian 13+ where unprivileged user
114+
# namespaces are restricted, that init can SIGTRAP. Pass --no-sandbox so the
115+
# probe isolates to "are the system shared libraries present and loadable?"
116+
# regardless of the kernel's userns policy.
114117
#
115118
# The `{ … } 2>/dev/null` wrapper suppresses bash's own "Trace/breakpoint trap
116119
# (core dumped)" report when the subprocess dies on signal — those messages
@@ -126,15 +129,6 @@ gnome_keyring_present() {
126129
command -v gnome-keyring-daemon >/dev/null 2>&1
127130
}
128131

129-
# Returns 0 if this system's kernel restricts unprivileged user namespaces
130-
# (Ubuntu 24.04+ / Debian 13+ / Kali rolling default), which blocks Chromium's
131-
# sandbox inside Electron AppImages unless an AppArmor profile grants userns.
132-
apparmor_userns_restricted() {
133-
[ -d /etc/apparmor.d ] || return 1
134-
command -v apparmor_parser >/dev/null 2>&1 || return 1
135-
[ "$(sysctl -n kernel.apparmor_restrict_unprivileged_userns 2>/dev/null || echo 0)" = "1" ]
136-
}
137-
138132
downloadLatestReleaseInfo() {
139133
local release_info_file="$TMP_DIR/latest_release.json"
140134
if [ ! -f "$release_info_file" ]; then
@@ -179,11 +173,11 @@ create_invocation_script() {
179173
APPIMAGE="$install_dir/$APPIMAGE_NAME"
180174
ERR=\$(mktemp)
181175
if ! "\$APPIMAGE" "\$@" 2>"\$ERR"; then
182-
if grep -qiE 'fuse|libfuse|dlopen' "\$ERR"; then
176+
if grep -qiE 'fuse|libfuse|fusermount|/dev/fuse' "\$ERR"; then
183177
rm -f "\$ERR"
184178
exec "\$APPIMAGE" --appimage-extract-and-run "\$@"
185179
fi
186-
if grep -qiE 'userns|apparmor|sandbox|setuid|namespace' "\$ERR"; then
180+
if grep -qiE 'userns|apparmor|sandbox|setuid|namespace|Check failed' "\$ERR"; then
187181
rm -f "\$ERR"
188182
exec "\$APPIMAGE" --no-sandbox "\$@"
189183
fi
@@ -212,32 +206,6 @@ gnome_keyring_pkg_if_needed() {
212206
fi
213207
}
214208

215-
# Installs an AppArmor profile that allows the AppImage to use unprivileged
216-
# user namespaces. Required on Ubuntu 24.04+ / Debian 13+ where the kernel knob
217-
# kernel.apparmor_restrict_unprivileged_userns=1 blocks Chromium's sandbox in
218-
# every Electron AppImage. No-op elsewhere, and a no-op if the profile is
219-
# already present (idempotent across re-runs and --upgrade).
220-
install_apparmor_profile_if_needed() {
221-
apparmor_userns_restricted || return 0
222-
[ -f "$APPARMOR_PROFILE_PATH" ] && return 0
223-
224-
sudo tee "$APPARMOR_PROFILE_PATH" >/dev/null <<EOF
225-
# Auto-generated by the Phoenix Code installer.
226-
# Required on Ubuntu 24.04+ / Debian 13+ where unprivileged user namespaces are
227-
# restricted by default, which would otherwise block Chromium's sandbox in the
228-
# Phoenix Code AppImage.
229-
abi <abi/4.0>,
230-
include <tunables/global>
231-
profile phoenix-code "$INSTALL_DIR/$APPIMAGE_NAME" flags=(unconfined) {
232-
userns,
233-
include if exists <local/phoenix-code>
234-
}
235-
EOF
236-
if ! sudo apparmor_parser -r "$APPARMOR_PROFILE_PATH" 2>/dev/null; then
237-
echo -e "${YELLOW}AppArmor profile reload failed; the wrapper will fall back to --no-sandbox if needed.${RESET}"
238-
fi
239-
}
240-
241209
# Picks the libfuse2 package name for whatever Ubuntu/Debian release we're on.
242210
# Ubuntu 24.04 Noble+ / Debian 13 Trixie+ / Kali rolling / Mint 22+ / Neon 24+
243211
# ship libfuse2t64 (the post-y2038 rename); older releases (Ubuntu 20.04 Focal
@@ -304,11 +272,8 @@ install_packages_for_distro() {
304272
# mounts the AppImage and AFTER the Electron binary loads its NEEDED libs.
305273
# 2. gnome-keyring daemon — binary-presence check; `--version` doesn't
306274
# exercise keytar so it can't verify libsecret/Secret Service end-to-end.
307-
# 3. AppArmor unprivileged-userns profile — Chromium sandbox prereq on
308-
# Ubuntu 24.04+ / Debian 13+. Independent of the other two.
309275
#
310-
# Sudo is requested ONLY if at least one of the three probes fails, and only
311-
# the failing pieces are installed.
276+
# Sudo is requested ONLY if at least one of the probes fails.
312277
ensure_runtime_dependencies() {
313278
local appimage="$1"
314279
if [ ! -f /etc/os-release ]; then
@@ -326,49 +291,30 @@ ensure_runtime_dependencies() {
326291
esac
327292
local keyring; keyring=$(gnome_keyring_pkg_if_needed)
328293

329-
local libs_ok=1 keyring_ok=1 apparmor_ok=1
294+
local libs_ok=1 keyring_ok=1
330295
verify_appimage_launches "$appimage" || libs_ok=0
331296
if [ -n "$keyring" ] && ! gnome_keyring_present; then keyring_ok=0; fi
332-
case "$distro" in
333-
ubuntu|debian|linuxmint|kali|neon)
334-
if apparmor_userns_restricted && [ ! -f "$APPARMOR_PROFILE_PATH" ]; then
335-
apparmor_ok=0
336-
fi
337-
;;
338-
esac
339297

340-
if [ "$libs_ok" = 1 ] && [ "$keyring_ok" = 1 ] && [ "$apparmor_ok" = 1 ]; then
298+
if [ "$libs_ok" = 1 ] && [ "$keyring_ok" = 1 ]; then
341299
echo -e "${GREEN}All runtime dependencies already present.${RESET}"
342300
return 0
343301
fi
344302

345-
local need_pkgs=0
346-
[ "$libs_ok" = 0 ] || [ "$keyring_ok" = 0 ] && need_pkgs=1
347-
if [ "$need_pkgs" = 1 ] && [ "$apparmor_ok" = 0 ]; then
348-
echo "Installing runtime packages and AppArmor sandbox profile..."
349-
elif [ "$libs_ok" = 0 ]; then
303+
if [ "$libs_ok" = 0 ]; then
350304
echo "Installing missing runtime libraries..."
351-
elif [ "$keyring_ok" = 0 ]; then
352-
echo "Installing system keychain daemon..."
353305
else
354-
echo "Installing AppArmor sandbox profile (keeps Chromium's sandbox enabled on Ubuntu 24.04+)..."
306+
echo "Installing system keychain daemon..."
355307
fi
356308
echo "This step requires administrative access."
357309
if ! sudo -n true 2>/dev/null; then
358310
echo "Please enter your password to proceed."
359311
fi
360312

361-
if [ "$need_pkgs" = 1 ]; then
362-
install_packages_for_distro "$distro" "$keyring"
363-
# Re-probe libraries; if still failing, surface a clear warning but don't
364-
# abort — the AppImage may have a runtime-specific issue we can't fix here.
365-
if [ "$libs_ok" = 0 ] && ! verify_appimage_launches "$appimage"; then
366-
echo -e "${YELLOW}WARN: AppImage still fails --version after dep install.${RESET}"
367-
fi
368-
fi
369-
370-
if [ "$apparmor_ok" = 0 ]; then
371-
install_apparmor_profile_if_needed
313+
install_packages_for_distro "$distro" "$keyring"
314+
# Re-probe libraries; if still failing, surface a clear warning but don't
315+
# abort — the AppImage may have a runtime-specific issue we can't fix here.
316+
if [ "$libs_ok" = 0 ] && ! verify_appimage_launches "$appimage"; then
317+
echo -e "${YELLOW}WARN: AppImage still fails --version after dep install.${RESET}"
372318
fi
373319
}
374320

@@ -552,15 +498,15 @@ install() {
552498
echo -e "${YELLOW}Phoenix Code appears to be already installed.${RESET}"
553499
if [ ! -t 0 ]; then
554500
echo -e "${GREEN}Reinstalling Phoenix Code...${RESET}"
555-
uninstall keep_apparmor
501+
uninstall
556502
downloadAndInstall
557503
copyFilesToDestination
558504
else
559505
read -r -p "Would you like to reinstall it? (y/N): " response
560506
case "$response" in
561507
[Yy]* )
562508
echo -e "${GREEN}Reinstalling Phoenix Code...${RESET}"
563-
uninstall keep_apparmor
509+
uninstall
564510
downloadAndInstall
565511
copyFilesToDestination
566512
;;
@@ -595,7 +541,7 @@ upgrade() {
595541
if [ -n "$latest_version" ] && [ "$(printf '%s\n' "$latest_version" "$current_version" | sort -V | tail -n1)" = "$latest_version" ] && [ "$latest_version" != "$current_version" ]; then
596542
echo -e "${YELLOW}A newer version of Phoenix Code is available. Proceeding with the upgrade...${RESET}"
597543
downloadAndInstall
598-
uninstall keep_apparmor
544+
uninstall
599545
copyFilesToDestination
600546
echo -e "${GREEN}Upgrade completed successfully. Phoenix Code has been updated to the latest version.${RESET}"
601547
else
@@ -604,15 +550,6 @@ upgrade() {
604550
}
605551

606552
uninstall() {
607-
# mode:
608-
# "full" (default) — remove every artifact, including the
609-
# system-level AppArmor profile under /etc/apparmor.d/.
610-
# Used by the --uninstall CLI entry-point.
611-
# "keep_apparmor" — remove user-scope artifacts only; leave the AppArmor
612-
# profile in place. Used by the internal reinstall and
613-
# --upgrade flows so a routine version bump doesn't need
614-
# sudo just to recreate the same profile content.
615-
local mode="${1:-full}"
616553
echo -e "${YELLOW}Starting uninstallation of Phoenix Code...${RESET}"
617554
uninstallBetaAppImage
618555

@@ -650,14 +587,6 @@ uninstall() {
650587
echo -e "${RED}Installation directory not found. Skipping...${RESET}"
651588
fi
652589

653-
if [ "$mode" != "keep_apparmor" ] && [ -f "$APPARMOR_PROFILE_PATH" ]; then
654-
echo -e "${YELLOW}Removing AppArmor profile...${RESET}"
655-
sudo rm -f "$APPARMOR_PROFILE_PATH" || true
656-
if command -v apparmor_parser >/dev/null 2>&1; then
657-
sudo apparmor_parser -R "$APPARMOR_PROFILE_PATH" 2>/dev/null || true
658-
fi
659-
fi
660-
661590
echo -e "${GREEN}Uninstallation of Phoenix Code completed.${RESET}"
662591
}
663592

0 commit comments

Comments
 (0)