mirror of
https://github.com/ryan4yin/nix-config.git
synced 2026-11-13 12:13:46 +01:00
* refactor(nixpak): unify desktop app sandboxes Move common GUI, portal, and network policy into reusable modules and migrate browsers plus Zoom/WeChat/QQ/Telegram to the same boundary. Assisted-by: DeepSeek Harness (deepseek-flash)
222 lines
7.3 KiB
Nix
222 lines
7.3 KiB
Nix
# Evaluate with the same pkgs/mkNixPak as the wrappers. Returns true or identifies
|
|
# the violated policy boundary; does not build or launch an application.
|
|
{ pkgs, mkNixPak }:
|
|
let
|
|
inherit (pkgs) lib;
|
|
load = name: (pkgs.callPackage (../nixpaks + "/${name}.nix") { inherit mkNixPak; }).config;
|
|
firefox = load "firefox";
|
|
chromium = load "chromium";
|
|
chrome = load "google-chrome";
|
|
restrictedBrowsers = map load [
|
|
"brave-origin"
|
|
"microsoft-edge"
|
|
];
|
|
chromiumFamily = map load [
|
|
"chromium"
|
|
"google-chrome"
|
|
"brave-origin"
|
|
"microsoft-edge"
|
|
];
|
|
browsers = map load [
|
|
"firefox"
|
|
"chromium"
|
|
"google-chrome"
|
|
"brave-origin"
|
|
"microsoft-edge"
|
|
];
|
|
nonBrowserApps = map load [
|
|
"qq"
|
|
"telegram-desktop"
|
|
"zoom"
|
|
"wechat"
|
|
];
|
|
browserAndChatApps =
|
|
browsers
|
|
++ map load [
|
|
"qq"
|
|
"telegram-desktop"
|
|
];
|
|
apps = browsers ++ nonBrowserApps;
|
|
# Inspect effective proxy arguments, not just capability switches: an extra
|
|
# direct service grant could otherwise bypass a disabled portal group.
|
|
forbiddenDbus =
|
|
argument:
|
|
lib.any (name: lib.hasInfix name argument) [
|
|
"org.freedesktop.portal.Usb"
|
|
"org.freedesktop.portal.RemoteDesktop"
|
|
"org.gnome.Shell.Screencast"
|
|
]
|
|
|| argument == "--talk=org.freedesktop.portal.Desktop";
|
|
withoutSecretPortal =
|
|
calls:
|
|
calls
|
|
// {
|
|
"org.freedesktop.portal.Desktop" = lib.filter (
|
|
arg: !lib.hasInfix "org.freedesktop.portal.Secret" arg
|
|
) calls."org.freedesktop.portal.Desktop";
|
|
};
|
|
mountStrings =
|
|
app: builtins.filter builtins.isString (lib.flatten (with app.bubblewrap.bind; rw ++ ro ++ dev));
|
|
forbiddenMount =
|
|
path:
|
|
path == "/dev"
|
|
|| lib.hasPrefix "/dev/video" path
|
|
|| lib.hasPrefix "/dev/bus/usb" path
|
|
|| path == "/run/dbus"
|
|
|| path == "/run/dbus/system_bus_socket";
|
|
checks = {
|
|
personalBrowserCapabilitiesMatch =
|
|
firefox.browserPermissions.media == chromium.browserPermissions.media
|
|
&& firefox.browserPermissions.personalFiles == chromium.browserPermissions.personalFiles
|
|
&& firefox.browserPermissions.browserpass == chromium.browserPermissions.browserpass
|
|
&&
|
|
(removeAttrs firefox.portalGroups [ "secret" ]) == (removeAttrs chromium.portalGroups [ "secret" ])
|
|
&& firefox.desktopIntegration == chromium.desktopIntegration;
|
|
personalBrowserPortalCallsMatch =
|
|
withoutSecretPortal firefox.dbus.rules.call == withoutSecretPortal chromium.dbus.rules.call;
|
|
personalBrowserHostLoopback = lib.all (app: app.bubblewrap.network && !app.pasta.enable) [
|
|
firefox
|
|
chromium
|
|
];
|
|
workBrowserIsolatedLoopback =
|
|
chrome.bubblewrap.network
|
|
&& chrome.pasta.enable
|
|
&& chrome.pasta.mode == "isolate"
|
|
&& lib.elem "--no-map-gw" chrome.pasta.args;
|
|
zoomDrmSysfs = lib.elem "/sys/class/drm" (load "zoom").bubblewrap.bind.ro;
|
|
zoomCameraDevices =
|
|
let
|
|
zoom = load "zoom";
|
|
in
|
|
lib.all (dev: lib.elem dev zoom.bubblewrap.bind.dev) [
|
|
"/dev/video0"
|
|
"/dev/video1"
|
|
];
|
|
fhsTrayIntegration =
|
|
lib.all
|
|
(
|
|
app:
|
|
app.desktopIntegration.tray
|
|
&& app.dbus.policies."org.freedesktop.DBus" == "talk"
|
|
&& app.dbus.policies."org.kde.StatusNotifierWatcher" == "talk"
|
|
&& app.dbus.policies."org.kde.StatusNotifierItem-2-1" == "own"
|
|
&& app.dbus.policies."org.kde.StatusNotifierItem-2-2" == "own"
|
|
)
|
|
(
|
|
map load [
|
|
"wechat"
|
|
"zoom"
|
|
]
|
|
);
|
|
appTmpIsolation =
|
|
let
|
|
tmpPath =
|
|
app:
|
|
lib.findFirst (
|
|
entry: builtins.isList entry && builtins.length entry == 2 && builtins.elem "/tmp" entry
|
|
) null app.bubblewrap.bind.rw;
|
|
paths = map tmpPath apps;
|
|
in
|
|
lib.all (path: builtins.isList path && builtins.length path == 2) paths
|
|
&& lib.length (lib.unique (map builtins.head paths)) == builtins.length apps;
|
|
workBrowserMedia =
|
|
chrome.portalGroups.camera
|
|
&& chrome.portalGroups.screenCast
|
|
&& chrome.bubblewrap.sockets.pulse
|
|
&& chrome.gpu.enable
|
|
&& lib.elem "--call=org.freedesktop.portal.Desktop=org.freedesktop.portal.Camera.OpenPipeWireRemote@/org/freedesktop/portal/desktop" chrome.dbus.args
|
|
&& lib.elem "--call=org.freedesktop.portal.Desktop=org.freedesktop.portal.ScreenCast.OpenPipeWireRemote@/org/freedesktop/portal/desktop" chrome.dbus.args;
|
|
secretPortalScope =
|
|
lib.all (
|
|
app:
|
|
app.browserPermissions.secretPortal
|
|
&& lib.any (
|
|
arg:
|
|
arg
|
|
== "--call=org.freedesktop.portal.Desktop=org.freedesktop.portal.Secret.RetrieveSecret@/org/freedesktop/portal/desktop"
|
|
) app.dbus.args
|
|
&& !lib.any (arg: arg == "--talk=org.freedesktop.secrets") app.dbus.args
|
|
) chromiumFamily
|
|
&& !firefox.browserPermissions.secretPortal
|
|
&& lib.all (
|
|
app:
|
|
!(app.browserPermissions.secretPortal or false)
|
|
&& !lib.any (arg: lib.hasInfix "org.freedesktop.portal.Secret" arg) app.dbus.args
|
|
) nonBrowserApps;
|
|
mediaScope =
|
|
lib.all
|
|
(
|
|
app:
|
|
app.browserPermissions.media
|
|
&& app.bubblewrap.sockets.pulse
|
|
&& app.portalGroups.camera
|
|
&& app.portalGroups.screenCast
|
|
)
|
|
[
|
|
firefox
|
|
chromium
|
|
chrome
|
|
];
|
|
restrictedBrowserMedia = lib.all (
|
|
app:
|
|
!app.browserPermissions.media
|
|
&& !app.bubblewrap.sockets.pulse
|
|
&& !app.bubblewrap.sockets.pipewire
|
|
&& !app.portalGroups.camera
|
|
&& !app.portalGroups.screenCast
|
|
&& !app.desktopIntegration.mpris
|
|
&& !lib.any (
|
|
arg:
|
|
lib.hasInfix "org.freedesktop.portal.Camera" arg
|
|
|| lib.hasInfix "org.freedesktop.portal.ScreenCast" arg
|
|
|| lib.hasInfix "org.mpris.MediaPlayer2" arg
|
|
) app.dbus.args
|
|
&& !lib.any (path: lib.hasPrefix "/dev/snd" path) (mountStrings app)
|
|
) restrictedBrowsers;
|
|
personalFileScope =
|
|
firefox.browserPermissions.personalFiles
|
|
&& chromium.browserPermissions.personalFiles
|
|
&& !chrome.browserPermissions.personalFiles;
|
|
browserpassScope =
|
|
lib.all (app: app.browserPermissions.browserpass) [
|
|
firefox
|
|
chromium
|
|
chrome
|
|
]
|
|
&& lib.all (app: !app.browserPermissions.browserpass) (
|
|
map load [
|
|
"brave-origin"
|
|
"microsoft-edge"
|
|
]
|
|
);
|
|
deniedServices = lib.all (app: !lib.any forbiddenDbus app.dbus.args) apps;
|
|
noBroadKeyring = lib.all (
|
|
app: !lib.any (arg: arg == "--talk=org.freedesktop.secrets") app.dbus.args
|
|
) apps;
|
|
deniedDevicesAndSystemBus =
|
|
let
|
|
zoomVideoDevices = [
|
|
"/dev/video0"
|
|
"/dev/video1"
|
|
];
|
|
forbiddenMountFor =
|
|
app:
|
|
if app.flatpak.appId == "us.zoom.Zoom" then
|
|
path: forbiddenMount path && !lib.elem path zoomVideoDevices
|
|
else
|
|
forbiddenMount;
|
|
in
|
|
lib.all (app: !lib.any (forbiddenMountFor app) (mountStrings app)) apps;
|
|
filteredEnvironmentAndMedia = lib.all (
|
|
app:
|
|
app.bubblewrap.clearEnv
|
|
&& app.dbus.enable
|
|
&& !app.bubblewrap.sockets.pipewire
|
|
&& !app.bubblewrap.sockets.x11
|
|
) browserAndChatApps;
|
|
};
|
|
in
|
|
lib.all (name: checks.${name} || throw "nixpak policy check failed: ${name}") (
|
|
builtins.attrNames checks
|
|
)
|