fix(examples/chrome-applescript): use JSON.stringify for AppleScript string interpolation#231
Conversation
…string interpolation
There was a problem hiding this comment.
Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit at claude.ai/admin-settings/claude-code.
Once credits are available, reopen this pull request to trigger a review.
tobinsouth
left a comment
There was a problem hiding this comment.
Security review
Verdict: ✅ Safe — fix is complete and correct.
Reviewed for: JSON.stringify → AppleScript breakout potential, regression vs the deleted escapeForAppleScript, new URL() validation scope, and completeness of interpolation-site coverage.
JSON.stringify safety — AppleScript string literals terminate only on an unescaped ". JSON.stringify guarantees " → \\" and \\ → \\\\, so no raw terminator or stray escape can reach the closing quote. The non-AppleScript escapes JSON emits (\\b, \\f, \\uXXXX) and raw U+2028/U+2029 contain no " — worst case is mis-rendering or a script parse error, never breakout. Inputs are type-guarded as strings before stringify (the existing typeof url !== "string" check; wrappedCode is a template literal), so output is always the "..." form. No injection path.
Regression vs old escaper — None. The deleted helper already covered \\ and " (the only breakout-relevant characters); JSON.stringify covers those plus \\t and all C0 control chars. Strictly ≥ old coverage.
new URL() validation — Permits javascript: / file: / data:, but the prior code had no validation at all and the server already exposes execute_javascript, so no new capability is granted. Defense-in-depth only.
Completeness — escapeForAppleScript fully removed. Remaining AppleScript interpolations are: safeTabId (sanitized in #230), JSON.stringify(url) (L296-297), JSON.stringify(wrappedCode) (L656/666), the hardcoded jsCode constant (L727/737), and operation.tabScript / successMessage (all callers pass literal constants). No user-input site left uncovered.
CI green across CodeQL + full test matrix. Approving.
Summary
Follow-up to #230. Replaces
escapeForAppleScript()withJSON.stringify()at the two remaining AppleScript string-interpolation sites inexamples/chrome-applescript: theurlparam inopen_urland thecodeparam inexecute_javascript(viawrappedCode).escapeForAppleScriptonly handled backslash, double quote,\n, and\r— tab, null, and other control characters passed through unescaped.JSON.stringifyproduces an RFC 8259–compliant quoted string that AppleScript parses as a string literal, covering the gaps.Also adds
new URL(url)validation toopen_urlso malformed URLs returnisError: trueinstead of reachingosascript.With those call sites migrated,
escapeForAppleScripthas no remaining callers and is deleted.Fix
open_url:"${escapeForAppleScript(url)}"→${JSON.stringify(url)}(+new URL(url)validity check before building the script)execute_javascript:"${escapeForAppleScript(wrappedCode)}"→${JSON.stringify(wrappedCode)}at both interpolation sitesescapeForAppleScripthelpertab_idsanitization is unchanged (landed in fix(examples/chrome-applescript): sanitize tab_id to prevent AppleScript injection #230)Test plan
node --check examples/chrome-applescript/server/index.jsyarn install && yarn lintcleangrep -n escapeForAppleScript examples/chrome-applescript/server/index.js→ no matchesopen_urlwithurl: "https://example.com/a\"b"→ navigates toexample.com/a%22b; the"is percent-encoded as a literal URL character, not anosascriptescapeexecute_javascriptwith acodestring containing",\t, and\\→ characters preserved as-is in the returned valueopen_urlwithurl: "not a url"→isError: true,Invalid URL