From b51d588cac747db7ba0cc37953a6b01fbf7f1eaf Mon Sep 17 00:00:00 2001 From: prxnvt <53838599+prxnvt@users.noreply.github.com> Date: Wed, 21 Oct 2026 15:26:30 -0400 Subject: [PATCH] Engine: macros can't open, hide or show windows The policy denies window.* commands, but the View tab's window commands are view.*: a macro could run view.newWindow, which adds a window that the run's rollback doesn't remove, or view.hideWindow and view.unhideWindow, which change the session's hidden windows. The policy now denies those three. Arrange All changes nothing in the engine and Switch Windows only lists windows, so both stay allowed. --- crates/engine/src/macros/policy.rs | 12 ++++++++++-- crates/engine/src/tests/macros.rs | 2 ++ docs/macros.md | 4 ++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/macros/policy.rs b/crates/engine/src/macros/policy.rs index 84bbc497b..8f23a5b40 100644 --- a/crates/engine/src/macros/policy.rs +++ b/crates/engine/src/macros/policy.rs @@ -30,8 +30,13 @@ const DENIED_PREFIXES: &[(&str, &str)] = &[ ]; /// Single commands that never run in a macro. -const DENIED: &[(&str, &str)] = - &[("edit.undo", "a macro is undone as one step after it runs"), ("edit.redo", "a macro is undone as one step after it runs")]; +const DENIED: &[(&str, &str)] = &[ + ("edit.undo", "a macro is undone as one step after it runs"), + ("edit.redo", "a macro is undone as one step after it runs"), + ("view.newWindow", "macros don't open, hide or show windows"), + ("view.hideWindow", "macros don't open, hide or show windows"), + ("view.unhideWindow", "macros don't open, hide or show windows"), +]; /// Commands under a denied prefix that only change the workbook. const ALLOWED: &[&str] = &["file.properties"]; @@ -97,6 +102,9 @@ mod tests { "macro.record", "edit.undo", "edit.redo", + "view.newWindow", + "view.hideWindow", + "view.unhideWindow", ] { assert!(matches!(macro_policy(id, &none, Trust::Local), Policy::Deny(_)), "{id}"); assert!(matches!(macro_policy(id, &path, Trust::FromFile), Policy::Deny(_)), "{id}"); diff --git a/crates/engine/src/tests/macros.rs b/crates/engine/src/tests/macros.rs index 2855f11e6..a928e9b53 100644 --- a/crates/engine/src/tests/macros.rs +++ b/crates/engine/src/tests/macros.rs @@ -419,6 +419,8 @@ fn the_policy_keeps_macros_away_from_files_and_the_app() { ("automate.runScript", json!({"name": "Script 1"})), ("edit.undo", json!({})), ("macro.run", json!({"name": "Self"})), + ("view.newWindow", json!({})), + ("view.hideWindow", json!({})), ] { put( &mut s, diff --git a/docs/macros.md b/docs/macros.md index 1e1b873fb..cf93148d3 100644 --- a/docs/macros.md +++ b/docs/macros.md @@ -75,8 +75,8 @@ called Output, even when a token or input has that name. `macro.run {name, inputs?, onConflict?, overwrite?, approve?, timeLimit?}`: -1. **Policy.** File, app, window, automate and macro commands, and Undo and Redo, never run in a - macro. Reading or writing a file (`data.getData`, `data.fromTextCsv`, `insert.picture` or +1. **Policy.** File, app, window (including New Window, Hide and Unhide), automate and macro + commands, and Undo and Redo, never run in a macro. Reading or writing a file (`data.getData`, `data.fromTextCsv`, `insert.picture` or `pageLayout.background` with a `path`, or an export or print with a `path`) needs the command in the macro's `permissions`. Any `path` other than `null` counts, including a blank one and a token, and the policy is checked again on the parameters as they are when the step runs. A macro that touches files, or that came with a workbook opened