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 {