[BUG] Linux .deb silently re-grants unprivileged user namespaces via an `unconfined` AppArmor profile, and ships no confinement in exchange
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
claude-desktop's postinst writes a root-owned AppArmor profile whose entire function is to exempt the app from kernel.apparmor_restrict_unprivileged_userns=1 — a hardening control Ubuntu enables by default. The profile takes that exemption with flags=(unconfined), so it grants the capability and confines nothing.
The result: on a system where the administrator has unprivileged user namespaces restricted, installing Claude Desktop silently re-opens that surface for a binary that executes model-directed shell commands and is exposed to prompt injection — and the app remains completely unconfined otherwise.
This is fixable without giving up the sandbox. The package already ships chrome-sandbox setuid root, and an enforcing AppArmor profile can grant userns inside confinement rather than instead of it. Claude Desktop should be sandboxed by default even when userns restriction is enabled.
A secondary packaging bug is included at the end: 49 packages / ~176 MB of QEMU stack installed via Recommends for a VM sandbox that is silently unavailable without KVM.
What Should Happen?
- Move
qemu-system-x86,ovmf,virtiofsdtoSuggests; prompt to install when a user first enables Cowork and KVM is usable. - Surface sandbox status in Settings: available / unavailable, with the concrete cause (no
/dev/kvm, VT-x disabled in BIOS, packages missing) and remediation — not a codename in a logfile. - Explain the virtualization stack in the package description or a
postinstnotice. - Consider dropping
virtiofsdfromRecommends; a copy is already vendored atresources/virtiofsd.
Error Messages/Logs
Steps to Reproduce
[BUG] Linux .deb silently re-grants unprivileged user namespaces via an unconfined AppArmor profile, and ships no confinement in exchange
Summary
claude-desktop's postinst writes a root-owned AppArmor profile whose entire function is to exempt the app from kernel.apparmor_restrict_unprivileged_userns=1 — a hardening control Ubuntu enables by default. The profile takes that exemption with flags=(unconfined), so it grants the capability and confines nothing.
The result: on a system where the administrator has unprivileged user namespaces restricted, installing Claude Desktop silently re-opens that surface for a binary that executes model-directed shell commands and is exposed to prompt injection — and the app remains completely unconfined otherwise.
This is fixable without giving up the sandbox. The package already ships chrome-sandbox setuid root, and an enforcing AppArmor profile can grant userns inside confinement rather than instead of it. Claude Desktop should be sandboxed by default even when userns restriction is enabled.
A secondary packaging bug is included at the end: 49 packages / ~176 MB of QEMU stack installed via Recommends for a VM sandbox that is silently unavailable without KVM.
Environment
| | |
|---|---|
| claude-desktop | 1.24012.11 (amd64, from downloads.claude.ai/claude-desktop/apt/stable) |
| OS | Ubuntu, kernel 7.0.0-29-generic |
| kernel.apparmor_restrict_unprivileged_userns | 1 (distro default) |
| kernel.unprivileged_userns_clone | 0 |
| CPU | VT-x present, kvm_intel unloaded, /dev/kvm absent |
---
Part 1 — The AppArmor profile (primary issue)
postinst installs, as root, without prompting:
# /etc/apparmor.d/claude-desktop
abi <abi/4.0>,
include <tunables/global>
profile claude-desktop /usr/lib/claude-desktop/claude-desktop flags=(unconfined) {
userns,
include if exists <local/claude-desktop>
}
The package's own postinst comment states the intent plainly:
Writes an unconfined AppArmor profile so Chromium's namespace sandbox works on Ubuntu 24.04+ (userns restriction).
Shipping an AppArmor profile is Ubuntu's documented mechanism for an application that needs unprivileged user namespaces, so the approach is not rogue. The objection is to flags=(unconfined) specifically:
- It is a pure subtraction from the system's security posture. Ubuntu restricts unprivileged userns because it has been a recurring local-privilege-escalation primitive. This profile removes that restriction for the binary and supplies no compensating controls. The exemption is taken; nothing is given back.
- It is silent and unconditional. A root maintainer script modifies a system-wide security policy at install time. There is no prompt, no notice in the package description, and no opt-out — unlike the APT repo registration in the same script, which at least honours
CLAUDE_DESKTOP_ADD_REPO.
- It reads as confinement when inspected. A file at
/etc/apparmor.d/claude-desktop, listed byaa-statusas a loaded profile, strongly implies the application is AppArmor-confined. It is not. An administrator auditing the system will draw the wrong conclusion.
- The threat model is not a typical Electron app. VS Code, Slack and Chrome are also unconfined, and that is normally unremarkable. Claude Desktop executes model-directed shell commands via
node-pty, loads third-party MCP servers, and ingests untrusted content (files, web pages, tool output) that is a live prompt-injection vector. Defence in depth is worth materially more here than in a chat client.
For completeness, the processes that matter run unconfined as the invoking user:
claude-desktop --type=utility --utility-sub-type=node.mojom.NodeService --service-sandbox-type=none
plus the Electron main process, node-pty, MCP servers and hooks — full access to $HOME, the login keyring, and the network. Renderers do get --enable-sandbox, but that is Chromium's content sandbox protecting against web content, not against tool execution.
This is solvable without losing the Chromium sandbox
The package already ships /usr/lib/claude-desktop/chrome-sandbox as -rwsr-xr-x root root — the setuid sandbox helper, which is Chromium's userns-free path to a sandboxed renderer. Options, roughly in increasing order of effort:
- Ship an enforcing profile that grants
usernswithin confinement.userns,is a rule, not a flag; it works inside a confined profile. The app would keep its namespace sandbox and gain a filesystem/capability boundary. Shipping incomplainmode first to gather denials would de-risk the rollout. - Evaluate whether the userns grant is needed at all, given the setuid helper is present. If the setuid path is viable, the profile could drop
usernsentirely. - Scope the exemption, so the grant covers only the renderer/zygote path that needs it rather than the whole binary.
- Offer a Flatpak build with portal-mediated filesystem access as an alternative channel for users who want confinement by default.
- At minimum, and immediately: rename or comment the profile so it cannot be mistaken for confinement, document the change in the package description, and honour an opt-out environment variable the way the APT registration does.
Ask
Claude Desktop should be sandboxed by default on Linux, including — especially — on systems with apparmor_restrict_unprivileged_userns=1. Trading the user's userns hardening for an unconfined binary is the wrong direction. Please also publish the Linux threat model: what the Cowork VM is trusted to contain, what runs unconfined on the host, and what changes when the VM is unavailable.
What is not being claimed
Agentic execution appears to fail closed when the VM sandbox is unavailable — with yukonSilver unsupported, device_bash reports itself unavailable rather than falling back to unsandboxed host execution. I did not audit every tool path and found no fail-open fallback. This report is about host confinement and the userns exemption, not a claim of a specific exploitable bug.
---
Part 2 — Packaging: 49 packages for a sandbox that never runs
apt-cache show claude-desktop:
Recommends: libasound2t64 | libasound2 | pulseaudio,
libayatana-appindicator3-1 | libappindicator3-1,
ca-certificates,
gnome-keyring | kwalletd6 | kwalletd5,
qemu-system-x86, ovmf, virtiofsd
Ubuntu ships APT::Install-Recommends "true", so the last three are installed by default and drag in their full closure:
qemu-system-x86→qemu-system-common,qemu-system-data,ubuntu-virt,ubuntu-helper-virt-hwe,seabios,ipxe-qemu,ipxe-qemu-256k-compat-efi-roms,libfdt1,libslirp0,libcacard0,libusbredirparser1t64,libbrlapi0.8,libspice-server1,libvirglrenderer1,virgl-server,libjack-jackd2-0,libsdl2-2.0-0,libsdl2-classic,libxss1,qemu-system-gui,qemu-system-modules-opengl,qemu-system-modules-spiceqemu-block-extra(aRecommendsofqemu-system-common) →librados2,librbd1,librdmacm1t64,libiscsi7,liburing2,libmultipath0,libmpathcmd0,libmpathpersist0,libdaxctl1,libndctl6,libpmem1,libpmemobj1,libboost-iostreams1.90.0,libboost-thread1.90.0,libssh-4ovmf→ovmf-generic,ovmf-amdsev,ovmf-inteltdxubuntu-virt→cpu-checker,msr-tools,uidmap,libsubid5
$ sudo apt-get -s autoremove --purge qemu-system-x86 ovmf virtiofsd
0 upgraded, 0 newly installed, 49 to remove and 0 not upgraded.
# ≈176 MB
Users reasonably ask why a chat client needs Ceph/RBD clients, SPICE, multipath, iSCSI, PXE ROMs and AMD SEV / Intel TDX confidential-computing firmware. The reason is legitimate — Cowork's sandbox is a QEMU microVM — but nothing in the package says so. The description is Desktop application for Claude.ai, twice.
On a machine without KVM, none of it is ever used. Every launch logs:
[cleanupVMBundleIfUnsupported] yukonSilver not supported (status=unsupported), checking for stale bundle...
[linux-vm] Spawning helper: /usr/lib/claude-desktop/resources/cowork-linux-helper
[cowork-linux-helper] [server] listening on /run/user/1000/claude-cowork-vm.sock
The helper listens; no qemu process ever spawns. There is no UI indication that the sandbox is unavailable. Diagnosing it requires finding the log, recognising yukonSilver as an internal codename, and knowing that implies KVM. A user cannot answer "is my agent sandboxed right now?" without reverse-engineering the app. On this machine the likely fix is "enable VT-x in firmware" — kvm_intel is present and unloaded while vmx is in /proc/cpuinfo — which a one-line preflight could have said.
Suggested fix
- Move
qemu-system-x86,ovmf,virtiofsdtoSuggests; prompt to install when a user first enables Cowork and KVM is usable. - Surface sandbox status in Settings: available / unavailable, with the concrete cause (no
/dev/kvm, VT-x disabled in BIOS, packages missing) and remediation — not a codename in a logfile. - Explain the virtualization stack in the package description or a
postinstnotice. - Consider dropping
virtiofsdfromRecommends; a copy is already vendored atresources/virtiofsd.
Why the deps exist, for reference
The .deb vendors the VM payload but not the firmware: resources/cowork-linux-helper (3.2 MB static Go binary; builds the QEMU command line, brokers host↔guest RPC over vsock), resources/smol-bin.x64.img (27 MB bootable image), and resources/virtiofsd. Strings in the helper show a q35 guest with -sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny, OVMF via if=pflash,format=raw,readonly=on,file= plus a per-VM efivars.fd, virtio-blk-pci,drive=rootdisk, vhost-user-fs-pci,chardev=virtiofs0,tag=claudeshared for granted folders, vhost-vsock-pci,guest-cid=N for control, and SLIRP user-mode NAT rather than a tap/bridge. OVMF is the one piece not vendored, hence the hard requirement. The design is sound; the objection is only that it installs unconditionally and unexplained.
---
Reproduction
# 1. userns exemption
sysctl kernel.apparmor_restrict_unprivileged_userns # = 1
cat /etc/apparmor.d/claude-desktop # flags=(unconfined)
ls -l /usr/lib/claude-desktop/chrome-sandbox # -rwsr-xr-x root root
ps aux | grep -- '--service-sandbox-type=none'
# 2. dependency bloat
apt-cache depends claude-desktop | grep -A2 Recommends
sudo apt-get -s autoremove --purge qemu-system-x86 ovmf virtiofsd
# 3. silent unavailability (machine without KVM)
kvm-ok
grep yukonSilver ~/.config/Claude/logs/cowork_vm_node.log
ps aux | grep qemu # nothing, ever
Workaround for affected users
The QEMU packages are Recommends, so removing them does not uninstall Claude Desktop, and on a KVM-less machine costs nothing that was working:
sudo apt-get autoremove --purge qemu-system-x86 ovmf virtiofsd
sudo apt-get install --no-install-recommends claude-desktop # fresh installs
cowork-linux-helper still spawns and idles on its socket regardless.
The AppArmor profile can be reverted, at the cost of the Chromium renderer sandbox — which is a bad trade and illustrates the problem: today the two are mutually exclusive, and they should not be.
sudo rm /etc/apparmor.d/claude-desktop && sudo systemctl reload apparmor
Unrelated note on postinst
The same script unconditionally registers the Anthropic APT repository, its signing key, and an unattended-upgrades Origins-Pattern snippet. That is the VS Code / Chrome model and is defensible, but the opt-out is currently only discoverable by reading the maintainer script:
echo 'CLAUDE_DESKTOP_ADD_REPO="false"' | sudo tee /etc/default/claude-desktop
Claude Model
Opus
Is this a regression?
No, this never worked
Last Working Version
_No response_
Claude Code Version
1.24012.11
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Xterm
Additional Information
[BUG] Linux .deb silently re-grants unprivileged user namespaces via an unconfined AppArmor profile, and ships no confinement in exchange
Summary
claude-desktop's postinst writes a root-owned AppArmor profile whose entire function is to exempt the app from kernel.apparmor_restrict_unprivileged_userns=1 — a hardening control Ubuntu enables by default. The profile takes that exemption with flags=(unconfined), so it grants the capability and confines nothing.
The result: on a system where the administrator has unprivileged user namespaces restricted, installing Claude Desktop silently re-opens that surface for a binary that executes model-directed shell commands and is exposed to prompt injection — and the app remains completely unconfined otherwise.
This is fixable without giving up the sandbox. The package already ships chrome-sandbox setuid root, and an enforcing AppArmor profile can grant userns inside confinement rather than instead of it. Claude Desktop should be sandboxed by default even when userns restriction is enabled.
A secondary packaging bug is included at the end: 49 packages / ~176 MB of QEMU stack installed via Recommends for a VM sandbox that is silently unavailable without KVM.
Environment
| | |
|---|---|
| claude-desktop | 1.24012.11 (amd64, from downloads.claude.ai/claude-desktop/apt/stable) |
| OS | Ubuntu, kernel 7.0.0-29-generic |
| kernel.apparmor_restrict_unprivileged_userns | 1 (distro default) |
| kernel.unprivileged_userns_clone | 0 |
| CPU | VT-x present, kvm_intel unloaded, /dev/kvm absent |
---
Part 1 — The AppArmor profile (primary issue)
postinst installs, as root, without prompting:
# /etc/apparmor.d/claude-desktop
abi <abi/4.0>,
include <tunables/global>
profile claude-desktop /usr/lib/claude-desktop/claude-desktop flags=(unconfined) {
userns,
include if exists <local/claude-desktop>
}
The package's own postinst comment states the intent plainly:
Writes an unconfined AppArmor profile so Chromium's namespace sandbox works on Ubuntu 24.04+ (userns restriction).
Shipping an AppArmor profile is Ubuntu's documented mechanism for an application that needs unprivileged user namespaces, so the approach is not rogue. The objection is to flags=(unconfined) specifically:
- It is a pure subtraction from the system's security posture. Ubuntu restricts unprivileged userns because it has been a recurring local-privilege-escalation primitive. This profile removes that restriction for the binary and supplies no compensating controls. The exemption is taken; nothing is given back.
- It is silent and unconditional. A root maintainer script modifies a system-wide security policy at install time. There is no prompt, no notice in the package description, and no opt-out — unlike the APT repo registration in the same script, which at least honours
CLAUDE_DESKTOP_ADD_REPO.
- It reads as confinement when inspected. A file at
/etc/apparmor.d/claude-desktop, listed byaa-statusas a loaded profile, strongly implies the application is AppArmor-confined. It is not. An administrator auditing the system will draw the wrong conclusion.
- The threat model is not a typical Electron app. VS Code, Slack and Chrome are also unconfined, and that is normally unremarkable. Claude Desktop executes model-directed shell commands via
node-pty, loads third-party MCP servers, and ingests untrusted content (files, web pages, tool output) that is a live prompt-injection vector. Defence in depth is worth materially more here than in a chat client.
For completeness, the processes that matter run unconfined as the invoking user:
claude-desktop --type=utility --utility-sub-type=node.mojom.NodeService --service-sandbox-type=none
plus the Electron main process, node-pty, MCP servers and hooks — full access to $HOME, the login keyring, and the network. Renderers do get --enable-sandbox, but that is Chromium's content sandbox protecting against web content, not against tool execution.
This is solvable without losing the Chromium sandbox
The package already ships /usr/lib/claude-desktop/chrome-sandbox as -rwsr-xr-x root root — the setuid sandbox helper, which is Chromium's userns-free path to a sandboxed renderer. Options, roughly in increasing order of effort:
- Ship an enforcing profile that grants
usernswithin confinement.userns,is a rule, not a flag; it works inside a confined profile. The app would keep its namespace sandbox and gain a filesystem/capability boundary. Shipping incomplainmode first to gather denials would de-risk the rollout. - Evaluate whether the userns grant is needed at all, given the setuid helper is present. If the setuid path is viable, the profile could drop
usernsentirely. - Scope the exemption, so the grant covers only the renderer/zygote path that needs it rather than the whole binary.
- Offer a Flatpak build with portal-mediated filesystem access as an alternative channel for users who want confinement by default.
- At minimum, and immediately: rename or comment the profile so it cannot be mistaken for confinement, document the change in the package description, and honour an opt-out environment variable the way the APT registration does.
Ask
Claude Desktop should be sandboxed by default on Linux, including — especially — on systems with apparmor_restrict_unprivileged_userns=1. Trading the user's userns hardening for an unconfined binary is the wrong direction. Please also publish the Linux threat model: what the Cowork VM is trusted to contain, what runs unconfined on the host, and what changes when the VM is unavailable.
What is not being claimed
Agentic execution appears to fail closed when the VM sandbox is unavailable — with yukonSilver unsupported, device_bash reports itself unavailable rather than falling back to unsandboxed host execution. I did not audit every tool path and found no fail-open fallback. This report is about host confinement and the userns exemption, not a claim of a specific exploitable bug.
---
Part 2 — Packaging: 49 packages for a sandbox that never runs
apt-cache show claude-desktop:
Recommends: libasound2t64 | libasound2 | pulseaudio,
libayatana-appindicator3-1 | libappindicator3-1,
ca-certificates,
gnome-keyring | kwalletd6 | kwalletd5,
qemu-system-x86, ovmf, virtiofsd
Ubuntu ships APT::Install-Recommends "true", so the last three are installed by default and drag in their full closure:
qemu-system-x86→qemu-system-common,qemu-system-data,ubuntu-virt,ubuntu-helper-virt-hwe,seabios,ipxe-qemu,ipxe-qemu-256k-compat-efi-roms,libfdt1,libslirp0,libcacard0,libusbredirparser1t64,libbrlapi0.8,libspice-server1,libvirglrenderer1,virgl-server,libjack-jackd2-0,libsdl2-2.0-0,libsdl2-classic,libxss1,qemu-system-gui,qemu-system-modules-opengl,qemu-system-modules-spiceqemu-block-extra(aRecommendsofqemu-system-common) →librados2,librbd1,librdmacm1t64,libiscsi7,liburing2,libmultipath0,libmpathcmd0,libmpathpersist0,libdaxctl1,libndctl6,libpmem1,libpmemobj1,libboost-iostreams1.90.0,libboost-thread1.90.0,libssh-4ovmf→ovmf-generic,ovmf-amdsev,ovmf-inteltdxubuntu-virt→cpu-checker,msr-tools,uidmap,libsubid5
$ sudo apt-get -s autoremove --purge qemu-system-x86 ovmf virtiofsd
0 upgraded, 0 newly installed, 49 to remove and 0 not upgraded.
# ≈176 MB
Users reasonably ask why a chat client needs Ceph/RBD clients, SPICE, multipath, iSCSI, PXE ROMs and AMD SEV / Intel TDX confidential-computing firmware. The reason is legitimate — Cowork's sandbox is a QEMU microVM — but nothing in the package says so. The description is Desktop application for Claude.ai, twice.
On a machine without KVM, none of it is ever used. Every launch logs:
[cleanupVMBundleIfUnsupported] yukonSilver not supported (status=unsupported), checking for stale bundle...
[linux-vm] Spawning helper: /usr/lib/claude-desktop/resources/cowork-linux-helper
[cowork-linux-helper] [server] listening on /run/user/1000/claude-cowork-vm.sock
The helper listens; no qemu process ever spawns. There is no UI indication that the sandbox is unavailable. Diagnosing it requires finding the log, recognising yukonSilver as an internal codename, and knowing that implies KVM. A user cannot answer "is my agent sandboxed right now?" without reverse-engineering the app. On this machine the likely fix is "enable VT-x in firmware" — kvm_intel is present and unloaded while vmx is in /proc/cpuinfo — which a one-line preflight could have said.
Suggested fix
- Move
qemu-system-x86,ovmf,virtiofsdtoSuggests; prompt to install when a user first enables Cowork and KVM is usable. - Surface sandbox status in Settings: available / unavailable, with the concrete cause (no
/dev/kvm, VT-x disabled in BIOS, packages missing) and remediation — not a codename in a logfile. - Explain the virtualization stack in the package description or a
postinstnotice. - Consider dropping
virtiofsdfromRecommends; a copy is already vendored atresources/virtiofsd.
Why the deps exist, for reference
The .deb vendors the VM payload but not the firmware: resources/cowork-linux-helper (3.2 MB static Go binary; builds the QEMU command line, brokers host↔guest RPC over vsock), resources/smol-bin.x64.img (27 MB bootable image), and resources/virtiofsd. Strings in the helper show a q35 guest with -sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny, OVMF via if=pflash,format=raw,readonly=on,file= plus a per-VM efivars.fd, virtio-blk-pci,drive=rootdisk, vhost-user-fs-pci,chardev=virtiofs0,tag=claudeshared for granted folders, vhost-vsock-pci,guest-cid=N for control, and SLIRP user-mode NAT rather than a tap/bridge. OVMF is the one piece not vendored, hence the hard requirement. The design is sound; the objection is only that it installs unconditionally and unexplained.
---
Reproduction
# 1. userns exemption
sysctl kernel.apparmor_restrict_unprivileged_userns # = 1
cat /etc/apparmor.d/claude-desktop # flags=(unconfined)
ls -l /usr/lib/claude-desktop/chrome-sandbox # -rwsr-xr-x root root
ps aux | grep -- '--service-sandbox-type=none'
# 2. dependency bloat
apt-cache depends claude-desktop | grep -A2 Recommends
sudo apt-get -s autoremove --purge qemu-system-x86 ovmf virtiofsd
# 3. silent unavailability (machine without KVM)
kvm-ok
grep yukonSilver ~/.config/Claude/logs/cowork_vm_node.log
ps aux | grep qemu # nothing, ever
Workaround for affected users
The QEMU packages are Recommends, so removing them does not uninstall Claude Desktop, and on a KVM-less machine costs nothing that was working:
sudo apt-get autoremove --purge qemu-system-x86 ovmf virtiofsd
sudo apt-get install --no-install-recommends claude-desktop # fresh installs
cowork-linux-helper still spawns and idles on its socket regardless.
The AppArmor profile can be reverted, at the cost of the Chromium renderer sandbox — which is a bad trade and illustrates the problem: today the two are mutually exclusive, and they should not be.
sudo rm /etc/apparmor.d/claude-desktop && sudo systemctl reload apparmor
Unrelated note on postinst
The same script unconditionally registers the Anthropic APT repository, its signing key, and an unattended-upgrades Origins-Pattern snippet. That is the VS Code / Chrome model and is defensible, but the opt-out is currently only discoverable by reading the maintainer script:
echo 'CLAUDE_DESKTOP_ADD_REPO="false"' | sudo tee /etc/default/claude-desktop