agent: code-atomiser-fix — broaden clipboard/paste guards to documented secondary windows

The Trust-3/Trust-6 fix in commit 7aee534 added ensure_main_window to
copy_to_clipboard, paste_text, and paste_text_replacing. That was over-
broad: the transcript-viewer and transcription-preview windows
legitimately call copy_to_clipboard (their "copy raw" / "copy
transcript" buttons), and the transcription-preview window legitimately
calls paste_text_replacing (the preview paste-to-foreground flow).

The original Trust-3/Trust-6 finding was about asymmetric exposure to
arbitrary windows, not about banning the documented secondary windows.
Fix: add ensure_window_in_set helper in commands::security, mirror the
allow-list against src-tauri/capabilities/secondary-windows.json so the
IPC trust boundary and the permission grant stay in lock-step.

  copy_to_clipboard         -> main + transcript-viewer + transcription-preview
  paste_text_replacing      -> main + transcription-preview
  paste_text                -> main only (unchanged; no secondary caller)

Adds two unit tests for ensure_window_in_set_label covering the
listed-label accept path and the unlisted-label reject path. The
existing Trust-3/Trust-6 tests remain in place and continue to assert
the size cap and the constants-equality invariant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 18:09:26 +01:00
parent 9653e25e32
commit 12b413d645
3 changed files with 66 additions and 8 deletions

View File

@@ -12,9 +12,35 @@ pub fn ensure_main_window_label(label: &str) -> Result<(), String> {
}
}
/// Reject the call unless the invoking window's label is one of `allowed`.
///
/// Use this for commands that are legitimately reachable from a small set of
/// documented secondary windows (e.g. the transcript viewer or the
/// transcription preview), where `ensure_main_window` would be over-broad
/// and break in-app flows. The allowed set should always be a fixed
/// `&[&'static str]` and should mirror an entry in
/// `src-tauri/capabilities/*.json` — keeping the IPC trust boundary and the
/// permission grant in lock-step.
pub fn ensure_window_in_set(
window: &tauri::WebviewWindow,
allowed: &[&str],
) -> Result<(), String> {
ensure_window_in_set_label(window.label(), allowed)
}
pub fn ensure_window_in_set_label(label: &str, allowed: &[&str]) -> Result<(), String> {
if allowed.iter().any(|a| *a == label) {
Ok(())
} else {
Err(format!(
"This command is only available from {allowed:?} (got {label})."
))
}
}
#[cfg(test)]
mod tests {
use super::ensure_main_window_label;
use super::{ensure_main_window_label, ensure_window_in_set_label};
#[test]
fn accepts_main_window() {
@@ -27,4 +53,19 @@ mod tests {
assert!(ensure_main_window_label(label).is_err());
}
}
#[test]
fn ensure_window_in_set_accepts_listed_labels() {
let allowed = &["main", "transcript-viewer", "transcription-preview"];
for label in allowed {
assert!(ensure_window_in_set_label(label, allowed).is_ok());
}
}
#[test]
fn ensure_window_in_set_rejects_unlisted_label() {
let allowed = &["main", "transcript-viewer"];
assert!(ensure_window_in_set_label("tasks-float", allowed).is_err());
assert!(ensure_window_in_set_label("attacker-popup", allowed).is_err());
}
}