cli: refuse non-object snapshot params instead of panicking

A script line for `ui.screenshot` without a `path` had its generated path
assigned with `params["path"] = …`: `serde_json`'s mutable index panics on
an array, a string or a number, and the CLI installs no `catch_unwind`, so
a script handed to the documented headless workflow aborted the process.
Only an object (or null, like an absent `params`) takes the path now;
anything else is a script error naming the line, like the existing handling
of a malformed line.

The regression test runs the real binary on such a script and asserts a
non-zero exit, no "panicked" in stderr and the `params` must be a JSON
object message (it fails before the fix with `cannot access key "path" in
JSON array`).
This commit is contained in:
midasmatterhorn
2026-10-14 23:12:32 +03:00
parent 3b4279fc24
commit b0241748ee
2 changed files with 29 additions and 1 deletions
+11 -1
View File
@@ -832,7 +832,17 @@ fn snapshot(args: &[String]) -> Result<(), String> {
match next_path(&mut shots) {
Some(p) => {
wrote_output = true;
params["path"] = json!(p);
// `params` is untrusted script JSON: `params["path"] = …` panics on
// an array or a string. Only an object (or null, like an absent
// `params`) may take the path.
if params.is_null() {
params = json!({});
}
if let Some(o) = params.as_object_mut() {
o.insert("path".into(), json!(p));
} else {
return Err(format!("{script}:{}: ui.screenshot: `params` must be a JSON object", n + 1));
}
}
None => return Err(format!("{script}:{}: ui.screenshot needs a `path` (or give -o)", n + 1)),
}
+18
View File
@@ -273,6 +273,24 @@ fn snapshot_script_failure_exits_non_zero() {
assert_eq!((d.width, d.height), (320, 200));
}
/// A script line whose `params` is not an object must be an error, never a panic: the
/// `ui.screenshot`-without-a-path branch used to index (`params["path"] = …`) an array and abort
/// the whole script.
#[test]
fn snapshot_script_with_non_object_params_is_an_error() {
let script = tmp("snap-bad-params.jsonl");
let out = tmp("snap-bad-params.png");
std::fs::write(&script, format!("{}\n", json!({"method": "ui.screenshot", "params": [1]}))).unwrap();
let o = Command::new(BIN)
.args(["snapshot", "--demo", "--script", script.to_str().unwrap(), "-o", out.to_str().unwrap(), "--size", "320x200"])
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&o.stderr);
assert!(!o.status.success(), "{stderr}");
assert!(!stderr.contains("panicked"), "a bad `params` must be an error, not a panic: {stderr}");
assert!(stderr.contains("`params` must be a JSON object"), "{stderr}");
}
#[test]
fn snapshot_ui_zoom_keeps_requested_pixel_dimensions() {
let script = tmp("snap-ui-zoom.jsonl");