From c4a00e507cb663f1292f69cb5ca7dc18e4b8254d Mon Sep 17 00:00:00 2001 From: Ryan Yin Date: Wed, 4 Nov 2026 21:21:12 +0800 Subject: [PATCH] fix(nixpak): pass the session TZ through to sandboxes ICU takes the zone name from the /etc/localtime symlink target path. A bind mount turns it into a plain file, which keeps the TZif data but loses the path, so the only identifier left is the ambiguous "CST" abbreviation that ICU reads from the file and maps to America/Chicago. Export TZ from time.timeZone and pass TZ/TZDIR through the nixpak env allowlist. Assisted-by: DeepSeek Harness (deepseek-flash) --- hardening/README.md | 1 + hardening/nixpaks/modules/desktop-base.nix | 5 +++++ hardening/tests/policy-lib.nix | 6 ++++++ modules/nixos/base/i18n.nix | 5 ++++- outputs/x86_64-linux/tests/nixpak-policy/expr.nix | 3 +++ 5 files changed, 19 insertions(+), 1 deletion(-) diff --git a/hardening/README.md b/hardening/README.md index ba1f480c..81d6f9fd 100644 --- a/hardening/README.md +++ b/hardening/README.md @@ -17,6 +17,7 @@ workflow. - Cleared environment; filtered session D-Bus; private `/dev/shm`; per-app `/tmp`. Chromium-family browsers share an app-specific `/tmp` so external URLs reuse one browser instance. - Read-only `/nix/store`; GPU and display; fonts and themes; LAN and Internet. +- Host timezone: `TZ` and `TZDIR` are passed through from the session environment. - Portals for file chooser, URI opening, notifications, input methods and document exports. - No whole host home, system D-Bus, SSH agent, raw USB/serial or raw camera. The only device passthrough is a security key's FIDO interface for Firefox/Chromium/Chrome; see [Limits](#limits). diff --git a/hardening/nixpaks/modules/desktop-base.nix b/hardening/nixpaks/modules/desktop-base.nix index b9618bc6..5d054e77 100644 --- a/hardening/nixpaks/modules/desktop-base.nix +++ b/hardening/nixpaks/modules/desktop-base.nix @@ -151,6 +151,11 @@ "NO_AT_BRIDGE" "QT_QPA_PLATFORMTHEME" "QT_WAYLAND_DISABLE_WINDOWDECORATION" + # ICU takes the zone name from the /etc/localtime symlink target path, but + # the sandbox binds it as a plain file, leaving only the ambiguous "CST" + # abbreviation; pass the name from the session. + "TZ" + "TZDIR" ] (name: sloth.envOr name ""); }; } diff --git a/hardening/tests/policy-lib.nix b/hardening/tests/policy-lib.nix index 9e431200..19129855 100644 --- a/hardening/tests/policy-lib.nix +++ b/hardening/tests/policy-lib.nix @@ -144,6 +144,12 @@ let "${name}/personalFiles" = (app.browserPermissions.personalFiles or false) == (spec.personalFiles or false); "${name}/secretPortal" = secretPortalCheck app (spec.secretPortal or false); + # nixpak resolves "env" entries from the launcher environment. + "${name}/timeZonePassthrough" = + app.bubblewrap.env ? TZ + && app.bubblewrap.env.TZ.type == "env" + && app.bubblewrap.env ? TZDIR + && app.bubblewrap.env.TZDIR.type == "env"; }; extraChecks = lib.mapAttrs' (checkName: value: { name = "${name}/${checkName}"; diff --git a/modules/nixos/base/i18n.nix b/modules/nixos/base/i18n.nix index 287108a1..08725c64 100644 --- a/modules/nixos/base/i18n.nix +++ b/modules/nixos/base/i18n.nix @@ -1,8 +1,11 @@ -{ lib, ... }: +{ config, lib, ... }: { # Set your time zone. A default: the computer-use VMs match their proxy region. time.timeZone = lib.mkDefault "Asia/Shanghai"; + # Passed through to nixpak sandboxes; see hardening/nixpaks/modules/desktop-base.nix. + environment.sessionVariables.TZ = config.time.timeZone; + # English is the default. Chinese overrides only the categories where the # en_US default is factually wrong for China (metric, A4, CNY); everything # else, including date/time names, stays English. diff --git a/outputs/x86_64-linux/tests/nixpak-policy/expr.nix b/outputs/x86_64-linux/tests/nixpak-policy/expr.nix index 7e54e306..a1c76c0e 100644 --- a/outputs/x86_64-linux/tests/nixpak-policy/expr.nix +++ b/outputs/x86_64-linux/tests/nixpak-policy/expr.nix @@ -6,7 +6,10 @@ }: let pkgs = outputs.nixosConfigurations.ai-niri.pkgs; + hostConfig = outputs.nixosConfigurations.ai-niri.config; in +# The sandboxes pass TZ through from the session environment. +assert hostConfig.environment.sessionVariables.TZ == hostConfig.time.timeZone; import ../../../../hardening/tests/policy.nix { inherit pkgs mylib; mkNixPak = inputs.nixpak.lib.nixpak {