diff --git a/crates/automation/src/confine.rs b/crates/automation/src/confine.rs index e5c53af39..c73e5fbef 100644 --- a/crates/automation/src/confine.rs +++ b/crates/automation/src/confine.rs @@ -153,7 +153,7 @@ impl Confinement { // (`effectcraft_engine::prefs::Scripting::same_permissions`), and neither may a confined // agent: `prefs.set` saves the settings file, so a script run long after this session // would still have it. (`prefs.reset` only puts the defaults back, which grant nothing.) - if id == "prefs.set" && prefs_keys(params).iter().any(|k| k.to_ascii_lowercase().starts_with("scripting.")) { + if id == "prefs.set" && prefs_keys(params).iter().any(|k| is_scripting_pref(k)) { return Err(Error::BadArgs( "`prefs.set`: the Scripting settings decide what a script may do, so they can't be changed while file access is confined".into(), )); @@ -238,6 +238,13 @@ impl Confinement { } } +/// Whether a `prefs.set` key reaches the Scripting page. `Prefs::set` turns the key into a JSON +/// pointer (`key.replace('.', "/")`), so `scripting/allowScriptsWriteFiles` resolves as well as +/// the dotted spelling, and the page on its own replaces every setting on it. +fn is_scripting_pref(key: &str) -> bool { + key.split(['.', '/']).next().is_some_and(|page| page.eq_ignore_ascii_case("scripting")) +} + /// The settings `prefs.set` is given: `key`, and the keys of `values`. fn prefs_keys(params: &Value) -> Vec<&str> { let mut keys: Vec<&str> = params.get("key").and_then(Value::as_str).into_iter().collect(); diff --git a/crates/automation/src/tests.rs b/crates/automation/src/tests.rs index 61af7afff..3e06c7339 100644 --- a/crates/automation/src/tests.rs +++ b/crates/automation/src/tests.rs @@ -1421,6 +1421,11 @@ fn automation_roots_refuse_running_script_code() { json!({"key": "scripting.allow_scripts_write_files", "value": true}), json!({"values": {"scripting.warnExecutingFiles": false}}), json!({"values": {"general.showToolTips": false, "scripting.enableJsDebugger": true}}), + // `Prefs::set` makes a JSON pointer of the key, so this spelling resolves too… + json!({"key": "scripting/allowScriptsWriteFiles", "value": true}), + // …and the page on its own replaces every setting on it. + json!({"key": "scripting", "value": {"allowScriptsWriteFiles": true}}), + json!({"values": {"scripting": {"warnExecutingFiles": false}}}), ] { let msg = refused(&mut s, "execute_command", json!({"command": "prefs.set", "params": params})); assert!(msg.contains("what a script may do"), "{msg}");