Stricter disable_ai overrides (#35977)

Settings overrides (e.g. local project settings, server settings) can no
longer change `disable_ai` to `false` if it was `true`; they can only
change it to `true`. In other words, settings can only cause AI to be
*more* disabled, they can't undo the user's preference for no AI (or the
project's requirement not to use AI).

Release Notes:

- Settings overrides (such as local project settings) can now only
override `disable_ai` to become `true`; they can no longer cause
otherwise-disabled AI to become re-enabled.

---------

Co-authored-by: Assistant <assistant@anthropic.com>
Co-authored-by: David Kleingeld <git@davidsk.dev>
This commit is contained in:
Richard Feldman 2025-08-11 10:56:45 -04:00 committed by GitHub
parent abb64d2320
commit 6478e66e7a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -962,14 +962,19 @@ impl settings::Settings for DisableAiSettings {
type FileContent = Option<bool>; type FileContent = Option<bool>;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> { fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
Ok(Self { // For security reasons, settings can only make AI restrictions MORE strict, not less.
disable_ai: sources // (For example, if someone is working on a project that contractually
.user // requires no AI use, that should override the user's setting which
.or(sources.server) // permits AI use.)
.copied() // This also prevents an attacker from using project or server settings to enable AI when it should be disabled.
.flatten() let disable_ai = sources
.unwrap_or(sources.default.ok_or_else(Self::missing_default)?), .project
}) .iter()
.chain(sources.user.iter())
.chain(sources.server.iter())
.any(|disabled| **disabled == Some(true));
Ok(Self { disable_ai })
} }
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
@ -5508,3 +5513,153 @@ fn provide_inline_values(
variables variables
} }
#[cfg(test)]
mod disable_ai_settings_tests {
use super::*;
use gpui::TestAppContext;
use settings::{Settings, SettingsSources};
#[gpui::test]
async fn test_disable_ai_settings_security(cx: &mut TestAppContext) {
cx.update(|cx| {
// Test 1: Default is false (AI enabled)
let sources = SettingsSources {
default: &Some(false),
global: None,
extensions: None,
user: None,
release_channel: None,
operating_system: None,
profile: None,
server: None,
project: &[],
};
let settings = DisableAiSettings::load(sources, cx).unwrap();
assert_eq!(settings.disable_ai, false, "Default should allow AI");
// Test 2: Global true, local false -> still disabled (local cannot re-enable)
let global_true = Some(true);
let local_false = Some(false);
let sources = SettingsSources {
default: &Some(false),
global: None,
extensions: None,
user: Some(&global_true),
release_channel: None,
operating_system: None,
profile: None,
server: None,
project: &[&local_false],
};
let settings = DisableAiSettings::load(sources, cx).unwrap();
assert_eq!(
settings.disable_ai, true,
"Local false cannot override global true"
);
// Test 3: Global false, local true -> disabled (local can make more restrictive)
let global_false = Some(false);
let local_true = Some(true);
let sources = SettingsSources {
default: &Some(false),
global: None,
extensions: None,
user: Some(&global_false),
release_channel: None,
operating_system: None,
profile: None,
server: None,
project: &[&local_true],
};
let settings = DisableAiSettings::load(sources, cx).unwrap();
assert_eq!(
settings.disable_ai, true,
"Local true can override global false"
);
// Test 4: Server can only make more restrictive (set to true)
let user_false = Some(false);
let server_true = Some(true);
let sources = SettingsSources {
default: &Some(false),
global: None,
extensions: None,
user: Some(&user_false),
release_channel: None,
operating_system: None,
profile: None,
server: Some(&server_true),
project: &[],
};
let settings = DisableAiSettings::load(sources, cx).unwrap();
assert_eq!(
settings.disable_ai, true,
"Server can set to true even if user is false"
);
// Test 5: Server false cannot override user true
let user_true = Some(true);
let server_false = Some(false);
let sources = SettingsSources {
default: &Some(false),
global: None,
extensions: None,
user: Some(&user_true),
release_channel: None,
operating_system: None,
profile: None,
server: Some(&server_false),
project: &[],
};
let settings = DisableAiSettings::load(sources, cx).unwrap();
assert_eq!(
settings.disable_ai, true,
"Server false cannot override user true"
);
// Test 6: Multiple local settings, any true disables AI
let global_false = Some(false);
let local_false3 = Some(false);
let local_true2 = Some(true);
let local_false4 = Some(false);
let sources = SettingsSources {
default: &Some(false),
global: None,
extensions: None,
user: Some(&global_false),
release_channel: None,
operating_system: None,
profile: None,
server: None,
project: &[&local_false3, &local_true2, &local_false4],
};
let settings = DisableAiSettings::load(sources, cx).unwrap();
assert_eq!(
settings.disable_ai, true,
"Any local true should disable AI"
);
// Test 7: All three sources can independently disable AI
let user_false2 = Some(false);
let server_false2 = Some(false);
let local_true3 = Some(true);
let sources = SettingsSources {
default: &Some(false),
global: None,
extensions: None,
user: Some(&user_false2),
release_channel: None,
operating_system: None,
profile: None,
server: Some(&server_false2),
project: &[&local_true3],
};
let settings = DisableAiSettings::load(sources, cx).unwrap();
assert_eq!(
settings.disable_ai, true,
"Local can disable even if user and server are false"
);
});
}
}