Skip to content

fix(desktop): repair the Chromium sandbox on Ubuntu 24.04+ - #142

Merged
ralyodio merged 1 commit into
masterfrom
fix/electron-sandbox-ubuntu2404
Aug 14, 2026
Merged

ralyodio merged 1 commit into
masterfrom
fix/electron-sandbox-ubuntu2404

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Reported from a 26.04 box, on the .deb and the AppImage alike:

FATAL:setuid_sandbox_host.cc(163)] The SUID sandbox helper binary was found, but
is not configured correctly. Rather than run without sandboxing I'm aborting now.

Cause

electron-builder's stock postinst picks between Chromium's two sandboxes like this:

if ! { [[ -L /proc/self/ns/user ]] && unshare --user true; }; then
    chmod 4755 chrome-sandbox   # no user namespaces -> SUID sandbox
else
    chmod 0755 chrome-sandbox   # namespaces work -> SUID not needed
fi

A postinst runs as root. Ubuntu 24.04+'s kernel.apparmor_restrict_unprivileged_userns=1
restricts unprivileged user namespaces — root is exempt. So the probe succeeds
at install time, the else branch runs, and chrome-sandbox is installed 0755.
The app is then launched by an ordinary user, refused the namespace sandbox, falls
back to the SUID helper, and finds it unconfigured. The right question asked of the
wrong user.

Fix

resources/after-install.sh replaces the postinst and:

  1. Installs an AppArmor profile granting the binary userns create, restoring the
    namespace sandbox — the one upstream Chromium develops against, rather than
    the compatibility helper. This is what Chrome/Chromium settled on for the same
    regression.
  2. Decides the SUID fallback by probing as nobody rather than as root, so the
    answer reflects the conditions the app actually runs under. Unresolvable probe
    means "assume SUID is needed" — an unused sandbox costs nothing, a missing one
    aborts the app.

resources/after-remove.sh unloads and removes the profile. Both re-implement the
stock update-alternatives / mime / desktop-database steps, because naming
afterInstall/afterRemove replaces electron-builder's templates rather than
extending them — flagged in a comment in both files and in electron-builder.yml.

AppImage

Not fixable from inside the artifact, and the README now says so plainly: an
AppImage is a FUSE squashfs mount, FUSE mounts are nosuid, so the SUID helper can
never work from one whatever its mode bits say — and there is no install step in
which to place a profile. The README gives users a profile to install, and notes
--no-sandbox as the worse option it is (turning the renderer sandbox off in a
security product is a poor default).

Verification

  • bash -n on both scripts, before and after macro expansion.
  • Replayed app-builder-lib's writeConfigFile substitution — it throws on any
    undefined ${...}, and these use only ${executable} and ${sanitizedProductName},
    the two the stock templates already use.
  • Both AppArmor profiles (deb and AppImage) parse under the system apparmor_parser -Q -d.
  • Decision logic exercised against a stub chrome-sandbox: unresolvable probe → 4755.

Not verified end-to-end on a machine that reproduces the abort — this dev box allows
unprivileged userns, so it does not fail in the first place. The one-line confirmation
on an affected box is ls -l /opt/ThreatCrush/chrome-sandbox: -rwxr-xr-x is the bug,
-rwsr-xr-x is the fix.

🤖 Generated with Claude Code

The .deb installs chrome-sandbox 0755 on Ubuntu 24.04+, so the first launch
by an ordinary user aborts:

  FATAL:setuid_sandbox_host.cc(163)] The SUID sandbox helper binary was
  found, but is not configured correctly.

electron-builder's stock postinst chooses between the namespace sandbox and
the SUID helper with `unshare --user true`. A postinst runs as root, and
kernel.apparmor_restrict_unprivileged_userns restricts *unprivileged* user
namespaces only — root is exempt. The probe therefore succeeds at install
time, the postinst concludes the SUID helper is unnecessary, and drops the
setuid bit. The app then runs as a normal user, is refused the namespace
sandbox, falls back to the SUID helper, and finds it unconfigured. The right
question asked of the wrong user.

Replace the postinst and postrm. The postinst installs an AppArmor profile
granting the binary `userns create`, which restores the namespace sandbox —
the one upstream Chromium develops against, rather than the compatibility
helper — and decides the SUID fallback by probing as `nobody` instead of as
root. Both scripts re-implement the stock update-alternatives, mime and
desktop-database steps, because naming afterInstall/afterRemove replaces
those templates rather than extending them.

The AppImage cannot be fixed from inside the artifact: a FUSE squashfs mount
is nosuid, so the SUID helper can never work from one, and there is no
install step in which to place a profile. Documented in the README with a
profile users can install, and `--no-sandbox` noted as the worse option it is.

Verified: both scripts pass `bash -n` before and after macro expansion, carry
no macro the replacer would reject, and both AppArmor profiles parse under
the system apparmor_parser.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread apps/desktop/README.md
abi <abi/4.0>,
include <tunables/global>

profile threatcrush-appimage "/tmp/.mount_*/threatcrush-desktop" flags=(unconfined) {
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

70 finding(s)

HIGH/CRITICAL: 12 | MEDIUM: 57 | LOW: 1

Severity Rule Location
HIGH secret-aws-access-key prd/0003-detect-hardcoded-secrets-before-they-are-committed-or-served.md:126
HIGH sh-eval-expansion .githooks/pre-commit:26
HIGH js-electron-node-integration apps/desktop/src/main/index.ts:22
HIGH sh-remote-script-execution apps/web/public/install.sh:272
HIGH sh-remote-script-execution apps/web/public/install.sh:320
HIGH secret-generic-credential modules/spend-guard/config/example.conf.toml:13
HIGH secret-generic-credential modules/spend-guard/README.md:84
HIGH secret-generic-credential PRD.md:268
HIGH tls-verification-disabled prd/0004-find-dangerous-code-patterns-without-pretending-to-be-a-compiler.md:121
HIGH tls-verification-disabled prd/0004-find-dangerous-code-patterns-without-pretending-to-be-a-compiler.md:122
HIGH sh-remote-script-execution scripts/smoke-test.sh:46
HIGH sh-remote-script-execution scripts/smoke-test.sh:47
MEDIUM insecure-temp-file .githooks/commit-msg:16
MEDIUM insecure-temp-file .githooks/post-commit:20
MEDIUM js-shell-exec-interpolation apps/cli/src/commands/init.ts:70
MEDIUM js-shell-exec-interpolation apps/cli/src/commands/init.ts:79
MEDIUM js-shell-exec-interpolation apps/cli/src/commands/service.ts:88
MEDIUM js-shell-exec-interpolation apps/cli/src/commands/service.ts:111
MEDIUM sql-template-interpolation apps/cli/src/core/state.ts:121
MEDIUM sql-template-interpolation apps/cli/src/core/state.ts:125
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:31
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:33
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:34
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:35
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:36
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:43
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:49
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:56
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:63
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:82
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:84
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:85
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:93
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:98
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:105
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:112
MEDIUM js-shell-exec-interpolation apps/cli/src/index.ts:425
MEDIUM insecure-temp-file apps/desktop/README.md:95
MEDIUM js-unescaped-html-sink apps/web/src/app/about/page.tsx:180
MEDIUM js-unescaped-html-sink apps/web/src/app/about/page.tsx:184
MEDIUM js-open-redirect apps/web/src/app/auth/login/page.tsx:50
MEDIUM js-unescaped-html-sink apps/web/src/app/blog/[slug]/page.tsx:125
MEDIUM js-unescaped-html-sink apps/web/src/app/blog/[slug]/page.tsx:153
MEDIUM js-unescaped-html-sink apps/web/src/app/blog/[slug]/page.tsx:157
MEDIUM js-unescaped-html-sink apps/web/src/app/get-whitepaper/page.tsx:346
MEDIUM js-unescaped-html-sink apps/web/src/app/layout.tsx:211
MEDIUM js-unescaped-html-sink apps/web/src/app/layout.tsx:215
MEDIUM js-unescaped-html-sink apps/web/src/app/layout.tsx:219
MEDIUM js-unescaped-html-sink apps/web/src/app/page.tsx:120
MEDIUM js-unescaped-html-sink apps/web/src/app/store/[slug]/page.tsx:107

…and 20 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 6dcb765 into master Aug 14, 2026
11 checks passed
@ralyodio
ralyodio deleted the fix/electron-sandbox-ubuntu2404 branch August 14, 2026 12:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants