From 95798b14f66037484bf3b28adaed7a599921779b Mon Sep 17 00:00:00 2001 From: Ryan Lahfa Date: Thu, 27 Nov 2025 02:48:26 +0100 Subject: [PATCH 1/9] modules/tools/browsers: init This rework the abstraction module for Firefox into a generic abstraction for both major browsers: Chromium and Firefox. This enables developers to let user choose between Firefox and Chromium and configure them uniformly. Obviously, abstracting all options is impossible, so a developer should always consider using the fine-grained API of a browser if needed. Signed-off-by: Ryan Lahfa --- modules/tools/browsers/chromium.nix | 277 ++++++++++++++++++ modules/tools/browsers/default.nix | 134 +++++++++ modules/tools/browsers/firefox.nix | 216 ++++++++++++++ modules/tools/browsers/homepage-dashboard.nix | 30 ++ modules/tools/browsers/option-types.nix | 101 +++++++ modules/tools/default.nix | 2 +- modules/tools/firefox.nix | 168 ----------- 7 files changed, 759 insertions(+), 169 deletions(-) create mode 100644 modules/tools/browsers/chromium.nix create mode 100644 modules/tools/browsers/default.nix create mode 100644 modules/tools/browsers/firefox.nix create mode 100644 modules/tools/browsers/homepage-dashboard.nix create mode 100644 modules/tools/browsers/option-types.nix delete mode 100644 modules/tools/firefox.nix diff --git a/modules/tools/browsers/chromium.nix b/modules/tools/browsers/chromium.nix new file mode 100644 index 00000000..5cb4b6b0 --- /dev/null +++ b/modules/tools/browsers/chromium.nix @@ -0,0 +1,277 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# SPDX-FileContributor: Elias Coppens +# +# SPDX-License-Identifier: MIT + +{ + config, + pkgs, + lib, + ... +}: +let + inherit (lib) + mkOption + mkEnableOption + mkIf + mapAttrsToList + concatStringsSep + ; + inherit (lib.types) + attrsOf + enum + submodule + nullOr + listOf + str + ; + inherit (import ./option-types.nix { inherit lib; }) lockFlagEnum bookmarkType proxyConfig; + + cfg = config.securix.chromium; +in +{ + options.securix.chromium = { + enable = mkEnableOption "Chromium pre-configuration"; + enableEncryptedMediaExtensions = mkEnableOption '' + allow encrypted media extensions to be used. + + This is required for websites like Netflix or YouTube. + ''; + + lockFlags = mkOption { + type = enum lockFlagEnum; + + default = [ + "allow-default-overrides" + "allow-user-messaging-overrides" + ]; + + description = '' + The lock flags determine how locked down the Firefox configuration is. + + By default, we do not let the user install any extension, but we still + let them modify the Firefox defaults. + ''; + }; + + proxy = mkOption { + type = nullOr submodule proxyConfig; + default = null; + description = '' + Proxy configuration for this instance of Firefox. + By default, it configures nothing. + ''; + }; + + extensions = mkOption { + type = listOf str; + default = [ ]; + example = [ + "gcbommkclmclpchllfjekcdonpmejbdp" # https everywhere + "cjpalhdlnbpafiamejdnhcphjbkeiagm" # ublock origin + ]; + description = '' + List of extension IDs to install from the Chrome store. + ''; + }; + + bookmarks = mkOption { + type = attrsOf (attrsOf (submodule bookmarkType)); + default = { }; + example = '' + { + Productivity = { + Github = { + href = "https://github.com"; + icon = "github.png"; + }; + }; + + Entertainment = { + Youtube = { + href = "https://youtube.com"; + icon = "si-youtube"; + }; + }; + } + ''; + description = '' + Bookmarks to show to homepage and firefox bookmarks. + ''; + }; + }; + + config = mkIf cfg.enable { + programs.chromium = { + enable = true; + + initialPrefs = { + # Always show the bookmark bar. + BookmarkBarEnabled = true; + # Always let the user create more profiles. + BrowserAddPersonEnabled = true; + # Always let the user use the guest mode if they want. + BrowserGuestModeEnabled = true; + # Let the user edit bookmarks if needed. + EditBookmarksEnabled = true; + + # TODO: allow customization of the label. + EnterpriseCustomLabel = "Securix"; + EnterpriseCustomLabelForBrowser = "Securix"; + # EnterpriseLogoUrl = ""; + # EnterpriseLogoUrlForBrowser = ""; + # TODO: should we enable this security? + # EnterpriseRealTimeUrlCheckMode = false; + + # Try to upgrade connections to HTTPS as much as possible. + HttpsUpgradesEnabled = true; + + # Here are a bunch of power savings knobs. + # We try to optimize for a moderate power saving experience + # by default. + HighEfficiencyModeEnabled = true; + IntensiveWakeUpThrottlingEnabled = true; + MemorySaverModeSavings = 1; # 0 or 2 + BatterySaverModeAvailability = true; + + # Allow DNS interception to determine whether + # we have a proxy that knows how to deal with certain DNS. + # Allow to suggest "Try http://intranet" error messages.u + IntranetRedirectBehavior = 3; + + # Home page != New tab page. + HomepageIsNewTabPage = false; + HomepageLocation = ""; + # Always restore previous tabs on startup. + RestoreOnStartup = 1; + # Show the home button. + ShowHomeButton = true; + + # In general, Autoplay is never a fun feature. + AutoplayAllowed = false; + + # Some websites may require it. + BlockThirdPartyCookies = true; + }; + + extraOpts = { + # Isolate all origins into their own process/sandbox. + IsolateOrigins = true; + # Block any external extension to install. + BlockExternalExtensions = true; + # Block developer mode for extensions. + ExtensionDeveloperModeSettings = 1; + + # Allow system CA certificates. + CAPlatformIntegrationEnabled = true; + # Let the user only provision *user* certificates. + CACertificateManagementAllowed = 1; + + # Do not use Chromium native password manager. + PasswordManagerEnabled = false; + + # Forbid all generative AI from Google. + GenAiSettings = 2; + BuiltInAIAPIIsEnabled = false; + + # Do not let the browser use Google to obtain accurate time information. + BrowserNetworkTimeQueriesEnabled = false; + + # Disable Google feedback surveys. + FeedbackSurveysEnabled = false; + # Disable Google Web Store icon. + HideWebStoreIcon = true; + # Disable any telemetry to Google. + MetricsReportingEnabled = false; + # Disable any advertising from Google. + PromotionsEnabled = false; + # Do not recommend media. + MediaRecommendationsEnabled = false; + # Do not report domain reliability to Google. + DomainReliabilityAllowed = false; + # Ensure that the Accept-Language and navigator.languages options + # are privacy-preserving. + ReduceAcceptLanguageEnabled = true; + + # These options controls nudges to the user + # to restart Chromium to benefit from updates. + # Sometimes, critical security updates. + # Or 1 + RelaunchNotification = 2; + # Every day. + RelaunchNotificationPeriod = 86400000; + + # RequireOnlineRevocationChecksForLocalAnchors = true; + + # Do not allow the browser connect to a Google account. + # Even if the user logs in to any Google service. + BrowserSignin = 0; + + # Disable the usage of built-in DNS client. + # Use the system DNS resolver. + BuiltInDnsClientEnabled = false; + + # TODO: allow DOH by default? + # More secure than usual DNS. + # DnsOverHttpsMode = "automatic"; + # DnsOverHttpsTemplates = ""; + + # Do not care if Chromium is by default. + DefaultBrowserSettingEnabled = false; + + # New Tab parameters. + # Provide some customizations options someday. + NTPCardsVisible = false; + NTPCustomBackgroundEnabled = false; + NTPFooterExtensionAttributionEnabled = true; + NTPFooterManagementNoticeEnabled = true; + + # Future architecture for the PDF viewer. + PdfViewerOutOfProcessIframeEnabled = true; + + # Enable PQC options by default. + PostQuantumKeyAgreementEnabled = true; + # Enable HTTP/3 QUIC by default. + QuicAllowed = true; + # Do not let WebAuthn store credentials on broken TLS certificates. + AllowWebAuthnWithBrokenTlsCerts = false; + + # HTTP proxy + ProxySettings = { + ProxyMode = + if cfg.proxy.httpProxy != null then + "fixed_servers" + else if cfg.proxy.autoConfigUrl != null then + "pac_script" + else + "auto_detect"; + ProxyBypassList = concatStringsSep "," cfg.proxy.noProxy; + # TODO: expose an option called `cfg.proxy.autoConfigFailSafe` + ProxyPacMandatory = false; + ProxyPacUrl = mkIf (cfg.proxy.autoConfigUrl != null) cfg.proxy.autoConfigUrl; + ProxyServer = mkIf (cfg.proxy.httpProxy != null) cfg.proxy.httpProxy; + }; + + # Pre-installed bookmarks + ManagedBookmarks = + let + mkItems = mapAttrsToList ( + name: + { href, ... }: + { + inherit name; + url = href; + } + ); + mkChildren = folder: values: { + children = mkItems values; + name = folder; + }; + in + mapAttrsToList mkChildren cfg.bookmarks; + }; + + inherit (cfg) extensions; + }; + }; +} diff --git a/modules/tools/browsers/default.nix b/modules/tools/browsers/default.nix new file mode 100644 index 00000000..e0b0509d --- /dev/null +++ b/modules/tools/browsers/default.nix @@ -0,0 +1,134 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# +# SPDX-License-Identifier: MIT + +{ config, lib, ... }: +let + cfg = config.securix.browser; + inherit (lib) + mkIf + mkEnableOption + types + mkOption + ; + inherit (import ./option-types.nix { inherit lib; }) lockFlagEnum bookmarkType; + + homepage = if cfg.enableLocalHomepage then "http://127.0.0.1:8082" else cfg.homepage; +in +{ + options.securix.browser = { + enable = mkEnableOption "browser preconfiguration"; + enableLocalHomepage = mkEnableOption "the local dynamic homepage"; + enableEncryptedMediaExtensions = mkEnableOption '' + allow encrypted media extensions to be used. + + This is required for websites like Netflix or YouTube. + ''; + + lockFlags = mkOption { + type = types.listOf lockFlagEnum; + description = '' + The lock flags determine how locked down the browser configuration is. + + By default, we do not let the user install any extension, but we still + let them modify the defaults. + ''; + }; + + homepage = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + URL to the home page. + The home page is usually locked by default. + ''; + }; + + browsers = mkOption { + type = types.listOf ( + types.enum [ + "firefox" + "chromium" + ] + ); + # This is the default recommended one. + default = [ "chromium" ]; + }; + + extensions = mkOption { + type = types.attrsOf (types.either types.attrs types.list); + description = '' + Per-browser attribute set of list of extensions to install in each instance. + ''; + }; + + bookmarks = mkOption { + type = types.attrsOf (types.attrsOf (types.submodule bookmarkType)); + example = '' + { + Productivity = { + Github = { + href = "https://github.com"; + icon = "github.png"; + }; + }; + + Entertainment = { + Youtube = { + href = "https://youtube.com"; + icon = "si-youtube"; + }; + }; + } + ''; + description = '' + Folders of bookmarks with their icons and link target. + ''; + }; + }; + + imports = [ + ./firefox.nix + ./chromium.nix + ./homepage-dashboard.nix + ]; + + config = mkIf cfg.enable { + securix.browser = { + # NOTE: this is a backward compatibility default. + enableLocalHomepage = lib.mkDefault (lib.elem "firefox" cfg.browsers); + extensions = { + firefox = lib.mkDefault { ublock-origin = "uBlock0@raymondhill.net"; }; + chromium = lib.mkDefault [ + "ddkjiahejlhfcafbddmgiahcphecmpfh" # uBlock Origin Lite + ]; + }; + + lockFlags = lib.mkDefault [ + "allow-user-messaging-overrides" + "allow-default-overrides" + ]; + }; + + securix.homepage-dashboard = { + enable = cfg.enableLocalHomepage; + inherit (cfg) bookmarks; + }; + + securix.firefox = { + enable = lib.elem "firefox" cfg.browsers; + inherit (cfg) enableEncryptedMediaExtensions; + extensions = cfg.extensions.firefox or [ ]; + inherit (cfg) bookmarks lockFlags; + inherit homepage; + }; + + securix.chromium = { + enable = lib.elem "chromium" cfg.browsers; + inherit (cfg) enableEncryptedMediaExtensions; + extensions = cfg.extensions.chromium or { }; + inherit (cfg) bookmarks lockFlags; + inherit homepage; + }; + }; +} diff --git a/modules/tools/browsers/firefox.nix b/modules/tools/browsers/firefox.nix new file mode 100644 index 00000000..731dd70d --- /dev/null +++ b/modules/tools/browsers/firefox.nix @@ -0,0 +1,216 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# SPDX-FileContributor: Elias Coppens +# +# SPDX-License-Identifier: MIT + +{ config, lib, ... }: +let + inherit (lib) + mkOption + mkEnableOption + mkIf + mapAttrs' + ; + inherit (lib.types) + attrsOf + enum + submodule + str + nullOr + ; + inherit (import ./option-types.nix { inherit lib; }) lockFlagEnum bookmarkType proxyConfig; + + cfg = config.securix.firefox; +in +{ + options.securix.firefox = { + enable = mkEnableOption "Firefox pre-configuration"; + enableEncryptedMediaExtensions = mkEnableOption '' + allow encrypted media extensions to be used. + + This is required for websites like Netflix or YouTube. + ''; + + lockFlags = mkOption { + type = enum lockFlagEnum; + default = [ + "allow-default-overrides" + "allow-user-messaging-overrides" + ]; + + description = '' + The lock flags determine how locked down the Firefox configuration is. + + By default, we do not let the user install any extension, but we still + let them modify the Firefox defaults. + ''; + }; + + proxy = mkOption { + type = nullOr submodule proxyConfig; + default = null; + description = '' + Proxy configuration for this instance of Firefox. + By default, it configures nothing. + ''; + }; + + extensions = mkOption { + type = attrsOf str; + default = { }; + description = '' + Attribute set of extensions to install to the Firefox instance. + + The key should be the short ID of the extension in the Mozilla store. + The value should be the UUID. + ''; + example = { + bitwarden-password-manager = "{446900e4-71c2-419f-a6a7-df9c091e268b}"; + }; + }; + + bookmarks = mkOption { + type = attrsOf (attrsOf (submodule bookmarkType)); + default = { }; + example = '' + { + Productivity = { + Github = { + href = "https://github.com"; + icon = "github.png"; + }; + }; + + Entertainment = { + Youtube = { + href = "https://youtube.com"; + icon = "si-youtube"; + }; + }; + } + ''; + description = '' + Bookmarks to show to homepage and firefox bookmarks. + ''; + }; + }; + + config = mkIf cfg.enable { + programs.firefox = { + enable = true; + languagePacks = [ + "fr" + "en-US" + ]; + + policies = { + Homepage = { + URL = cfg.homepage; + # By default, the user is not allowed to update the homepage. + # This can be bypassed if the lock flag contains an allow element. + Locked = !lib.elem "allow-homepage-overrides" cfg.lockFlags; + # homepage-locked will prevent the user from restoring session, that's bad UX! + StartPage = "homepage"; + }; + + Bookmarks = lib.flatten ( + map ( + folder: + map ( + { name, value }: + { + Title = name; + URL = value.href; + Folder = folder.name; + } + ) (lib.attrsToList folder.value) + ) (lib.attrsToList cfg.bookmarks) + ); + + DisplayBookmarksToolbar = "always"; + DisableProfileImport = true; + NoDefaultBookmarks = true; + NewTabPage = false; + + # Don't save password on Firefox to avoid data losses + PasswordManagerEnabled = false; + OfferToSaveLogins = false; + + # Unnecessary. + DontCheckDefaultBrowser = true; + # Firefox version is managed by Sécurix + AppAutoUpdate = false; + DisableAppUpdate = true; + + # By default, we disable DRMs APIs which makes little sense + # on an admin laptop. Office laptops might want to re-enable this. + EncryptedMediaExtensions = { + Enabled = lib.mkDefault cfg.enableEncryptedMediaExtensions; + }; + + Proxy = mkIf (cfg.proxy != null) { + Mode = + if cfg.proxy.httpProxy != null then + "manual" + else if cfg.proxy.autoConfigURL != null then + "autoConfig" + else + "autoDetect"; + + Locked = cfg.proxy.locked; + + HTTPProxy = mkIf (cfg.proxy.httpProxy != null) cfg.proxy.httpProxy; + UseHTTPProxyForAllProtocols = mkIf (cfg.proxy.httpProxy != null) true; + SOCKSVersion = 5; + + Passthrough = cfg.proxy.noProxy; + AutoConfigURL = mkIf (cfg.proxy.autoConfigURL != null) cfg.proxy.autoConfigURL; + + AutoLogin = lib.mkDefault true; + UseProxyForDNS = lib.mkDefault true; + }; + + ExtensionSettings = + let + extension = shortId: uuid: { + name = uuid; + value = { + install_url = "https://addons.mozilla.org/en-US/firefox/downloads/latest/${shortId}/latest.xpi"; + installation_mode = "normal_installed"; + }; + }; + in + { + # By default, we will block any extension installs. + # This is what makes the most sense on an admin laptop + # and any IT operated asset. + # In certain cases, the browser could be unlocked to simplify operations + # e.g. you use your own extension store. + "*".installation_mode = + if lib.elem "allow-extension-installs" cfg.lockFlags then "allowed" else "blocked"; + } + // mapAttrs' extension cfg.extensions; + + DisablePocket = true; + DisableFirefoxAccounts = true; + DisableTelemetry = true; + DisableFirefoxStudies = true; + + UserMessaging = { + ExtensionRecommendations = false; + UrlbarInterventions = false; + MoreFromMozilla = false; + FirefoxLabs = false; + # If people wants to get spammed by Firefox… They can. + # We may want to lock down user messaging for some hardening reason. + Locked = !lib.elem "allow-user-messaging-overrides" cfg.lockFlags; + }; + }; + + # By default, we would allow the user to override the preferences. + # In certain cases, we may want to lock further down this. + preferencesStatus = + if lib.elem "allow-default-overrides" cfg.lockFlags then "default" else "locked"; + }; + }; +} diff --git a/modules/tools/browsers/homepage-dashboard.nix b/modules/tools/browsers/homepage-dashboard.nix new file mode 100644 index 00000000..9264b088 --- /dev/null +++ b/modules/tools/browsers/homepage-dashboard.nix @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# SPDX-FileContributor: Elias Coppens +# +# SPDX-License-Identifier: MIT + +{ config, lib, ... }: +let + cfg = config.securix.homepage-dashboard; + inherit (lib) mkIf mkOption mkEnableOption; +in +{ + config = mkIf cfg.enable { + # This spawns the dashboard on 127.0.0.1:8082. + services.homepage-dashboard = { + enable = true; + + bookmarks = map ( + { name, value }: + { + ${name} = map ( + { name, value }: + { + ${name} = [ value ]; + } + ) (lib.attrsToList value); + } + ) (lib.attrsToList cfg.bookmarks); + }; + }; +} diff --git a/modules/tools/browsers/option-types.nix b/modules/tools/browsers/option-types.nix new file mode 100644 index 00000000..62c2271a --- /dev/null +++ b/modules/tools/browsers/option-types.nix @@ -0,0 +1,101 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# SPDX-FileContributor: Elias Coppens +# +# SPDX-License-Identifier: MIT + +{ lib, ... }: +let + inherit (lib) mkOption; + inherit (lib.types) + str + bool + listOf + nullOr + ; +in +{ + bookmarkType = { + options = { + icon = mkOption { + type = str; + default = ""; + description = '' + Name of the icon of the bookmark. + ''; + }; + + href = mkOption { + type = str; + description = '' + URL of the website that the bookmark points to. + ''; + }; + + description = mkOption { + type = str; + default = ""; + description = '' + Description of the website that the bookmark points to. + ''; + }; + }; + }; + + # To support browser-specific lock flag, + # expand the browser-specific enum, not this one. + lockFlagEnum = [ + "allow-extension-installs" + "allow-default-overrides" + "allow-user-messaging-overrides" + "allow-homepage-overrides" + ]; + + proxyConfig = { + options = { + locked = mkOption { + type = bool; + default = false; + description = '' + Whether the proxy options can be changed or not by the user. + + By default, it is always possible as an admin user may need + to workaround a broken proxy configuration. + + Defense against proxy bypasses cannot rely on this option. + + This can be locked to avoid user errors, a firewall configuration + needs to be enabled to ensure security. + ''; + }; + + noProxy = mkOption { + type = listOf str; + default = [ ]; + description = '' + List of exempted URIs for the proxy. + ''; + }; + + autoConfigURL = mkOption { + type = nullOr str; + default = null; + description = '' + PAC URL to automatically configure the proxy. + + https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file + ''; + }; + + httpProxy = mkOption { + type = nullOr str; + default = null; + description = '' + URL to the HTTP proxy. + This proxy will be used for: SSL, FTP, SOCKS5 as well. + + SOCKS4 is not supported. + ''; + }; + }; + }; +} diff --git a/modules/tools/default.nix b/modules/tools/default.nix index 142d2ca8..62ce8f07 100644 --- a/modules/tools/default.nix +++ b/modules/tools/default.nix @@ -15,7 +15,7 @@ let cfg = config.securix.tools; in { - imports = [ ./firefox.nix ]; + imports = [ ./browsers ]; options.securix.tools = { enable = mkEnableOption "Install tools"; diff --git a/modules/tools/firefox.nix b/modules/tools/firefox.nix deleted file mode 100644 index 9c907e65..00000000 --- a/modules/tools/firefox.nix +++ /dev/null @@ -1,168 +0,0 @@ -# SPDX-FileCopyrightText: 2025 Ryan Lahfa -# SPDX-FileContributor: Elias Coppens -# -# SPDX-License-Identifier: MIT - -{ - config, - pkgs, - lib, - ... -}: -let - inherit (lib) listToAttrs mkOption; - inherit (lib.types) attrsOf submodule str; - - cfg = config.securix.firefox; - - bookmarkType = submodule { - options = { - icon = mkOption { - type = str; - default = ""; - description = '' - Name of the icon of the bookmark. - ''; - }; - - href = mkOption { - type = str; - description = '' - URL of the website that the bookmark points to. - ''; - }; - - description = mkOption { - type = str; - default = ""; - description = '' - Description of the website that the bookmark points to. - ''; - }; - }; - }; -in -{ - options.securix.firefox.bookmarks = mkOption { - type = attrsOf (attrsOf bookmarkType); - default = { }; - example = '' - { - Productivity = { - Github = { - href = "https://github.com"; - icon = "github.png"; - }; - }; - - Entertainment = { - Youtube = { - href = "https://youtube.com"; - icon = "si-youtube"; - }; - }; - } - ''; - description = '' - Bookmarks to show to homepage and firefox bookmarks. - ''; - }; - - config = { - # This spawns the dashboard on 127.0.0.1:8082. - services.homepage-dashboard = { - enable = true; - - bookmarks = map ({ name, value }: { - ${name} = map ({ name, value }: { ${name} = [ value ]; }) (lib.attrsToList value); - }) (lib.attrsToList cfg.bookmarks); - }; - - programs.firefox = { - enable = true; - languagePacks = [ - "fr" - "en-US" - ]; - - nativeMessagingHosts.packages = [ pkgs.tridactyl-native ]; - - policies = { - Homepage = { - # Connect to the local dashboard. - URL = "http://127.0.0.1:8082"; - # The user cannot change the homepage. All changes should go via Sécurix. - Locked = true; - # homepage-locked will prevent the user from restoring session, that's bad UX! - StartPage = "homepage"; - }; - - Bookmarks = lib.flatten ( - map ( - folder: - map ({ name, value }: { - Title = name; - URL = value.href; - Folder = folder.name; - }) (lib.attrsToList folder.value) - ) (lib.attrsToList cfg.bookmarks) - ); - DisplayBookmarksToolbar = "always"; - DisableProfileImport = true; - NoDefaultBookmarks = true; - NewTabPage = false; - - # Don't save password on Firefox to avoid data losses - PasswordManagerEnabled = false; - OfferToSaveLogins = false; - - # Unnecessary. - DontCheckDefaultBrowser = true; - # Firefox version is managed by Sécurix - AppAutoUpdate = false; - DisableAppUpdate = true; - - # You are not supposed to watch Netflix on Sécurix. - EncryptedMediaExtensions = { - Enabled = lib.mkDefault false; - }; - - ExtensionSettings = - let - extension = shortId: uuid: { - name = uuid; - value = { - install_url = "https://addons.mozilla.org/en-US/firefox/downloads/latest/${shortId}/latest.xpi"; - installation_mode = "normal_installed"; - }; - }; - in - { - # Block all manual extension install. You NEED to propose your extension to the Sécurix repository. - "*".installation_mode = "blocked"; - } - // (listToAttrs [ - (extension "ublock-origin" "uBlock0@raymondhill.net") - (extension "bitwarden-password-manager" "{446900e4-71c2-419f-a6a7-df9c091e268b}") - ]); - - DisablePocket = true; - DisableFirefoxAccounts = true; - DisableTelemetry = true; - DisableFirefoxStudies = true; - - UserMessaging = { - ExtensionRecommendations = false; - UrlbarInterventions = false; - MoreFromMozilla = false; - FirefoxLabs = false; - # If people wants to get spammed by Firefox… They can. - Locked = false; - }; - }; - - # Let the user override the default. - preferencesStatus = "default"; - }; - }; -} From 4938e915f5d22c4546ee68df9446d9f7ba193aba Mon Sep 17 00:00:00 2001 From: risk-alt Date: Tue, 11 Aug 2026 23:33:56 +0200 Subject: [PATCH 2/9] modules/tools/browsers: fix module evaluation The module as introduced did not evaluate: - homepage-dashboard.nix read securix.homepage-dashboard.{enable,bookmarks} without declaring either option; - lockFlags was typed as an enum although it holds a list of flags; - nullOr was applied to two arguments instead of to (submodule proxyConfig); - firefox.nix and chromium.nix consumed a homepage option neither declared; - chromium.nix dereferenced cfg.proxy unconditionally although it defaults to null, and read autoConfigUrl where option-types.nix declares autoConfigURL; - the per-browser extensions were typed loosely enough that each browser could receive the other one's shape. Type the extensions submodule so that each browser gets what it expects. The Chromium proxy policy also used mkIf inside programs.chromium.extraOpts. That option is a plain attribute set, so the module system never discharges the property, and {_type = "if"; ...} would be serialised as-is into the policy JSON. Build the conditional parts with optionalAttrs instead. Signed-off-by: risk-alt --- modules/tools/browsers/chromium.nix | 61 +++++++++++-------- modules/tools/browsers/default.nix | 28 +++++++-- modules/tools/browsers/firefox.nix | 28 +++++---- modules/tools/browsers/homepage-dashboard.nix | 29 +++++---- 4 files changed, 95 insertions(+), 51 deletions(-) diff --git a/modules/tools/browsers/chromium.nix b/modules/tools/browsers/chromium.nix index 5cb4b6b0..51c5a40b 100644 --- a/modules/tools/browsers/chromium.nix +++ b/modules/tools/browsers/chromium.nix @@ -16,6 +16,7 @@ let mkIf mapAttrsToList concatStringsSep + optionalAttrs ; inherit (lib.types) attrsOf @@ -28,6 +29,25 @@ let inherit (import ./option-types.nix { inherit lib; }) lockFlagEnum bookmarkType proxyConfig; cfg = config.securix.chromium; + + # `extraOpts` is a plain attribute set: a nested `mkIf` is not discharged and + # would land verbatim in the policy JSON. + proxySettings = optionalAttrs (cfg.proxy != null) { + ProxySettings = { + ProxyMode = + if cfg.proxy.httpProxy != null then + "fixed_servers" + else if cfg.proxy.autoConfigURL != null then + "pac_script" + else + "auto_detect"; + ProxyBypassList = concatStringsSep "," cfg.proxy.noProxy; + # TODO: expose an option called `cfg.proxy.autoConfigFailSafe` + ProxyPacMandatory = false; + } + // optionalAttrs (cfg.proxy.autoConfigURL != null) { ProxyPacUrl = cfg.proxy.autoConfigURL; } + // optionalAttrs (cfg.proxy.httpProxy != null) { ProxyServer = cfg.proxy.httpProxy; }; + }; in { options.securix.chromium = { @@ -39,7 +59,7 @@ in ''; lockFlags = mkOption { - type = enum lockFlagEnum; + type = listOf (enum lockFlagEnum); default = [ "allow-default-overrides" @@ -47,22 +67,30 @@ in ]; description = '' - The lock flags determine how locked down the Firefox configuration is. + The lock flags determine how locked down the Chromium configuration is. By default, we do not let the user install any extension, but we still - let them modify the Firefox defaults. + let them modify the Chromium defaults. ''; }; proxy = mkOption { - type = nullOr submodule proxyConfig; + type = nullOr (submodule proxyConfig); default = null; description = '' - Proxy configuration for this instance of Firefox. + Proxy configuration for this instance of Chromium. By default, it configures nothing. ''; }; + homepage = mkOption { + type = nullOr str; + default = null; + description = '' + URL of the home page, or `null` to leave the browser default alone. + ''; + }; + extensions = mkOption { type = listOf str; default = [ ]; @@ -236,29 +264,11 @@ in # Do not let WebAuthn store credentials on broken TLS certificates. AllowWebAuthnWithBrokenTlsCerts = false; - # HTTP proxy - ProxySettings = { - ProxyMode = - if cfg.proxy.httpProxy != null then - "fixed_servers" - else if cfg.proxy.autoConfigUrl != null then - "pac_script" - else - "auto_detect"; - ProxyBypassList = concatStringsSep "," cfg.proxy.noProxy; - # TODO: expose an option called `cfg.proxy.autoConfigFailSafe` - ProxyPacMandatory = false; - ProxyPacUrl = mkIf (cfg.proxy.autoConfigUrl != null) cfg.proxy.autoConfigUrl; - ProxyServer = mkIf (cfg.proxy.httpProxy != null) cfg.proxy.httpProxy; - }; - # Pre-installed bookmarks ManagedBookmarks = let mkItems = mapAttrsToList ( - name: - { href, ... }: - { + name: { href, ... }: { inherit name; url = href; } @@ -269,7 +279,8 @@ in }; in mapAttrsToList mkChildren cfg.bookmarks; - }; + } + // proxySettings; inherit (cfg) extensions; }; diff --git a/modules/tools/browsers/default.nix b/modules/tools/browsers/default.nix index e0b0509d..8871a445 100644 --- a/modules/tools/browsers/default.nix +++ b/modules/tools/browsers/default.nix @@ -56,9 +56,29 @@ in }; extensions = mkOption { - type = types.attrsOf (types.either types.attrs types.list); + type = types.submodule { + options = { + firefox = mkOption { + type = types.attrsOf types.str; + default = { }; + description = '' + Firefox extensions, keyed by their short ID in the Mozilla store, + valued by their UUID. + ''; + }; + + chromium = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + Chromium extension IDs to install from the Chrome web store. + ''; + }; + }; + }; + default = { }; description = '' - Per-browser attribute set of list of extensions to install in each instance. + Per-browser set of extensions to install in each instance. ''; }; @@ -118,7 +138,7 @@ in securix.firefox = { enable = lib.elem "firefox" cfg.browsers; inherit (cfg) enableEncryptedMediaExtensions; - extensions = cfg.extensions.firefox or [ ]; + extensions = cfg.extensions.firefox; inherit (cfg) bookmarks lockFlags; inherit homepage; }; @@ -126,7 +146,7 @@ in securix.chromium = { enable = lib.elem "chromium" cfg.browsers; inherit (cfg) enableEncryptedMediaExtensions; - extensions = cfg.extensions.chromium or { }; + extensions = cfg.extensions.chromium; inherit (cfg) bookmarks lockFlags; inherit homepage; }; diff --git a/modules/tools/browsers/firefox.nix b/modules/tools/browsers/firefox.nix index 731dd70d..de7dc8a9 100644 --- a/modules/tools/browsers/firefox.nix +++ b/modules/tools/browsers/firefox.nix @@ -14,6 +14,7 @@ let inherit (lib.types) attrsOf enum + listOf submodule str nullOr @@ -32,7 +33,7 @@ in ''; lockFlags = mkOption { - type = enum lockFlagEnum; + type = listOf (enum lockFlagEnum); default = [ "allow-default-overrides" "allow-user-messaging-overrides" @@ -47,7 +48,7 @@ in }; proxy = mkOption { - type = nullOr submodule proxyConfig; + type = nullOr (submodule proxyConfig); default = null; description = '' Proxy configuration for this instance of Firefox. @@ -55,6 +56,14 @@ in ''; }; + homepage = mkOption { + type = nullOr str; + default = null; + description = '' + URL of the home page, or `null` to leave the browser default alone. + ''; + }; + extensions = mkOption { type = attrsOf str; default = { }; @@ -104,7 +113,7 @@ in ]; policies = { - Homepage = { + Homepage = mkIf (cfg.homepage != null) { URL = cfg.homepage; # By default, the user is not allowed to update the homepage. # This can be bypassed if the lock flag contains an allow element. @@ -116,14 +125,11 @@ in Bookmarks = lib.flatten ( map ( folder: - map ( - { name, value }: - { - Title = name; - URL = value.href; - Folder = folder.name; - } - ) (lib.attrsToList folder.value) + map ({ name, value }: { + Title = name; + URL = value.href; + Folder = folder.name; + }) (lib.attrsToList folder.value) ) (lib.attrsToList cfg.bookmarks) ); diff --git a/modules/tools/browsers/homepage-dashboard.nix b/modules/tools/browsers/homepage-dashboard.nix index 9264b088..08203077 100644 --- a/modules/tools/browsers/homepage-dashboard.nix +++ b/modules/tools/browsers/homepage-dashboard.nix @@ -1,5 +1,6 @@ # SPDX-FileCopyrightText: 2025 Ryan Lahfa # SPDX-FileContributor: Elias Coppens +# SPDX-FileContributor: 2026 risk-alt # # SPDX-License-Identifier: MIT @@ -7,24 +8,30 @@ let cfg = config.securix.homepage-dashboard; inherit (lib) mkIf mkOption mkEnableOption; + inherit (lib.types) attrsOf submodule; + inherit (import ./option-types.nix { inherit lib; }) bookmarkType; in { + options.securix.homepage-dashboard = { + enable = mkEnableOption "the local homepage dashboard"; + + bookmarks = mkOption { + type = attrsOf (attrsOf (submodule bookmarkType)); + default = { }; + description = '' + Folders of bookmarks to display on the local homepage. + ''; + }; + }; + config = mkIf cfg.enable { # This spawns the dashboard on 127.0.0.1:8082. services.homepage-dashboard = { enable = true; - bookmarks = map ( - { name, value }: - { - ${name} = map ( - { name, value }: - { - ${name} = [ value ]; - } - ) (lib.attrsToList value); - } - ) (lib.attrsToList cfg.bookmarks); + bookmarks = map ({ name, value }: { + ${name} = map ({ name, value }: { ${name} = [ value ]; }) (lib.attrsToList value); + }) (lib.attrsToList cfg.bookmarks); }; }; } From bfa8804979e106294a3bad4cc0309050db766f52 Mon Sep 17 00:00:00 2001 From: risk-alt Date: Tue, 11 Aug 2026 23:41:41 +0200 Subject: [PATCH 3/9] modules/tools: keep Firefox installed by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit securix.browser.enable defaulted to false and modules/tools/default.nix only imported the module, so the browser abstraction removed Firefox from the system instead of configuring it. Enable it by default, and default the browser list to Firefox, which is what Sécurix shipped so far. The default lives on the option rather than in modules/tools/default.nix on purpose: list options concatenate on merge, so a definition there would force users to mkForce their way out of Firefox. Restore the Bitwarden extension the refactor dropped along the way, and install it on Chromium as well. programs.chromium only writes policy files, so install the package explicitly, otherwise picking Chromium configures a browser that is not there. Drop the bare firefox entry from environment.systemPackages: programs.firefox already installs the wrapped package, and shipping both puts two bin/firefox in collision inside the system profile. Enabling the module for real also surfaced one last type error: the browser-wide lockFlags were typed as listOf lockFlagEnum, where lockFlagEnum is the list of allowed flags rather than a type. Signed-off-by: risk-alt --- modules/tools/browsers/chromium.nix | 3 +++ modules/tools/browsers/default.nix | 15 +++++++++++---- modules/tools/default.nix | 5 +++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/modules/tools/browsers/chromium.nix b/modules/tools/browsers/chromium.nix index 51c5a40b..d425f50d 100644 --- a/modules/tools/browsers/chromium.nix +++ b/modules/tools/browsers/chromium.nix @@ -130,6 +130,9 @@ in }; config = mkIf cfg.enable { + # programs.chromium only writes policy files, it installs nothing. + environment.systemPackages = [ pkgs.chromium ]; + programs.chromium = { enable = true; diff --git a/modules/tools/browsers/default.nix b/modules/tools/browsers/default.nix index 8871a445..e1b3a7a8 100644 --- a/modules/tools/browsers/default.nix +++ b/modules/tools/browsers/default.nix @@ -26,7 +26,7 @@ in ''; lockFlags = mkOption { - type = types.listOf lockFlagEnum; + type = types.listOf (types.enum lockFlagEnum); description = '' The lock flags determine how locked down the browser configuration is. @@ -51,8 +51,11 @@ in "chromium" ] ); - # This is the default recommended one. - default = [ "chromium" ]; + # Sécurix shipped Firefox only, keep it as the default. + default = [ "firefox" ]; + description = '' + Browsers to install and preconfigure. + ''; }; extensions = mkOption { @@ -118,9 +121,13 @@ in # NOTE: this is a backward compatibility default. enableLocalHomepage = lib.mkDefault (lib.elem "firefox" cfg.browsers); extensions = { - firefox = lib.mkDefault { ublock-origin = "uBlock0@raymondhill.net"; }; + firefox = lib.mkDefault { + ublock-origin = "uBlock0@raymondhill.net"; + bitwarden-password-manager = "{446900e4-71c2-419f-a6a7-df9c091e268b}"; + }; chromium = lib.mkDefault [ "ddkjiahejlhfcafbddmgiahcphecmpfh" # uBlock Origin Lite + "nngceckbapebfimnlniiiahkandclblb" # Bitwarden Password Manager ]; }; diff --git a/modules/tools/default.nix b/modules/tools/default.nix index 62ce8f07..804e9649 100644 --- a/modules/tools/default.nix +++ b/modules/tools/default.nix @@ -22,6 +22,9 @@ in }; config = mkIf cfg.enable { + # Sécurix ships a preconfigured browser out of the box. + securix.browser.enable = lib.mkDefault true; + programs.mtr.enable = true; environment.systemPackages = with pkgs; [ @@ -97,8 +100,6 @@ in glibcInfo man-pages man-pages-posix - # Browser - firefox qrencode ]; }; From 02ca7f3b7a7ec22090a40819bd23b04d6e7bd67c Mon Sep 17 00:00:00 2001 From: risk-alt Date: Tue, 11 Aug 2026 23:42:23 +0200 Subject: [PATCH 4/9] modules/tools/browsers/chromium: fix policy placement programs.chromium.initialPrefs feeds /etc/chromium/initial_preferences, whose schema is the initial preferences one, not the Chrome Enterprise policy one. Every policy written there was silently ignored. Move them to extraOpts, which lands in /etc/chromium/policies/managed/extra.json. Three keys do not exist as written, checked against the policy list in components/policy/resources/templates/policies.yaml: - IsolateOrigins is typed as a string holding a comma separated list of origins; the intent here, isolating every origin, is SitePerProcess; - GenAiSettings is GenAiDefaultSettings, an int-enum where 2 means "do not allow GenAI features"; - BuiltInAIAPIIsEnabled is BuiltInAIAPIsEnabled. Wire the home page through programs.chromium.homepageLocation, which was the remaining TODO on the Chromium side, and honour the extension lock flag the way the Firefox module already does. Pass null rather than an empty list when no extension is configured, so that ExtensionInstallForcelist stays out of the generated policy file. Signed-off-by: risk-alt --- modules/tools/browsers/chromium.nix | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/modules/tools/browsers/chromium.nix b/modules/tools/browsers/chromium.nix index d425f50d..08c5f539 100644 --- a/modules/tools/browsers/chromium.nix +++ b/modules/tools/browsers/chromium.nix @@ -136,7 +136,9 @@ in programs.chromium = { enable = true; - initialPrefs = { + homepageLocation = cfg.homepage; + + extraOpts = { # Always show the bookmark bar. BookmarkBarEnabled = true; # Always let the user create more profiles. @@ -167,12 +169,11 @@ in # Allow DNS interception to determine whether # we have a proxy that knows how to deal with certain DNS. - # Allow to suggest "Try http://intranet" error messages.u + # Allow to suggest "Try http://intranet" error messages. IntranetRedirectBehavior = 3; # Home page != New tab page. HomepageIsNewTabPage = false; - HomepageLocation = ""; # Always restore previous tabs on startup. RestoreOnStartup = 1; # Show the home button. @@ -183,13 +184,18 @@ in # Some websites may require it. BlockThirdPartyCookies = true; - }; - extraOpts = { # Isolate all origins into their own process/sandbox. - IsolateOrigins = true; + # IsolateOrigins takes a list of origins, SitePerProcess is the + # blanket switch. + SitePerProcess = true; # Block any external extension to install. BlockExternalExtensions = true; + # By default, we will block any extension install, mirroring what the + # Firefox side does. Unlocking it is opt-in through the lock flags. + ExtensionInstallBlocklist = lib.optionals (!lib.elem "allow-extension-installs" cfg.lockFlags) [ + "*" + ]; # Block developer mode for extensions. ExtensionDeveloperModeSettings = 1; @@ -202,8 +208,8 @@ in PasswordManagerEnabled = false; # Forbid all generative AI from Google. - GenAiSettings = 2; - BuiltInAIAPIIsEnabled = false; + GenAiDefaultSettings = 2; # Do not allow GenAI features. + BuiltInAIAPIsEnabled = false; # Do not let the browser use Google to obtain accurate time information. BrowserNetworkTimeQueriesEnabled = false; @@ -285,7 +291,7 @@ in } // proxySettings; - inherit (cfg) extensions; + extensions = if cfg.extensions == [ ] then null else cfg.extensions; }; }; } From dc065325fe68db8e7bf4c03926214a9582675e01 Mon Sep 17 00:00:00 2001 From: risk-alt Date: Tue, 11 Aug 2026 23:47:08 +0200 Subject: [PATCH 5/9] tests/browsers: cover the browser configuration The browser modules only produce configuration, so assert on what they emit: the Firefox policy file, both Chromium managed policy files, the presence of each browser in the system profile, and the local homepage dashboard answering on its port. This is what would have caught the policies landing in initial_preferences. Signed-off-by: risk-alt --- tests/browsers.nix | 81 ++++++++++++++++++++++++++++++++++++++++++++++ tests/default.nix | 1 + 2 files changed, 82 insertions(+) create mode 100644 tests/browsers.nix diff --git a/tests/browsers.nix b/tests/browsers.nix new file mode 100644 index 00000000..a5113b55 --- /dev/null +++ b/tests/browsers.nix @@ -0,0 +1,81 @@ +# SPDX-FileCopyrightText: 2026 risk-alt +# +# SPDX-License-Identifier: MIT + +{ pkgs, libSecurix }: +let + terminal = libSecurix.mkTerminal { + name = "browsers"; + userSpecificModule = { }; + vpnProfiles = { }; + modules = [ + { + securix = { + graphical-interface.variant = "sway"; + tools.enable = true; + self = { + mainDisk = "/dev/nvme0n1"; + machine = { + hardwareSKU = "x280"; + serialNumber = "000000"; + inventoryId = 0; + }; + }; + + browser = { + browsers = [ + "firefox" + "chromium" + ]; + + bookmarks.Productivity.Github = { + href = "https://github.com"; + icon = "si-github"; + }; + }; + }; + } + ]; + }; +in +pkgs.testers.nixosTest { + name = "browsers"; + nodes = { + securix-unbranded-0 = { + imports = terminal.modules; + }; + }; + testScript = '' + import json + + securix = securix_unbranded_0 + securix.wait_for_unit("default.target") + + firefox = json.loads(securix.succeed("cat /etc/firefox/policies/policies.json"))["policies"] + assert firefox["Homepage"]["URL"] == "http://127.0.0.1:8082", firefox["Homepage"] + assert { + "Title": "Github", + "URL": "https://github.com", + "Folder": "Productivity", + } in firefox["Bookmarks"], firefox["Bookmarks"] + assert firefox["ExtensionSettings"]["*"]["installation_mode"] == "blocked" + + chromium = json.loads( + securix.succeed("cat /etc/chromium/policies/managed/extra.json") + ) + assert chromium["SitePerProcess"] is True + assert chromium["PasswordManagerEnabled"] is False + assert chromium["ExtensionInstallBlocklist"] == ["*"] + + chromium_default = json.loads( + securix.succeed("cat /etc/chromium/policies/managed/default.json") + ) + assert chromium_default["HomepageLocation"] == "http://127.0.0.1:8082" + + securix.succeed("test -x /run/current-system/sw/bin/firefox") + securix.succeed("test -x /run/current-system/sw/bin/chromium") + + securix.wait_for_unit("homepage-dashboard.service") + securix.wait_for_open_port(8082) + ''; +} diff --git a/tests/default.nix b/tests/default.nix index 3f6f74b8..2e53e9aa 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -9,4 +9,5 @@ idempotent-autoinstall = import ./idempotent-autoinstall.nix { inherit pkgs libSecurix; }; portail = import ./portail.nix { inherit pkgs libSecurix; }; tools = import ./tools.nix { inherit pkgs libSecurix; }; + browsers = import ./browsers.nix { inherit pkgs libSecurix; }; } From b780b940bab27301c1bd0060860fbfe847eda225 Mon Sep 17 00:00:00 2001 From: risk-alt Date: Tue, 11 Aug 2026 23:47:22 +0200 Subject: [PATCH 6/9] docs: reference securix.browser securix.firefox is now one browser behind the securix.browser abstraction. Signed-off-by: risk-alt --- docs/manual/src/user/quick_start.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/src/user/quick_start.md b/docs/manual/src/user/quick_start.md index af76f3ed..daaa546e 100644 --- a/docs/manual/src/user/quick_start.md +++ b/docs/manual/src/user/quick_start.md @@ -20,7 +20,7 @@ In your inventory, you can organize items into two main sections: *machines* and At this point, you can customize your NixOS system. You have two options: -* Use the modules provided by Sécurix, such as `securix.firefox` for Firefox configuration. +* Use the modules provided by Sécurix, such as `securix.browser` for browser configuration. * Alternatively, you can use standard NixOS modules for system customization. ## 4. Deploy the USB Installer From 3369b11ada34e1a62537e146037038144ca22d87 Mon Sep 17 00:00:00 2001 From: risk-alt Date: Thu, 3 Sep 2026 11:12:38 +0200 Subject: [PATCH 7/9] modules/tools/browsers/firefox: restore the Tridactyl native messaging host modules/tools/firefox.nix installed it before the browser abstraction moved the module, and the move dropped it. Without the native host, Tridactyl degrades silently: no :native commands, no editor integration. Signed-off-by: risk-alt --- modules/tools/browsers/firefox.nix | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/tools/browsers/firefox.nix b/modules/tools/browsers/firefox.nix index de7dc8a9..7192649c 100644 --- a/modules/tools/browsers/firefox.nix +++ b/modules/tools/browsers/firefox.nix @@ -3,7 +3,12 @@ # # SPDX-License-Identifier: MIT -{ config, lib, ... }: +{ + config, + lib, + pkgs, + ... +}: let inherit (lib) mkOption @@ -112,6 +117,8 @@ in "en-US" ]; + nativeMessagingHosts.packages = [ pkgs.tridactyl-native ]; + policies = { Homepage = mkIf (cfg.homepage != null) { URL = cfg.homepage; From 14f2636aa84ad03be375311cd4ddb4d558482d33 Mon Sep 17 00:00:00 2001 From: risk-alt Date: Thu, 3 Sep 2026 11:12:53 +0200 Subject: [PATCH 8/9] modules/tools/browsers/chromium: require the PAC script in PAC mode ProxyPacMandatory keeps Chromium from falling back to a direct connection when the PAC script is unavailable or invalid. Leaving it false means a fetch failure silently takes the browser around the proxy, which is the opposite of what configuring one is for. The key is only read in pac_script mode, so move it next to ProxyPacUrl. Signed-off-by: risk-alt --- modules/tools/browsers/chromium.nix | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/tools/browsers/chromium.nix b/modules/tools/browsers/chromium.nix index 08c5f539..bacc691b 100644 --- a/modules/tools/browsers/chromium.nix +++ b/modules/tools/browsers/chromium.nix @@ -42,10 +42,14 @@ let else "auto_detect"; ProxyBypassList = concatStringsSep "," cfg.proxy.noProxy; + } + // optionalAttrs (cfg.proxy.autoConfigURL != null) { + ProxyPacUrl = cfg.proxy.autoConfigURL; + # Do not fall back to a direct connection when the PAC script cannot be + # fetched. # TODO: expose an option called `cfg.proxy.autoConfigFailSafe` - ProxyPacMandatory = false; + ProxyPacMandatory = true; } - // optionalAttrs (cfg.proxy.autoConfigURL != null) { ProxyPacUrl = cfg.proxy.autoConfigURL; } // optionalAttrs (cfg.proxy.httpProxy != null) { ProxyServer = cfg.proxy.httpProxy; }; }; in From 55dfdbba9b4ce2c90b66b93fda3051b09b84a51e Mon Sep 17 00:00:00 2001 From: risk-alt Date: Thu, 3 Sep 2026 11:56:17 +0200 Subject: [PATCH 9/9] Revert "modules/tools/browsers/firefox: restore the Tridactyl native messaging host" This reverts commit 3369b1152a2e9a0bd90ecd8be76fd8f0be3d1216. Keep this pull request to the browser abstraction itself. Reverting this revert is all it takes to bring the native messaging host back. Signed-off-by: risk-alt --- modules/tools/browsers/firefox.nix | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/modules/tools/browsers/firefox.nix b/modules/tools/browsers/firefox.nix index 7192649c..de7dc8a9 100644 --- a/modules/tools/browsers/firefox.nix +++ b/modules/tools/browsers/firefox.nix @@ -3,12 +3,7 @@ # # SPDX-License-Identifier: MIT -{ - config, - lib, - pkgs, - ... -}: +{ config, lib, ... }: let inherit (lib) mkOption @@ -117,8 +112,6 @@ in "en-US" ]; - nativeMessagingHosts.packages = [ pkgs.tridactyl-native ]; - policies = { Homepage = mkIf (cfg.homepage != null) { URL = cfg.homepage;