mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat(app-server): enforce managed remote control disable (#27961)
## Why Managed deployments need a reliable deny gate for remote control. Persisted enablement and explicit startup requests currently remain able to start the transport, while the removed `features.remote_control` key is intentionally only a compatibility no-op. This adds a dedicated requirement that administrators can use to force remote control off without deleting the user's persisted preference. Removing the requirement and restarting restores the prior choice. ## What Changed - Added top-level `allow_remote_control` requirements parsing, sourced layer precedence, debug output, and `configRequirements/read` exposure as `allowRemoteControl`. - Added a typed transport policy captured from the startup requirements snapshot. Managed disable forces the initial state to disabled and prevents enrollment, refresh, connection, and persisted-preference mutation. - Rejected every `remoteControl/*` RPC before parameter deserialization with JSON-RPC `-32600` and `remote control is disabled by managed requirements`. - Preserved the existing disabled status notification and the previous behavior when the requirement is `true` or omitted. - Regenerated app-server protocol schemas and documented the new requirement. ## Verification - Confirmed all remote-control RPCs, including a malformed request, return the managed-policy error while the initial status notification remains `disabled`. - Confirmed explicit ephemeral startup and persisted enablement make no backend connection and leave the SQLite preference unchanged. - Confirmed `allow_remote_control = true` does not enable or block remote control and `configRequirements/read` returns `allowRemoteControl: false` for the deny policy. Related issue: N/A (managed-policy hardening).
This commit is contained in:
committed by
GitHub
Unverified
parent
5d7db08b61
commit
b9dc3b7a8b
@@ -149,6 +149,7 @@ pub struct ConfigRequirements {
|
||||
pub web_search_mode: ConstrainedWithSource<WebSearchMode>,
|
||||
pub allow_managed_hooks_only: Option<Sourced<bool>>,
|
||||
pub allow_appshots: Option<Sourced<bool>>,
|
||||
pub allow_remote_control: Option<Sourced<bool>>,
|
||||
pub computer_use: Option<Sourced<ComputerUseRequirementsToml>>,
|
||||
pub feature_requirements: Option<Sourced<FeatureRequirementsToml>>,
|
||||
pub managed_hooks: Option<ConstrainedWithSource<ManagedHooksRequirementsToml>>,
|
||||
@@ -189,6 +190,7 @@ impl Default for ConfigRequirements {
|
||||
),
|
||||
allow_managed_hooks_only: None,
|
||||
allow_appshots: None,
|
||||
allow_remote_control: None,
|
||||
computer_use: None,
|
||||
feature_requirements: None,
|
||||
managed_hooks: None,
|
||||
@@ -828,6 +830,7 @@ pub struct ConfigRequirementsToml {
|
||||
pub allowed_web_search_modes: Option<Vec<WebSearchModeRequirement>>,
|
||||
pub allow_managed_hooks_only: Option<bool>,
|
||||
pub allow_appshots: Option<bool>,
|
||||
pub allow_remote_control: Option<bool>,
|
||||
pub computer_use: Option<ComputerUseRequirementsToml>,
|
||||
pub windows: Option<WindowsRequirementsToml>,
|
||||
#[serde(rename = "features", alias = "feature_requirements")]
|
||||
@@ -882,6 +885,7 @@ pub struct ConfigRequirementsWithSources {
|
||||
pub allowed_web_search_modes: Option<Sourced<Vec<WebSearchModeRequirement>>>,
|
||||
pub allow_managed_hooks_only: Option<Sourced<bool>>,
|
||||
pub allow_appshots: Option<Sourced<bool>>,
|
||||
pub allow_remote_control: Option<Sourced<bool>>,
|
||||
pub computer_use: Option<Sourced<ComputerUseRequirementsToml>>,
|
||||
pub windows: Option<Sourced<WindowsRequirementsToml>>,
|
||||
pub feature_requirements: Option<Sourced<FeatureRequirementsToml>>,
|
||||
@@ -924,6 +928,7 @@ impl ConfigRequirementsWithSources {
|
||||
allowed_web_search_modes: _,
|
||||
allow_managed_hooks_only: _,
|
||||
allow_appshots: _,
|
||||
allow_remote_control: _,
|
||||
computer_use: _,
|
||||
windows: _,
|
||||
feature_requirements: _,
|
||||
@@ -959,6 +964,7 @@ impl ConfigRequirementsWithSources {
|
||||
allowed_web_search_modes,
|
||||
allow_managed_hooks_only,
|
||||
allow_appshots,
|
||||
allow_remote_control,
|
||||
computer_use,
|
||||
windows,
|
||||
feature_requirements,
|
||||
@@ -992,6 +998,7 @@ impl ConfigRequirementsWithSources {
|
||||
allowed_web_search_modes,
|
||||
allow_managed_hooks_only,
|
||||
allow_appshots,
|
||||
allow_remote_control,
|
||||
computer_use,
|
||||
windows,
|
||||
feature_requirements,
|
||||
@@ -1015,6 +1022,7 @@ impl ConfigRequirementsWithSources {
|
||||
allowed_web_search_modes: allowed_web_search_modes.map(|sourced| sourced.value),
|
||||
allow_managed_hooks_only: allow_managed_hooks_only.map(|sourced| sourced.value),
|
||||
allow_appshots: allow_appshots.map(|sourced| sourced.value),
|
||||
allow_remote_control: allow_remote_control.map(|sourced| sourced.value),
|
||||
computer_use: computer_use.map(|sourced| sourced.value),
|
||||
windows: windows.map(|sourced| sourced.value),
|
||||
feature_requirements: feature_requirements.map(|sourced| sourced.value),
|
||||
@@ -1104,6 +1112,7 @@ impl ConfigRequirementsToml {
|
||||
&& self.allowed_web_search_modes.is_none()
|
||||
&& self.allow_managed_hooks_only.is_none()
|
||||
&& self.allow_appshots.is_none()
|
||||
&& self.allow_remote_control.is_none()
|
||||
&& self
|
||||
.computer_use
|
||||
.as_ref()
|
||||
@@ -1156,6 +1165,7 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
allowed_web_search_modes,
|
||||
allow_managed_hooks_only,
|
||||
allow_appshots,
|
||||
allow_remote_control,
|
||||
computer_use,
|
||||
windows,
|
||||
feature_requirements,
|
||||
@@ -1434,6 +1444,7 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
web_search_mode,
|
||||
allow_managed_hooks_only,
|
||||
allow_appshots,
|
||||
allow_remote_control,
|
||||
computer_use,
|
||||
feature_requirements,
|
||||
managed_hooks,
|
||||
@@ -1525,6 +1536,7 @@ mod tests {
|
||||
allowed_web_search_modes,
|
||||
allow_managed_hooks_only,
|
||||
allow_appshots,
|
||||
allow_remote_control,
|
||||
computer_use,
|
||||
windows,
|
||||
feature_requirements,
|
||||
@@ -1555,6 +1567,8 @@ mod tests {
|
||||
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
allow_appshots: allow_appshots
|
||||
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
allow_remote_control: allow_remote_control
|
||||
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
computer_use: computer_use.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
windows: windows.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
feature_requirements: feature_requirements
|
||||
@@ -1688,6 +1702,19 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allow_remote_control_false_is_still_configured() -> Result<()> {
|
||||
let requirements: ConfigRequirementsToml = from_str(
|
||||
r#"
|
||||
allow_remote_control = false
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_eq!(requirements.allow_remote_control, Some(false));
|
||||
assert!(!requirements.is_empty());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_computer_use_requirements() -> Result<()> {
|
||||
let requirements: ConfigRequirementsToml = from_str(
|
||||
@@ -1745,6 +1772,7 @@ mod tests {
|
||||
allowed_web_search_modes: Some(allowed_web_search_modes.clone()),
|
||||
allow_managed_hooks_only: Some(true),
|
||||
allow_appshots: Some(false),
|
||||
allow_remote_control: Some(false),
|
||||
computer_use: Some(computer_use.clone()),
|
||||
windows: None,
|
||||
feature_requirements: Some(feature_requirements.clone()),
|
||||
@@ -1787,6 +1815,10 @@ mod tests {
|
||||
enforce_source.clone(),
|
||||
)),
|
||||
allow_appshots: Some(Sourced::new(/*value*/ false, enforce_source.clone(),)),
|
||||
allow_remote_control: Some(Sourced::new(
|
||||
/*value*/ false,
|
||||
enforce_source.clone(),
|
||||
)),
|
||||
computer_use: Some(Sourced::new(computer_use, enforce_source.clone())),
|
||||
windows: None,
|
||||
feature_requirements: Some(Sourced::new(
|
||||
@@ -1835,6 +1867,7 @@ mod tests {
|
||||
allowed_web_search_modes: None,
|
||||
allow_managed_hooks_only: None,
|
||||
allow_appshots: None,
|
||||
allow_remote_control: None,
|
||||
computer_use: None,
|
||||
windows: None,
|
||||
feature_requirements: None,
|
||||
@@ -1888,6 +1921,7 @@ mod tests {
|
||||
allowed_web_search_modes: None,
|
||||
allow_managed_hooks_only: None,
|
||||
allow_appshots: None,
|
||||
allow_remote_control: None,
|
||||
computer_use: None,
|
||||
windows: None,
|
||||
feature_requirements: None,
|
||||
|
||||
@@ -182,6 +182,7 @@ fn populate_merged_regular_fields_with_sources(
|
||||
allowed_web_search_modes,
|
||||
allow_managed_hooks_only,
|
||||
allow_appshots,
|
||||
allow_remote_control,
|
||||
computer_use,
|
||||
windows,
|
||||
feature_requirements,
|
||||
@@ -210,6 +211,7 @@ fn populate_merged_regular_fields_with_sources(
|
||||
set_sourced!(allowed_web_search_modes, &["allowed_web_search_modes"]);
|
||||
set_sourced!(allow_managed_hooks_only, &["allow_managed_hooks_only"]);
|
||||
set_sourced!(allow_appshots, &["allow_appshots"]);
|
||||
set_sourced!(allow_remote_control, &["allow_remote_control"]);
|
||||
set_sourced!(computer_use, &["computer_use"]);
|
||||
set_sourced!(windows, &["windows"]);
|
||||
set_sourced!(feature_requirements, &["features", "feature_requirements"]);
|
||||
|
||||
@@ -63,6 +63,7 @@ fn top_level_values_use_toml_priority() {
|
||||
allowed_approval_policies = ["on-request"]
|
||||
allowed_sandbox_modes = ["workspace-write"]
|
||||
default_permissions = ":workspace"
|
||||
allow_remote_control = true
|
||||
|
||||
[allowed_permission_profiles]
|
||||
":read-only" = true
|
||||
@@ -76,6 +77,7 @@ default_permissions = ":workspace"
|
||||
allowed_approval_policies = ["never"]
|
||||
allowed_sandbox_modes = ["read-only"]
|
||||
default_permissions = ":read-only"
|
||||
allow_remote_control = false
|
||||
|
||||
[allowed_permission_profiles]
|
||||
":danger-full-access" = false
|
||||
@@ -93,6 +95,7 @@ default_permissions = ":read-only"
|
||||
allowed_approval_policies = ["never"]
|
||||
allowed_sandbox_modes = ["read-only"]
|
||||
default_permissions = ":read-only"
|
||||
allow_remote_control = false
|
||||
|
||||
[allowed_permission_profiles]
|
||||
":danger-full-access" = false
|
||||
@@ -135,6 +138,7 @@ fn composition_strategy_applies_to_non_cloud_layers() {
|
||||
format!(
|
||||
r#"
|
||||
allowed_approval_policies = ["on-request"]
|
||||
allow_remote_control = true
|
||||
|
||||
[features]
|
||||
shared = false
|
||||
@@ -154,6 +158,7 @@ deny_read = [{low_path:?}]
|
||||
format!(
|
||||
r#"
|
||||
allowed_approval_policies = ["never"]
|
||||
allow_remote_control = false
|
||||
|
||||
[features]
|
||||
shared = true
|
||||
@@ -178,6 +183,7 @@ deny_read = [{high_path:?}]
|
||||
expected_requirements(format!(
|
||||
r#"
|
||||
allowed_approval_policies = ["never"]
|
||||
allow_remote_control = false
|
||||
|
||||
[features]
|
||||
shared = true
|
||||
@@ -198,7 +204,14 @@ deny_read = [{high_path:?}, {low_path:?}]
|
||||
);
|
||||
assert_eq!(
|
||||
composed.allowed_approval_policies,
|
||||
Some(Sourced::new(vec![AskForApproval::Never], mdm_source))
|
||||
Some(Sourced::new(
|
||||
vec![AskForApproval::Never],
|
||||
mdm_source.clone()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
composed.allow_remote_control,
|
||||
Some(Sourced::new(/*value*/ false, mdm_source))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -73,14 +73,18 @@ impl LoaderOverrides {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns overrides with host MDM disabled and managed config loaded from `managed_config_path`.
|
||||
/// Returns overrides with host MDM disabled and managed config loaded from
|
||||
/// `managed_config_path`. System requirements are loaded from a sibling
|
||||
/// `requirements.toml` fixture.
|
||||
///
|
||||
/// This is intended for tests that supply an explicit managed config fixture.
|
||||
pub fn with_managed_config_path_for_tests(managed_config_path: PathBuf) -> Self {
|
||||
let system_requirements_path = managed_config_path.with_file_name("requirements.toml");
|
||||
Self {
|
||||
user_config_path: None,
|
||||
user_config_profile: None,
|
||||
managed_config_path: Some(managed_config_path),
|
||||
system_requirements_path: Some(system_requirements_path),
|
||||
..Self::without_managed_config_for_tests()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user