diff --git a/hardening/README.md b/hardening/README.md index 007ca587..9b6ea548 100644 --- a/hardening/README.md +++ b/hardening/README.md @@ -19,19 +19,21 @@ Deployed and interactively validated on the Linux desktop host (2026-10-31). Pol `Media` means audio playback, microphone, Camera and ScreenCast portals. Writable paths are listed explicitly; other files use the document portal. Every app also has private writable state. -| App | Network | Writable paths | Grants | -| ------------ | ------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | -| Firefox | Host loopback | `~/.mozilla`, personal files, Trash, Firefox artwork | Audio, browserpass, MPRIS, idle; camera/screen sharing intentionally unavailable | -| Chromium | Host loopback | `~/.config/chromium`, personal files | Media, browserpass, MPRIS, idle, v12 Secret | -| Chrome | Isolated | `~/.config/google-chrome`, Downloads | Media, browserpass, MPRIS, idle, v12 Secret | -| Brave Origin | Isolated | `~/.config/BraveSoftware/Brave-Origin`, Downloads | v12 Secret | -| Edge | Isolated | `~/.config/microsoft-edge`, Downloads | v12 Secret | -| QQ | Isolated | Personal files, private app state | Media, notifications, tray, MPRIS, idle, accessibility | -| Telegram | Isolated | Personal files, private app state | Media, notifications, tray, MPRIS, idle, accessibility, Ayatana indicators | -| Zoom | Isolated | `~/.zoom`, `zoomus.conf`, `zoom.conf`, Downloads, `Documents/Zoom`, private app state | Media, X11/XWayland, FHS, SNI tray, idle, `/dev/video0-1`, read-only `/sys/class/drm` | -| WeChat | Isolated | Documents, Downloads, app-scoped `wechat` data, private app state | Media, Wayland, X11 fallback, SNI tray, file chooser portal | -| Foliate | None | app-scoped state | File chooser, OpenURI, notifications, IME, accessibility | -| Krita | None | app-scoped state | File chooser, OpenURI, print, inhibit, notifications, IME, accessibility | +| App | Network | Writable paths | Grants | +| ------------ | ------------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| Firefox | Host loopback | `~/.mozilla`, personal files, Trash, Firefox artwork | Audio, browserpass, MPRIS, idle; camera/screen sharing intentionally unavailable | +| Chromium | Host loopback | `~/.config/chromium`, personal files | Media, browserpass, MPRIS, idle, v12 Secret | +| Chrome | Isolated | `~/.config/google-chrome`, Downloads | Media, browserpass, MPRIS, idle, v12 Secret | +| Brave Origin | Isolated | `~/.config/BraveSoftware/Brave-Origin`, Downloads | v12 Secret | +| Edge | Isolated | `~/.config/microsoft-edge`, Downloads | v12 Secret | +| QQ | Isolated | Personal files, private app state | Media, notifications, tray, MPRIS, idle, accessibility | +| Telegram | Isolated | Personal files, private app state | Media, notifications, tray, MPRIS, idle, accessibility, Ayatana indicators | +| Zoom | Isolated | `~/.zoom`, `zoomus.conf`, `zoom.conf`, Downloads, `Documents/Zoom`, private app state | Media, X11/XWayland, FHS, SNI tray, idle, `/dev/video0-1`, read-only `/sys/class/drm` | +| WeChat | Isolated | Documents, Downloads, app-scoped `wechat` data, private app state | Media, Wayland, X11 fallback, SNI tray, file chooser portal | +| Foliate | None | Documents/Downloads/Pictures read-only, app-scoped state | File chooser, OpenURI, notifications, IME, accessibility | +| Krita | None | Documents/Downloads/Pictures writable, app-scoped state | File chooser, OpenURI, print, inhibit, notifications, IME, accessibility | +| Bambu Studio | Isolated | Documents/Downloads/Pictures writable, app-scoped state | X11/XWayland, PulseAudio, cloud/LAN network, file chooser, notifications, IME, accessibility | +| OrcaSlicer | Isolated | Documents/Downloads/Pictures writable, app-scoped state | X11/XWayland, cloud/LAN network, file chooser, notifications, IME, accessibility | All browsers get read-only NSS certificates, printing, native notifications and accessibility. diff --git a/hardening/nixpaks/bambu-studio.nix b/hardening/nixpaks/bambu-studio.nix new file mode 100644 index 00000000..351a5205 --- /dev/null +++ b/hardening/nixpaks/bambu-studio.nix @@ -0,0 +1,70 @@ +{ + lib, + bambu-studio, + mkNixPak, + buildEnv, + makeDesktopItem, + runCommandLocal, + ... +}: +let + appId = "com.bambulab.BambuStudio"; + package = bambu-studio; + + wrapped = mkNixPak { + config = { + imports = [ ./modules/slicer-policy.nix ]; + app = { + inherit package; + binPath = "bin/bambu-studio"; + }; + flatpak = { inherit appId; }; + # The Agora SDK used by the printer camera preview aborts when + # PulseAudio initialization fails. + slicer.enablePulse = true; + # Work around wxWidgets HiDPI text clipping under fractional scaling. + # https://github.com/bambulab/BambuStudio/issues/4959 + bubblewrap.env.GDK_DPI_SCALE = "0.8"; + }; + }; + exe = lib.getExe wrapped.config.script; + assets = runCommandLocal "bambu-studio-desktop-assets" { } '' + mkdir -p $out/share + ln -s ${package}/share/icons $out/share/icons + ''; +in +buildEnv { + inherit (wrapped.config.script) name passthru; + meta = wrapped.config.script.meta // { + mainProgram = "bambu-studio"; + }; + paths = [ + wrapped.config.script + assets + (makeDesktopItem { + name = "com.bambulab.BambuStudio"; + desktopName = "BambuStudio"; + genericName = "3D Printing Software"; + comment = "PC software for BambuLab 3D printers"; + exec = "${exe} %U"; + icon = "BambuStudio"; + terminal = false; + startupWMClass = "bambu-studio"; + categories = [ + "Graphics" + "3DGraphics" + "Engineering" + ]; + mimeTypes = [ + "model/stl" + "model/3mf" + "application/vnd.ms-3mfdocument" + "application/prs.wavefront-obj" + "application/x-amf" + "x-scheme-handler/bambustudio" + "model/step" + ]; + extraConfig.X-Flatpak = appId; + }) + ]; +} diff --git a/hardening/nixpaks/default.nix b/hardening/nixpaks/default.nix index 6871e888..7b79d4f7 100644 --- a/hardening/nixpaks/default.nix +++ b/hardening/nixpaks/default.nix @@ -29,6 +29,8 @@ in microsoft-edge = wrapper super ./microsoft-edge.nix; foliate = wrapper super ./foliate.nix; krita = wrapper pkgs-stable ./krita.nix; + bambu-studio = wrapper pkgs-master ./bambu-studio.nix; + orca-slicer = wrapper pkgs-master ./orca-slicer.nix; zoom = wrapper super ./zoom.nix; wechat = wrapper pkgs-patched ./wechat.nix; }; diff --git a/hardening/nixpaks/modules/slicer-policy.nix b/hardening/nixpaks/modules/slicer-policy.nix new file mode 100644 index 00000000..a501b650 --- /dev/null +++ b/hardening/nixpaks/modules/slicer-policy.nix @@ -0,0 +1,50 @@ +# Shared policy for the wxWidgets/GTK3 slicers. Both applications are X11-only +# and use pasta networking for cloud services and LAN printer discovery. +{ + config, + lib, + sloth, + ... +}: +let + cfg = config.slicer; +in +{ + imports = [ + ./desktop-base.nix + ./network.nix + ]; + options.slicer.enablePulse = lib.mkEnableOption "PulseAudio for Bambu printer video preview"; + config = { + pasta.mode = "isolate"; + bubblewrap = { + sockets = { + x11 = true; + pulse = lib.mkForce cfg.enablePulse; + }; + env = { + DISPLAY = sloth.envOr "DISPLAY" ""; + XAUTHORITY = sloth.envOr "XAUTHORITY" (sloth.concat' sloth.homeDir "/.Xauthority"); + GDK_BACKEND = "x11"; + }; + # File-manager launches pass host paths into the sandbox. Slicers both read + # models and write sliced output, so give the common document directories + # read-write access; the file chooser still handles other locations. + bind.rw = [ + sloth.xdgDocumentsDir + sloth.xdgDownloadDir + sloth.xdgPicturesDir + ]; + }; + portalGroups = { + fileChooser = true; + openUri = true; + notifications = true; + inputMethod = true; + }; + desktopIntegration = { + notifications = true; + accessibility = true; + }; + }; +} diff --git a/hardening/nixpaks/orca-slicer.nix b/hardening/nixpaks/orca-slicer.nix new file mode 100644 index 00000000..982d6597 --- /dev/null +++ b/hardening/nixpaks/orca-slicer.nix @@ -0,0 +1,67 @@ +{ + lib, + orca-slicer, + mkNixPak, + buildEnv, + makeDesktopItem, + runCommandLocal, + ... +}: +let + appId = "com.orcaslicer.OrcaSlicer"; + package = orca-slicer; + + wrapped = mkNixPak { + config = { + imports = [ ./modules/slicer-policy.nix ]; + app = { + inherit package; + binPath = "bin/orca-slicer"; + }; + flatpak = { inherit appId; }; + # WebKitGTK's DMA-BUF renderer produces a blank home page under + # XWayland on NVIDIA; force the software path like Bambu does. + bubblewrap.env.WEBKIT_DISABLE_DMABUF_RENDERER = "1"; + }; + }; + exe = lib.getExe wrapped.config.script; + assets = runCommandLocal "orca-slicer-desktop-assets" { } '' + mkdir -p $out/share + ln -s ${package}/share/icons $out/share/icons + ''; +in +buildEnv { + inherit (wrapped.config.script) name passthru; + meta = wrapped.config.script.meta // { + mainProgram = "orca-slicer"; + }; + paths = [ + wrapped.config.script + assets + (makeDesktopItem { + name = "com.orcaslicer.OrcaSlicer"; + desktopName = "OrcaSlicer"; + genericName = "3D Printing Software"; + comment = "G-code generator for 3D printers"; + exec = "${exe} %U"; + icon = "OrcaSlicer"; + terminal = false; + startupWMClass = "orca-slicer"; + categories = [ + "Graphics" + "3DGraphics" + "Engineering" + ]; + mimeTypes = [ + "model/stl" + "model/3mf" + "application/vnd.ms-3mfdocument" + "application/prs.wavefront-obj" + "application/x-amf" + "x-scheme-handler/orcaslicer" + "model/step" + ]; + extraConfig.X-Flatpak = appId; + }) + ]; +} diff --git a/hardening/tests/policy-lib.nix b/hardening/tests/policy-lib.nix index 371aad46..aa89ddc9 100644 --- a/hardening/tests/policy-lib.nix +++ b/hardening/tests/policy-lib.nix @@ -74,10 +74,9 @@ let mediaCheck = app: expected: if expected then - app.bubblewrap.sockets.pulse && app.portalGroups.camera && app.portalGroups.screenCast + app.portalGroups.camera && app.portalGroups.screenCast else - !app.bubblewrap.sockets.pulse - && !app.bubblewrap.sockets.pipewire + !app.bubblewrap.sockets.pipewire && !app.portalGroups.camera && !app.portalGroups.screenCast && !(hasDbusFragment app "org.freedesktop.portal.Camera") @@ -125,6 +124,7 @@ let "${name}/network" = networkCheck app spec.network; "${name}/x11" = app.bubblewrap.sockets.x11 == (spec.x11 or false); "${name}/media" = mediaCheck app (spec.media or false); + "${name}/pulse" = app.bubblewrap.sockets.pulse == (spec.pulse or (spec.media or false)); "${name}/mpris" = app.desktopIntegration.mpris == (spec.mpris or false); "${name}/notifications" = app.desktopIntegration.notifications == (spec.notifications or true); "${name}/tray" = trayCheck app (spec.tray or false); diff --git a/hardening/tests/policy/bambu-studio.nix b/hardening/tests/policy/bambu-studio.nix new file mode 100644 index 00000000..4eceb9d5 --- /dev/null +++ b/hardening/tests/policy/bambu-studio.nix @@ -0,0 +1,22 @@ +{ + load, + mkAppPolicy, + ... +}: +let + app = load "bambu-studio"; +in +mkAppPolicy "bambu-studio" { + network = "pasta"; + media = false; + pulse = true; + x11 = true; + notifications = true; + extraChecks = { + gdkX11 = app.bubblewrap.env.GDK_BACKEND == "x11"; + fileChooser = app.portalGroups.fileChooser; + openUri = app.portalGroups.openUri; + inputMethod = app.portalGroups.inputMethod; + accessibility = app.desktopIntegration.accessibility; + }; +} diff --git a/hardening/tests/policy/orca-slicer.nix b/hardening/tests/policy/orca-slicer.nix new file mode 100644 index 00000000..e82fd3a2 --- /dev/null +++ b/hardening/tests/policy/orca-slicer.nix @@ -0,0 +1,22 @@ +{ + load, + mkAppPolicy, + ... +}: +let + app = load "orca-slicer"; +in +mkAppPolicy "orca-slicer" { + network = "pasta"; + media = false; + x11 = true; + notifications = true; + extraChecks = { + gdkX11 = app.bubblewrap.env.GDK_BACKEND == "x11"; + webkitDmabuf = app.bubblewrap.env.WEBKIT_DISABLE_DMABUF_RENDERER == "1"; + fileChooser = app.portalGroups.fileChooser; + openUri = app.portalGroups.openUri; + inputMethod = app.portalGroups.inputMethod; + accessibility = app.desktopIntegration.accessibility; + }; +} diff --git a/hardening/tests/runtime-validation.md b/hardening/tests/runtime-validation.md index 8d8a5e5c..105e9bcf 100644 --- a/hardening/tests/runtime-validation.md +++ b/hardening/tests/runtime-validation.md @@ -24,6 +24,8 @@ Nixpak wrappers are accepted in three layers: | WeChat (native nixpak) | Pass | Interactive Wayland validation pending | | Foliate | Pass | Interactive validation pending | | Krita | Pass | Interactive validation pending | +| Bambu Studio | Pass | Interactive validation pending | +| OrcaSlicer | Pass | Interactive validation pending | Intentional exceptions: diff --git a/home/linux/gui/base/creative.nix b/home/linux/gui/base/creative.nix index d8c40aa1..0572f3b5 100644 --- a/home/linux/gui/base/creative.nix +++ b/home/linux/gui/base/creative.nix @@ -1,29 +1,16 @@ { pkgs, pkgs-stable, - pkgs-master, pkgs-blender, ... }: -let - bambu-studio = pkgs.symlinkJoin { - name = "bambu-studio-${pkgs-master.bambu-studio.version}"; - paths = [ pkgs-master.bambu-studio ]; - nativeBuildInputs = [ pkgs.makeWrapper ]; - postBuild = '' - # Work around wxWidgets HiDPI text clipping under fractional scaling. - # https://github.com/bambulab/BambuStudio/issues/4959 - wrapProgram $out/bin/bambu-studio --set GDK_DPI_SCALE 0.8 - ''; - }; -in { home.packages = with pkgs-stable; [ # creative pkgs.nixpaks.krita # digital painting musescore # music notation - pkgs-master.orca-slicer # 3d printer slicer app - bambu-studio # bambu 3d printer slicer app + pkgs.nixpaks.orca-slicer # 3d printer slicer app + pkgs.nixpaks.bambu-studio # bambu 3d printer slicer app pkgs-blender.blender # 3d modeling ];