libexpr/flake-config: collect untrusted settings into one prompt

Instead of showing untrusted configuration items one by one, collect all
of them into one batched ask with 4 options:

- apply the configuration wholesale **for now** (yes)
- apply the configuration wholesale and trust it permanently (allow
  permanently)
- selectively choose subset (no)
- rejecting the configuration wholesale (no to all)

Note that we do not offer options to reject permanently a configuration,
that's what `--no-accept-flake-config` is for.

Individual prompts gain a new option to distinguish "yes for now" and
"yes forever".

Fixes #682.

Change-Id: I2492717ac277af33ea4ac9b1a11bd7d6d1bfe0db
This commit is contained in:
isabel
2026-03-03 21:57:03 +00:00
committed by Raito Bezarius
parent b28723c5b9
commit fc649250ec
2 changed files with 164 additions and 29 deletions
@@ -0,0 +1,88 @@
---
synopsis: "Collect Flakes untrusted settings into one prompt"
cls: [2921]
issues: [fj#682]
category: "Improvements"
credits: [isabelroses, raito, horrors]
---
When working with Flakes containing untrusted settings, a prompt is shown for each setting, asking whether to vet or approve it. This looks like:
```
nix flake lock
warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
The following settings require your decision:
- allow-dirty = false
- sandbox = false
Do you want to allow configuration settings to be applied?
This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all)
```
In Flakes with a large number of settings to approve or reject, this process can become tedious as each option must be handled individually.
To address this, all untrusted settings are now consolidated into a single prompt: allowing for bulk acceptance permanently or not, rejection, or detailed review. For example:
### Scrutiny scenario
```console
nix flake lock
warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
The following settings require your decision:
- allow-dirty = false
- sandbox = false
Do you want to allow configuration settings to be applied?
This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all) n
warning: you can set 'accept-flake-config' to 'false' to automatically reject configuration options supplied by flakes
Do you want to allow setting 'allow-dirty = false'? (yes for now/Allow always/no for now) y
Do you want to allow setting 'sandbox = false'? (yes for now/Allow always/no for now) n
```
### Reject everything scenario
```console
nix flake lock
warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
The following settings require your decision:
- allow-dirty = false
- sandbox = false
Do you want to allow configuration settings to be applied?
This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all) N
Rejecting all untrusted nix.conf entries
warning: you can set 'accept-flake-config' to 'false' to automatically reject configuration options supplied by flakes
```
### Accept everything scenario
```console
nix flake lock
warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
The following settings require your decision:
- allow-dirty = false
- sandbox = false
Do you want to allow configuration settings to be applied?
This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all) y
```
### Accept everything PERMANENTLY scenario
Note that accepting everything permanently will authorize these options for any
further operations.
The file containing this trust information is usually located in
`~/.local/share/nix/trusted-settings.json` and can be edited manually to revoke
this permission until Lix provides a first-class command for this manipulation.
```console
nix flake lock
warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
The following settings require your decision:
- allow-dirty = false
- sandbox = false
Do you want to allow configuration settings to be applied?
This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all) A
```
+76 -29
View File
@@ -1,4 +1,5 @@
#include "lix/libexpr/flake/flake.hh"
#include "lix/libutil/fmt.hh"
#include "lix/libutil/logging.hh"
#include "lix/libutil/json.hh"
#include "lix/libutil/users.hh"
@@ -29,19 +30,28 @@ static void writeTrustedList(const TrustedList & trustedList)
writeFile(path, JSON(trustedList).dump());
}
static bool askForSetting(
static bool batchAskForSetting(
bool & negativeTrustOverride,
TrustedList & trustedList,
const std::string & name,
const std::string & valueS)
std::map<std::string, std::string> & untrustedSettings)
{
bool trusted = false;
printWarning("The following settings require your decision:");
for (const auto & [name, valueS] : untrustedSettings) {
// FIXME: filter ANSI escapes, newlines, \r, etc.
logger->cout("- %s = %s", name, valueS);
}
// FIXME: filter ANSI escapes, newlines, \r, etc.
auto reply = logger->ask(fmt("Do you want to allow configuration setting '%s' to be set to '" ANSI_RED "%s" ANSI_NORMAL "'?\nThis may allow the flake to gain root, see the nix.conf manual page (" ANSI_BOLD "y" ANSI_NORMAL "es/" ANSI_BOLD "n" ANSI_NORMAL "o/" ANSI_BOLD "N" ANSI_NORMAL "o to all) ", name, valueS)).value_or('n');
auto reply = logger
->ask(
fmt("Do you want to allow configuration settings to be applied?\nThis may allow the "
"flake to gain root, see the nix.conf manual page (" ANSI_BOLD "y" ANSI_NORMAL
"es for now/" ANSI_BOLD "A" ANSI_NORMAL "llow always/" ANSI_BOLD "n" ANSI_NORMAL
"o/" ANSI_BOLD "N" ANSI_NORMAL "o to all) ")
)
.value_or('n');
if (reply == 'N') {
printTaggedWarning("Rejecting all untrusted nix.conf entries");
printWarning("Rejecting all untrusted nix.conf entries");
printTaggedWarning(
"you can set '%s' to '%b' to automatically reject configuration options supplied by "
"flakes",
@@ -49,25 +59,59 @@ static bool askForSetting(
false
);
negativeTrustOverride = true;
} else {
if (std::tolower(reply) == 'y') {
trusted = true;
} else {
printTaggedWarning(
"you can set '%s' to '%b' to automatically reject configuration options supplied "
"by flakes",
"accept-flake-config",
false
);
return false;
}
if (reply == 'y' || reply == 'A') {
auto alwaysAllow = reply == 'A';
for (const auto & [name, valueS] : untrustedSettings) {
if (alwaysAllow) {
trustedList[name][valueS] = true;
}
globalConfig.set(name, valueS);
}
if (std::tolower(logger->ask(fmt("do you want to permanently (in %s) mark this value as %s? (y/N) ", trustedListPath(), trusted ? "trusted": "untrusted" )).value_or('n')) == 'y') {
trustedList[name][valueS] = trusted;
if (alwaysAllow) {
writeTrustedList(trustedList);
}
return true;
} else {
printTaggedWarning(
"you can set '%s' to '%b' to automatically reject configuration options supplied "
"by flakes",
"accept-flake-config",
false
);
}
auto didTrustedListChange = false;
for (const auto & [name, valueS] : untrustedSettings) {
auto individualReply = logger
->ask(
fmt("Do you want to allow setting '%s = %s'? (" ANSI_BOLD
"y" ANSI_NORMAL "es for now/" ANSI_BOLD "A" ANSI_NORMAL
"llow always/" ANSI_BOLD "n" ANSI_NORMAL "o for now) ",
name,
valueS)
)
.value_or('n');
if (individualReply == 'y' || individualReply == 'A') {
if (individualReply == 'A') {
trustedList[name][valueS] = true;
didTrustedListChange = true;
}
globalConfig.set(name, valueS);
}
}
return trusted;
if (didTrustedListChange) {
writeTrustedList(trustedList);
}
return false;
}
void ConfigFile::apply()
@@ -77,8 +121,11 @@ void ConfigFile::apply()
// Allows to ignore all subsequent settings from this file.
bool negativeTrustOverride = false;
for (auto & [name, value] : settings) {
std::map<std::string, std::string> untrustedSettings;
TrustedList trustedList = readTrustedList();
for (auto & [name, value] : settings) {
auto baseName = name.starts_with("extra-") ? std::string(name, 6) : name;
// FIXME: Move into libutil/config.cc.
@@ -90,11 +137,12 @@ void ConfigFile::apply()
else if (auto* b = std::get_if<Explicit<bool>>(&value))
valueS = b->t ? "true" : "false";
else if (auto ss = std::get_if<std::vector<std::string>>(&value))
valueS = concatStringsSep(" ", *ss); // FIXME: evil
valueS = concatStringsSep(" ", *ss); // FIXME: evil
else
assert(false);
bool trusted = whitelist.count(baseName);
if (!trusted) {
switch (nix::fetchSettings.acceptFlakeConfig.get()) {
case AcceptFlakeConfig::True: {
@@ -102,21 +150,16 @@ void ConfigFile::apply()
break;
}
case AcceptFlakeConfig::Ask: {
auto trustedList = readTrustedList();
auto tlname = get(trustedList, name);
if (auto saved = tlname ? get(*tlname, valueS) : nullptr) {
trusted = *saved;
printInfo("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name, valueS);
} else {
if (negativeTrustOverride) {
trusted = false;
} else {
trusted = askForSetting(negativeTrustOverride, trustedList, name, valueS);
}
untrustedSettings[name] = valueS;
}
break;
}
case nix::AcceptFlakeConfig::False: {
case AcceptFlakeConfig::False: {
trusted = false;
break;
};
@@ -135,6 +178,10 @@ void ConfigFile::apply()
);
}
}
if (!untrustedSettings.empty()) {
batchAskForSetting(negativeTrustOverride, trustedList, untrustedSettings);
}
}
}