Skip to content

modules/tools/browsers: finish the browser abstraction - #239

Open
RISK-alt wants to merge 9 commits into
cloud-gouv:mainfrom
RISK-alt:browsers
Open

RISK-alt wants to merge 9 commits into
cloud-gouv:mainfrom
RISK-alt:browsers

Conversation

@RISK-alt

Copy link
Copy Markdown
Contributor

This picks up #71, which was said not to be moving any time soon, and finishes
it. The first commit is Ryan's original, rebased on main with his authorship and
sign-off intact; everything after it is mine.

The module did not evaluate. Seven distinct issues, fixed in one commit and
listed in its message. The one worth calling out: programs.chromium.extraOpts
is a types.attrs, whose merge is a shallow //, so the module system never
discharges a nested mkIf. The two in ProxySettings would have been
serialised into the policy JSON as {"_type":"if",...}. They are built with
optionalAttrs now.

The abstraction removed Firefox from the system. securix.browser.enable
defaulted to false and nothing enabled it. The browser list now defaults to
Firefox, which is what Sécurix shipped so far, and the default lives on the
option rather than in modules/tools/default.nix because list options
concatenate on merge — a definition there would force users to mkForce their
way out. Chromium stays one option away. The Tridactyl native messaging host and
the Bitwarden extension, both dropped by the refactor, are back.

Chromium was never installed. programs.chromium only writes policy files,
so picking Chromium configured a browser that was not there.

The Chromium policies did not apply. They were written to initialPrefs,
which feeds /etc/chromium/initial_preferences and follows the initial
preferences schema, not the enterprise policy one. Moved to extraOpts. Three
policy names do not exist as written, checked against
components/policy/resources/templates/policies.yaml:

  • IsolateOrigins is typed as a string of comma separated origins; the intent
    here is SitePerProcess;
  • GenAiSettings is GenAiDefaultSettings, an int-enum where 2 means "do not
    allow GenAI features";
  • BuiltInAIAPIIsEnabled is BuiltInAIAPIsEnabled.

I only touched placement and invalid names, not the values that were chosen.

A test covers it. tests/browsers builds a terminal with both browsers and
asserts on the Firefox policy file, both Chromium managed policy files, the
presence of each browser in the system profile, and the local dashboard
answering on its port. It would have caught the initialPrefs bug.

One behaviour note for reviewers: securix.firefox.bookmarks still works, but
it no longer feeds the local dashboard — securix.browser.bookmarks does. I did
not add a compatibility shim; say the word if you want one.

This is the groundwork for #208, which I will send as a follow-up on top of this
branch.

@RISK-alt

Copy link
Copy Markdown
Contributor Author

For this PR, as usual, I used AI to help me put the problem into context, after which I drafted a plan/implementation strategy to help me think through the solution. I then provided clear instructions so that it could carry out what I wanted to do more quickly. After that, I checked the code for completeness (hence the “temp”). Finally, I made a few corrections.

Comment thread modules/tools/browsers/chromium.nix Outdated

cfg = config.securix.chromium;

# `programs.chromium.extraOpts` is a plain attribute set: the module system

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid Claude-style comments in the code? This reads very unnatural to me.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewritten in the commit that introduces the binding:

# `extraOpts` is a plain attribute set: a nested `mkIf` is not discharged and
# would land verbatim in the policy JSON.

Kept to two lines because that constraint is the reason proxySettings exists as a separate binding instead of an inline mkIf.

Comment thread modules/tools/browsers/chromium.nix Outdated
Comment thread modules/tools/browsers/chromium.nix Outdated
"auto_detect";
ProxyBypassList = concatStringsSep "," cfg.proxy.noProxy;
# TODO: expose an option called `cfg.proxy.autoConfigFailSafe`
ProxyPacMandatory = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are in PAC mode, shouldn't the ProxyPacMandatory be true?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It is true now, and since the key is only read in pac_script mode I moved it next to ProxyPacUrl, in the PAC-conditional block:

// 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 = true;
}

Your TODO stays: that option is how one would opt back into failing open. Done as its own commit since it changes behaviour rather than fixing the port.

Comment thread modules/tools/browsers/chromium.nix Outdated
Comment thread modules/tools/browsers/default.nix Outdated
Comment thread modules/tools/browsers/default.nix Outdated
Comment thread modules/tools/browsers/default.nix
Comment thread modules/tools/browsers/firefox.nix Outdated
Comment thread modules/tools/browsers/firefox.nix Outdated
Comment thread modules/tools/browsers/firefox.nix Outdated

@rlahfa-dinum rlahfa-dinum left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a first pass, a couple of items, mostly cosmetic and some are important (LLM style comments are not helpful).

@rlahfa-dinum rlahfa-dinum added status: awaiting-author Blocked on author's actions and removed status: awaiting-maintainers This is blocked on a maintainer's review bandwidth labels Sep 2, 2026
rlahfa-dinum and others added 4 commits September 3, 2026 11:11
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 <ryan.lahfa.ext@numerique.gouv.fr>
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 <aldu6974@gmail.com>
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 <aldu6974@gmail.com>
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 <aldu6974@gmail.com>
@RISK-alt

RISK-alt commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Force-pushed. Rebased on main, and the review items are folded into the commits that introduced the code rather than appended as fixups, plus two new commits at the end (Tridactyl, PAC).

One thing to flag that comes from the rebase rather than the review: #221 landed securix.tools.enable, disabled by default. securix.browser.enable = lib.mkDefault true therefore now sits inside that mkIf, so no browser is configured unless tools are enabled. Hoisting it out of the gate would put a browser back on every install, which is the opposite of what #221 is for, so I kept it inside and tests/browsers.nix sets tools.enable = true. Tell me if you would rather have it the other way.

The tests failure on the previous run was the binary cache post-build-hook on a fork PR (AWS error fetching 'nix-cache-info'), which #236 fixed after that run. Nothing is evaluated on my side, so this CI run is the first real check.

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 <aldu6974@gmail.com>
securix.firefox is now one browser behind the securix.browser abstraction.

Signed-off-by: risk-alt <aldu6974@gmail.com>
…g 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 <aldu6974@gmail.com>
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 <aldu6974@gmail.com>

@rlahfa-dinum rlahfa-dinum left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All that is left is the tridactyl revert commit, I believe and we are good to go.

…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 <aldu6974@gmail.com>
@RISK-alt

RISK-alt commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Pushed as a fast-forward, the eight commits you reviewed are untouched and 55dfdbb is appended on top. #240 rebased onto it.

@rlahfa-dinum rlahfa-dinum added status: ready-to-merge Approved + CI green (or accepted breakage), maintainer will merge once they have a time and removed status: awaiting-author Blocked on author's actions labels Sep 3, 2026
@rlahfa-dinum rlahfa-dinum added the status: awaiting smoke testing Awaiting basic smoke testing on the Sécurix team side. label Sep 9, 2026

@ldesgouilles-dinum ldesgouilles-dinum left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did preliminary integration in our Bureautix repo and it looks good to me, thank you for the work!
Just a note, on a fresh start with no previous state, Chromium decides to show me about://new-tab-page instead of the homepage, but the homepage button properly brings me to localhost:8082. I don't know why is that but I don't think we should block this PR because of this.

@rlahfa-dinum rlahfa-dinum removed the status: awaiting smoke testing Awaiting basic smoke testing on the Sécurix team side. label Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A/modules Usecases abstractions: high-level NixOS modules for a need status: ready-to-merge Approved + CI green (or accepted breakage), maintainer will merge once they have a time

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants