mirror of
https://github.com/ryan4yin/nix-config.git
synced 2026-11-13 12:13:46 +01:00
feat(nixpak): clear env, drop system bus, isolate network (#490)
* fix(nixpak): stop exposing the system D-Bus to apps * fix(nixpak): bind only the PulseAudio socket * feat(nixpak): run apps in a new session tied to the launcher * feat(nixpak): start apps from an allowlisted environment * feat(nixpak): isolate app networking with pasta * fix(nixpak): keep Firefox on the host network Assisted-by: Claude Code (claude-opus-5-5)
This commit is contained in:
1 parent
3a6f03814e
commit
707da35996
5 files changed
+74
-21
No files matched your search
+3
-2
@@ -323,8 +323,9 @@ isolation. Source review and socket metadata—not reads of private credentials
|
|||||||
- `ssh -G` reports forwarding for explicit `192.168.*` destinations, but not the sampled `shoryu`
|
- `ssh -G` reports forwarding for explicit `192.168.*` destinations, but not the sampled `shoryu`
|
||||||
alias or GitHub destination. Do not treat forwarding as enabled for every LAN alias, or remove it
|
alias or GitHub destination. Do not treat forwarding as enabled for every LAN alias, or remove it
|
||||||
without checking deployment/build workflows.
|
without checking deployment/build workflows.
|
||||||
- Nixpak apps retain deliberate host networking, GUI/audio/portal/GPU access and selected writable
|
- Nixpak apps retain outbound networking (through pasta, without the host's loopback),
|
||||||
document directories. Firefox additionally retains browserpass/GnuPG integration. These are
|
GUI/audio/portal/GPU access, selected writable document directories, and a read-only view of the
|
||||||
|
whole `/nix/store`. Firefox additionally retains browserpass/GnuPG integration. These are
|
||||||
filesystem sandboxes, not blanket credential or management-network isolation.
|
filesystem sandboxes, not blanket credential or management-network isolation.
|
||||||
- WeChat retains networking and X11 through its FHS wrapper. Source arguments alone do not prove
|
- WeChat retains networking and X11 through its FHS wrapper. Source arguments alone do not prove
|
||||||
complete runtime isolation. App launch, file chooser, IME, audio/video, GPU and disposable-agent
|
complete runtime isolation. App launch, file chooser, IME, audio/video, GPU and disposable-agent
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ let
|
|||||||
sloth,
|
sloth,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
let
|
||||||
|
passwordStore = sloth.concat' sloth.homeDir "/.local/share/password-store";
|
||||||
|
in
|
||||||
{
|
{
|
||||||
app = {
|
app = {
|
||||||
package = firefox;
|
package = firefox;
|
||||||
@@ -34,6 +37,11 @@ let
|
|||||||
./modules/custom-policy.nix
|
./modules/custom-policy.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# Firefox keeps the host network: local development servers and the
|
||||||
|
# mihomo controller live on 127.0.0.1, and pasta's isolated loopback
|
||||||
|
# would hide them. The other sandboxed apps do not need host loopback.
|
||||||
|
pasta.enable = lib.mkForce false;
|
||||||
|
|
||||||
bubblewrap = {
|
bubblewrap = {
|
||||||
# To trace all the home files Firefox accesses, you can use the following nushell command:
|
# To trace all the home files Firefox accesses, you can use the following nushell command:
|
||||||
# just trace-access firefox
|
# just trace-access firefox
|
||||||
@@ -68,7 +76,7 @@ let
|
|||||||
# ================ for browserpass extension ===============================
|
# ================ for browserpass extension ===============================
|
||||||
"/etc/gnupg"
|
"/etc/gnupg"
|
||||||
(sloth.concat' sloth.homeDir "/.gnupg") # gpg's config
|
(sloth.concat' sloth.homeDir "/.gnupg") # gpg's config
|
||||||
(sloth.concat' sloth.homeDir "/.local/share/password-store") # my secrets
|
passwordStore # my secrets
|
||||||
(sloth.concat' sloth.runtimeDir "/gnupg") # for access gpg-agent socket
|
(sloth.concat' sloth.runtimeDir "/gnupg") # for access gpg-agent socket
|
||||||
|
|
||||||
# Match the host's default-app associations and XDG user dirs.
|
# Match the host's default-app associations and XDG user dirs.
|
||||||
@@ -81,6 +89,8 @@ let
|
|||||||
# dconf settings. Not verified as needed; candidate for removal.
|
# dconf settings. Not verified as needed; candidate for removal.
|
||||||
(sloth.concat' sloth.xdgConfigHome "/dconf")
|
(sloth.concat' sloth.xdgConfigHome "/dconf")
|
||||||
];
|
];
|
||||||
|
# browserpass looks for the store here, not at pass's default path.
|
||||||
|
env.PASSWORD_STORE_DIR = passwordStore;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -197,8 +197,8 @@ in
|
|||||||
# Desktop-wide mounts (Wayland/at-spi/gvfsd, the app cache, fontconfig and
|
# Desktop-wide mounts (Wayland/at-spi/gvfsd, the app cache, fontconfig and
|
||||||
# Mesa caches, GTK config, /dev/shm) are provided by ./gui-base.nix, so
|
# Mesa caches, GTK config, /dev/shm) are provided by ./gui-base.nix, so
|
||||||
# each path is bound exactly once. This is what keeps a read-only bind
|
# each path is bound exactly once. This is what keeps a read-only bind
|
||||||
# from shadowing a writable one (see the note on the PulseAudio runtime
|
# from shadowing a writable one. Only app-specific mounts belong in this
|
||||||
# dir there). Only app-specific mounts belong in this file.
|
# file.
|
||||||
bind.rw = with sloth; [
|
bind.rw = with sloth; [
|
||||||
[
|
[
|
||||||
(mkdir appDataDir)
|
(mkdir appDataDir)
|
||||||
|
|||||||
@@ -14,6 +14,9 @@
|
|||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
envSuffix = envKey: suffix: sloth.concat' (sloth.env envKey) suffix;
|
envSuffix = envKey: suffix: sloth.concat' (sloth.env envKey) suffix;
|
||||||
|
# nixpak cannot pass a variable only when it is set, so an unset one arrives
|
||||||
|
# empty; only list variables whose readers treat empty as unset.
|
||||||
|
passHostEnv = names: lib.genAttrs names (name: sloth.envOr name "");
|
||||||
# cursor & icon's theme should be the same as the host's one.
|
# cursor & icon's theme should be the same as the host's one.
|
||||||
cursorTheme = pkgs.bibata-cursors;
|
cursorTheme = pkgs.bibata-cursors;
|
||||||
iconTheme = pkgs.papirus-icon-theme;
|
iconTheme = pkgs.papirus-icon-theme;
|
||||||
@@ -43,15 +46,20 @@ in
|
|||||||
# Nixpak uses --ro-bind-try / --dev-bind-try: absent host paths are skipped,
|
# Nixpak uses --ro-bind-try / --dev-bind-try: absent host paths are skipped,
|
||||||
# so shared mappings work on Intel/NVIDIA hosts and Apple Silicon alike.
|
# so shared mappings work on Intel/NVIDIA hosts and Apple Silicon alike.
|
||||||
network = lib.mkDefault false;
|
network = lib.mkDefault false;
|
||||||
|
# setsid keeps the app off the launching terminal (TIOCSTI), and the
|
||||||
|
# sandbox goes down with the launcher instead of outliving it.
|
||||||
|
newSession = true;
|
||||||
|
dieWithParent = true;
|
||||||
|
|
||||||
# nixpak binds sockets read-only, which is sufficient: connect() only needs
|
# nixpak binds sockets read-only, which is sufficient: connect() only needs
|
||||||
# the socket inode's write bit, and the kernel's read-only-filesystem check
|
# the socket inode's write bit, and the kernel's read-only-filesystem check
|
||||||
# does not apply to sockets. PulseAudio is the exception (see bind.rw).
|
# does not apply to sockets. Every app here is Wayland-only, so X11 stays
|
||||||
# Every app here is Wayland-only, so X11 stays disabled (the nixpak default,
|
# disabled (the nixpak default, spelled out for intent) and PipeWire is
|
||||||
# spelled out for intent) and PipeWire is shared for screensharing.
|
# shared for screensharing.
|
||||||
sockets = {
|
sockets = {
|
||||||
wayland = true;
|
wayland = true;
|
||||||
pipewire = true;
|
pipewire = true;
|
||||||
|
pulse = true;
|
||||||
x11 = false;
|
x11 = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,19 +74,11 @@ in
|
|||||||
# gvfsd is a directory that holds FUSE mounts, not a socket, so it must
|
# gvfsd is a directory that holds FUSE mounts, not a socket, so it must
|
||||||
# stay writable.
|
# stay writable.
|
||||||
(envSuffix "XDG_RUNTIME_DIR" "/gvfsd")
|
(envSuffix "XDG_RUNTIME_DIR" "/gvfsd")
|
||||||
|
|
||||||
# libpulse prepares its runtime dir before connecting (mkdir + chmod 0700),
|
|
||||||
# so this directory must be writable. nixpak's built-in `sockets.pulse`
|
|
||||||
# binds it read-only instead, and libpulse then aborts with EROFS when the
|
|
||||||
# directory is not already 0700 (e.g. pipewire-pulse has not started yet).
|
|
||||||
# Keep this the only bind of $XDG_RUNTIME_DIR/pulse.
|
|
||||||
(envSuffix "XDG_RUNTIME_DIR" "/pulse")
|
|
||||||
];
|
];
|
||||||
bind.ro = [
|
bind.ro = [
|
||||||
# Sockets only need to be connectable, and connect() works on a read-only
|
# Sockets only need to be connectable, and connect() works on a read-only
|
||||||
# bind (see the note on `sockets` above).
|
# bind (see the note on `sockets` above).
|
||||||
(envSuffix "XDG_RUNTIME_DIR" "/at-spi/bus")
|
(envSuffix "XDG_RUNTIME_DIR" "/at-spi/bus")
|
||||||
"/run/dbus"
|
|
||||||
|
|
||||||
(sloth.concat' sloth.xdgConfigHome "/gtk-2.0")
|
(sloth.concat' sloth.xdgConfigHome "/gtk-2.0")
|
||||||
(sloth.concat' sloth.xdgConfigHome "/gtk-3.0")
|
(sloth.concat' sloth.xdgConfigHome "/gtk-3.0")
|
||||||
@@ -112,7 +112,23 @@ in
|
|||||||
"/tmp"
|
"/tmp"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# Start from an empty environment so host secrets (tokens, credentials)
|
||||||
|
# stay outside; everything the apps need is listed here.
|
||||||
|
clearEnv = true;
|
||||||
env = {
|
env = {
|
||||||
|
HOME = sloth.homeDir;
|
||||||
|
USER = sloth.env "USER";
|
||||||
|
XDG_RUNTIME_DIR = sloth.runtimeDir;
|
||||||
|
XDG_CACHE_HOME = sloth.xdgCacheHome;
|
||||||
|
XDG_CONFIG_HOME = sloth.xdgConfigHome;
|
||||||
|
XDG_DATA_HOME = sloth.xdgDataHome;
|
||||||
|
XDG_STATE_HOME = sloth.xdgStateHome;
|
||||||
|
# Same fallback as nixpak's Wayland socket bind, so they always agree.
|
||||||
|
WAYLAND_DISPLAY = sloth.envOr "WAYLAND_DISPLAY" "wayland-0";
|
||||||
|
XDG_SESSION_TYPE = "wayland";
|
||||||
|
# GTK reads an empty GTK_A11Y as an unknown backend; "0" is its unset value.
|
||||||
|
GTK_A11Y = sloth.envOr "GTK_A11Y" "0";
|
||||||
|
|
||||||
XDG_DATA_DIRS = lib.mkForce (
|
XDG_DATA_DIRS = lib.mkForce (
|
||||||
lib.makeSearchPath "share" [
|
lib.makeSearchPath "share" [
|
||||||
iconTheme
|
iconTheme
|
||||||
@@ -126,7 +142,32 @@ in
|
|||||||
"${cursorTheme}/share/pixmaps"
|
"${cursorTheme}/share/pixmaps"
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
// passHostEnv [
|
||||||
|
"LANG"
|
||||||
|
"LANGUAGE"
|
||||||
|
"LC_ALL"
|
||||||
|
"LC_ADDRESS"
|
||||||
|
"LC_COLLATE"
|
||||||
|
"LC_CTYPE"
|
||||||
|
"LC_IDENTIFICATION"
|
||||||
|
"LC_MEASUREMENT"
|
||||||
|
"LC_MESSAGES"
|
||||||
|
"LC_MONETARY"
|
||||||
|
"LC_NAME"
|
||||||
|
"LC_NUMERIC"
|
||||||
|
"LC_PAPER"
|
||||||
|
"LC_TELEPHONE"
|
||||||
|
"LC_TIME"
|
||||||
|
|
||||||
|
"NO_AT_BRIDGE"
|
||||||
|
"NIXOS_XDG_OPEN_USE_PORTAL"
|
||||||
|
"NIXOS_OZONE_WL" # QQ's wrapper adds its Wayland flags only when set
|
||||||
|
"XCURSOR_THEME"
|
||||||
|
"XCURSOR_SIZE"
|
||||||
|
"QT_QPA_PLATFORMTHEME"
|
||||||
|
"QT_WAYLAND_DISABLE_WINDOWDECORATION"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
# https://github.com/nixpak/pkgs/blob/master/pkgs/modules/network.nix
|
# https://github.com/nixpak/pkgs/blob/master/pkgs/modules/network.nix
|
||||||
{
|
{
|
||||||
etc.sslCertificates.enable = true;
|
etc.sslCertificates.enable = true;
|
||||||
bubblewrap = {
|
bubblewrap.network = true;
|
||||||
bind.ro = [ "/etc/resolv.conf" ];
|
# pasta's default isolate mode gives the app its own loopback, so host
|
||||||
network = true;
|
# services on 127.0.0.1 are out of reach. Proxying still works because
|
||||||
};
|
# mihomo runs in TUN mode, which sees pasta's outbound traffic.
|
||||||
|
pasta.enable = true;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user