From 9c45f8219326722a4492a540e978d753d16aacaa Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 2 Feb 2026 18:52:43 -0600 Subject: [PATCH] Add base64 input for eval command (#340) * Add base64 input for eval command Adds -b/--base64 flag to decode script from base64, avoiding shell escaping issues for AI agents. * Document base64 eval in SKILL.md --- cli/Cargo.lock | 7 ++++ cli/Cargo.toml | 1 + cli/src/commands.rs | 71 ++++++++++++++++++++++++++++++++++- cli/src/output.rs | 6 ++- skills/agent-browser/SKILL.md | 10 ++++- 5 files changed, 92 insertions(+), 3 deletions(-) diff --git a/cli/Cargo.lock b/cli/Cargo.lock index f68c880..cea2aa5 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -6,6 +6,7 @@ version = 4 name = "agent-browser" version = "0.8.7" dependencies = [ + "base64", "dirs", "libc", "serde", @@ -13,6 +14,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "2.10.0" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 7f2d788..50a47d5 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -9,6 +9,7 @@ license = "Apache-2.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" dirs = "5.0" +base64 = "0.22" [target.'cfg(unix)'.dependencies] libc = "0.2" diff --git a/cli/src/commands.rs b/cli/src/commands.rs index cb31b08..f4fa483 100644 --- a/cli/src/commands.rs +++ b/cli/src/commands.rs @@ -1,3 +1,4 @@ +use base64::{engine::general_purpose::STANDARD, Engine}; use serde_json::{json, Value}; use crate::flags::Flags; @@ -417,7 +418,28 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result Ok(json!({ "id": id, "action": "evaluate", "script": rest.join(" ") })), + "eval" => { + let (is_base64, script_parts): (bool, &[&str]) = + if rest.first() == Some(&"-b") || rest.first() == Some(&"--base64") { + (true, &rest[1..]) + } else { + (false, rest.as_slice()) + }; + let raw_script = script_parts.join(" "); + let script = if is_base64 { + let decoded = STANDARD.decode(&raw_script).map_err(|_| ParseError::InvalidValue { + message: "Invalid base64 encoding".to_string(), + usage: "eval -b ", + })?; + String::from_utf8(decoded).map_err(|_| ParseError::InvalidValue { + message: "Base64 decoded to invalid UTF-8".to_string(), + usage: "eval -b ", + })? + } else { + raw_script + }; + Ok(json!({ "id": id, "action": "evaluate", "script": script })) + } // === Close === "close" | "quit" | "exit" => Ok(json!({ "id": id, "action": "close" })), @@ -2025,6 +2047,53 @@ mod tests { )); } + // === Eval Tests === + + #[test] + fn test_eval_basic() { + let cmd = parse_command(&args("eval document.title"), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "evaluate"); + assert_eq!(cmd["script"], "document.title"); + } + + #[test] + fn test_eval_base64_short_flag() { + // "document.title" in base64 + let cmd = parse_command(&args("eval -b ZG9jdW1lbnQudGl0bGU="), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "evaluate"); + assert_eq!(cmd["script"], "document.title"); + } + + #[test] + fn test_eval_base64_long_flag() { + // "document.title" in base64 + let cmd = + parse_command(&args("eval --base64 ZG9jdW1lbnQudGl0bGU="), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "evaluate"); + assert_eq!(cmd["script"], "document.title"); + } + + #[test] + fn test_eval_base64_with_special_chars() { + // "document.querySelector('[src*=\"_next\"]')" in base64 + let cmd = parse_command( + &args("eval -b ZG9jdW1lbnQucXVlcnlTZWxlY3RvcignW3NyYyo9Il9uZXh0Il0nKQ=="), + &default_flags(), + ) + .unwrap(); + assert_eq!(cmd["action"], "evaluate"); + assert_eq!(cmd["script"], "document.querySelector('[src*=\"_next\"]')"); + } + + #[test] + fn test_eval_base64_invalid() { + let result = parse_command(&args("eval -b !!!invalid!!!"), &default_flags()); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!(matches!(err, ParseError::InvalidValue { .. })); + assert!(err.format().contains("Invalid base64")); + } + #[test] fn test_unknown_command() { let result = parse_command(&args("unknowncommand"), &default_flags()); diff --git a/cli/src/output.rs b/cli/src/output.rs index b486b59..07e035f 100644 --- a/cli/src/output.rs +++ b/cli/src/output.rs @@ -879,10 +879,13 @@ Examples: r##" agent-browser eval - Execute JavaScript -Usage: agent-browser eval