1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-11-08 11:36:05 +01:00

firefox: new permission checks for extensions

Add two new options to customize how extension permissions are checked:

- `extensions.exhaustivePermissions`
  Ensures that the permissions requested by all extensions managed by
  home-manager are authorized
- `extensions.exactPermissions`
  When enabled, the user must authorize only the permissions that the
  extensions requests, not more nor less.
This commit is contained in:
musjj 2025-09-27 21:00:42 +07:00 committed by Austin Horstman
parent b27e551270
commit c7f4214fac
4 changed files with 306 additions and 20 deletions

View file

@ -704,6 +704,35 @@ in
type = types.bool;
};
exhaustivePermissions = mkOption {
description = ''
When enabled, the user must authorize requested
permissions for all extensions from
{option}`${moduleName}.profiles.<profile>.extensions.packages`
in
{option}`${moduleName}.profiles.<profile>.extensions.settings.<extensionID>.permissions`
'';
default = false;
example = true;
type = types.bool;
};
exactPermissions = mkOption {
description = ''
When enabled,
{option}`${moduleName}.profiles.<profile>.extensions.settings.<extensionID>.permissions`
must specify the exact set of permissions that the
extension will request.
This means that if the authorized permissions are
broader than what the extension requests, the
assertion will fail.
'';
default = false;
example = true;
type = types.bool;
};
settings = mkOption {
default = { };
example = literalExpression ''
@ -800,12 +829,63 @@ in
'';
}
]
++ (builtins.concatMap (
{ addonId, meta, ... }:
let
permissions = config.extensions.settings.${addonId}.permissions or null;
requireCheck = config.extensions.exhaustivePermissions || permissions != null;
authorizedPermissions = lib.optionals (permissions != null) permissions;
missingPermissions = lib.subtractLists authorizedPermissions meta.mozPermissions;
redundantPermissions = lib.subtractLists meta.mozPermissions authorizedPermissions;
checkSatisfied =
if config.extensions.exactPermissions then
missingPermissions == [ ] && redundantPermissions == [ ]
else
missingPermissions == [ ];
errorMessage =
if
config.extensions.exactPermissions && missingPermissions != [ ] && redundantPermissions != [ ]
then
''
Extension ${addonId} requests permissions that weren't
authorized: ${builtins.toJSON missingPermissions}.
Additionally, the following permissions were authorized,
but extension ${addonId} did not request them:
${builtins.toJSON redundantPermissions}.
Consider adjusting the permissions in''
else if config.extensions.exactPermissions && redundantPermissions != [ ] then
''
The following permissions were authorized, but extension
${addonId} did not request them: ${builtins.toJSON redundantPermissions}.
Consider removing the redundant permissions from''
else
''
Extension ${addonId} requests permissions that weren't
authorized: ${builtins.toJSON missingPermissions}.
Consider adding the missing permissions to'';
in
[
{
assertion = !requireCheck || checkSatisfied;
message = ''
${errorMessage}
'${
lib.showAttrPath (
profilePath
++ [
"extensions"
addonId
]
)
}.permissions'.
'';
}
]
) config.extensions.packages)
++ (builtins.concatMap (
{ name, value }:
let
packages = builtins.filter (pkg: pkg.addonId == name) config.extensions.packages;
package = builtins.head packages;
unauthorized = lib.subtractLists value.permissions package.meta.mozPermissions;
in
[
{
@ -815,24 +895,6 @@ in
in '${lib.showOption profilePath}.extensions.packages' but found ${toString (length packages)}.
'';
}
{
assertion = value.permissions == null || length packages != 1 || unauthorized == [ ];
message = ''
Extension ${name} requests permissions that weren't
authorized: ${builtins.toJSON unauthorized}.
Consider adding the missing permissions to
'${
lib.showAttrPath (
profilePath
++ [
"extensions"
name
]
)
}.permissions'.
'';
}
]
) (lib.attrsToList config.extensions.settings))
++ config.bookmarks.assertions;

View file

@ -20,6 +20,8 @@ builtins.mapAttrs
"${name}-profiles-duplicate-ids" = ./profiles/duplicate-ids.nix;
"${name}-profiles-extensions" = ./profiles/extensions;
"${name}-profiles-extensions-assertions" = ./profiles/extensions/assertions.nix;
"${name}-profiles-extensions-exhaustive" = ./profiles/extensions/exhaustive.nix;
"${name}-profiles-extensions-exact" = ./profiles/extensions/exact.nix;
"${name}-profiles-overwrite" = ./profiles/overwrite;
"${name}-profiles-search" = ./profiles/search;
"${name}-profiles-settings" = ./profiles/settings;

View file

@ -0,0 +1,130 @@
modulePath:
{ config, lib, ... }:
let
firefoxMockOverlay = import ../../setup-firefox-mock-overlay.nix modulePath;
uBlockStubPkg = config.lib.test.mkStubPackage {
name = "ublock-origin-dummy";
extraAttrs = {
addonId = "uBlock0@raymondhill.net";
meta.mozPermissions = [
"privacy"
"storage"
"tabs"
];
};
};
sponsorBlockStubPkg = config.lib.test.mkStubPackage {
name = "sponsorblock";
extraAttrs = {
addonId = "sponsorBlocker@ajay.app";
meta.mozPermissions = [
"storage"
"scripting"
"https://sponsor.ajay.app/*"
];
};
};
noscriptStubPkg = config.lib.test.mkStubPackage {
name = "noscript";
extraAttrs = {
addonId = "{73a6fe31-595d-460b-a920-fcc0f8843232}";
meta.mozPermissions = [
"contextMenus"
"storage"
"tabs"
];
};
};
bitwardenStubPkg = config.lib.test.mkStubPackage {
name = "bitwarden";
extraAttrs = {
addonId = "{446900e4-71c2-419f-a6a7-df9c091e268b}";
meta.mozPermissions = [
"webNavigation"
"webRequest"
"webRequestBlocking"
];
};
};
in
{
imports = [ firefoxMockOverlay ];
config = lib.mkIf config.test.enableBig (
lib.setAttrByPath modulePath {
enable = true;
profiles.extensions = {
extensions = {
packages = [
uBlockStubPkg
sponsorBlockStubPkg
noscriptStubPkg
bitwardenStubPkg
];
exactPermissions = true;
settings = {
${uBlockStubPkg.addonId} = {
permissions = [
"privacy"
"storage"
];
};
${sponsorBlockStubPkg.addonId} = {
permissions = [
"storage"
"scripting"
"https://sponsor.ajay.app/*"
"<all_urls>"
];
};
${noscriptStubPkg.addonId} = {
permissions = [
"storage"
"tabs"
"https://github.com/*"
];
};
${bitwardenStubPkg.addonId} = {
permissions = [
"webNavigation"
"webRequest"
"webRequestBlocking"
];
};
};
};
};
}
// {
test.asserts.assertions.expected = [
''
Extension ${uBlockStubPkg.addonId} requests permissions that weren't
authorized: ["tabs"].
Consider adding the missing permissions to
'${lib.showOption modulePath}.profiles.extensions.extensions."${uBlockStubPkg.addonId}".permissions'.
''
''
The following permissions were authorized, but extension
${sponsorBlockStubPkg.addonId} did not request them: ["<all_urls>"].
Consider removing the redundant permissions from
'${lib.showOption modulePath}.profiles.extensions.extensions."${sponsorBlockStubPkg.addonId}".permissions'.
''
''
Extension ${noscriptStubPkg.addonId} requests permissions that weren't
authorized: ["contextMenus"].
Additionally, the following permissions were authorized,
but extension ${noscriptStubPkg.addonId} did not request them:
["https://github.com/*"].
Consider adjusting the permissions in
'${lib.showOption modulePath}.profiles.extensions.extensions."${noscriptStubPkg.addonId}".permissions'.
''
];
}
);
}

View file

@ -0,0 +1,92 @@
modulePath:
{ config, lib, ... }:
let
firefoxMockOverlay = import ../../setup-firefox-mock-overlay.nix modulePath;
uBlockStubPkg = config.lib.test.mkStubPackage {
name = "ublock-origin-dummy";
extraAttrs = {
addonId = "uBlock0@raymondhill.net";
meta.mozPermissions = [
"<all_urls>"
"http://*/*"
"https://github.com/*"
];
};
};
sponsorBlockStubPkg = config.lib.test.mkStubPackage {
name = "sponsorblock";
extraAttrs = {
addonId = "sponsorBlocker@ajay.app";
meta.mozPermissions = [
"storage"
"scripting"
"https://sponsor.ajay.app/*"
];
};
};
noscriptStubPkg = config.lib.test.mkStubPackage {
name = "noscript";
extraAttrs = {
addonId = "{73a6fe31-595d-460b-a920-fcc0f8843232}";
meta.mozPermissions = [
"contextMenus"
"storage"
"tabs"
];
};
};
in
{
imports = [ firefoxMockOverlay ];
config = lib.mkIf config.test.enableBig (
lib.setAttrByPath modulePath {
enable = true;
profiles.extensions = {
extensions = {
packages = [
uBlockStubPkg
sponsorBlockStubPkg
noscriptStubPkg
];
exhaustivePermissions = true;
settings = {
${uBlockStubPkg.addonId} = {
permissions = [
"<all_urls>"
"http://*/*"
"https://github.com/*"
];
};
${noscriptStubPkg.addonId} = {
permissions = [
"contextMenus"
];
};
};
};
};
}
// {
test.asserts.assertions.expected = [
''
Extension ${sponsorBlockStubPkg.addonId} requests permissions that weren't
authorized: ["storage","scripting","https://sponsor.ajay.app/*"].
Consider adding the missing permissions to
'${lib.showOption modulePath}.profiles.extensions.extensions."${sponsorBlockStubPkg.addonId}".permissions'.
''
''
Extension ${noscriptStubPkg.addonId} requests permissions that weren't
authorized: ["storage","tabs"].
Consider adding the missing permissions to
'${lib.showOption modulePath}.profiles.extensions.extensions."${noscriptStubPkg.addonId}".permissions'.
''
];
}
);
}