Fix script corruption in executeJxa shell-escape#32
Conversation
executeJxa built the osascript command as a shell string and escaped single quotes with `'` -> `''`. That is not a valid bash escape inside a single-quoted string; `''` collapses to nothing, so every apostrophe in the JXA script was silently stripped before reaching osascript. The bug is latent today because the project's JXA strings use double quotes throughout, but any tool emitting a single-quoted JXA string literal (an `'foo'`, an apostrophe in a thrown error message, etc.) would produce a syntax error or, worse, a silently malformed script. Switch to execFile with an argv array. osascript receives the script as a single argument, no shell interpretation, no escaping needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request replaces the use of exec with execFile in src/applescript/execute.ts to improve security and handle script escaping more reliably when executing JXA scripts. Feedback was provided regarding the default maxBuffer size, suggesting an increase to 10MB to prevent potential ERR_CHILD_PROCESS_STDIO_MAXBUFFER errors when processing large amounts of metadata or document content from DEVONthink. Additionally, it was noted that for very large scripts, piping to osascript via stdin would be more robust than using the -e flag to avoid system command-line argument length limits.
| return new Promise((resolve, reject) => { | ||
| const command = `osascript -l JavaScript -e '${script.replace(/'/g, "''")}'`; | ||
| exec(command, (error, stdout, stderr) => { | ||
| execFile("osascript", ["-l", "JavaScript", "-e", script], (error, stdout, stderr) => { |
There was a problem hiding this comment.
While execFile is a significant improvement over exec for security and escaping, it still uses a default maxBuffer of 1MB. For a tool interacting with a database like DEVONthink, which can return large amounts of metadata or document content (e.g., via get_record_content), this limit might be exceeded, causing the process to fail with ERR_CHILD_PROCESS_STDIO_MAXBUFFER. Consider increasing the buffer size.\n\nAdditionally, passing the script via the -e flag is subject to the system's command-line argument length limit (ARG_MAX). For very large scripts (e.g., when updating records with large text content), it would be more robust to pipe the script to osascript via stdin in the future.
| execFile("osascript", ["-l", "JavaScript", "-e", script], (error, stdout, stderr) => { | |
| \t\texecFile("osascript", ["-l", "JavaScript", "-e", script], { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => { |
|
Thanks for this, it LGTM! Let me try this change later |
Summary
executeJxabuilds theosascriptcommand as a shell string and escapes single quotes with'→''. That isn't a valid bash escape inside a single-quoted string —''collapses to nothing, so every apostrophe in the JXA script is silently stripped before reachingosascript.The bug is latent today because the project's JXA strings use double quotes throughout, but any tool emitting a single-quoted JXA string literal (
'foo', a contraction in an error message thrown back viaerror.toString(), etc.) would produce either anosascriptsyntax error or, worse, a silently malformed script.Fix
Switch from
exec(shell-interpreted command string) toexecFilewith an argv array.osascriptreceives the script as a single argument — no shell, no escaping needed.Test plan