Repository navigation
fix(inline-tools): escape app in pressKey tool to prevent AppleScript injection - #994
Conversation
…pt injection
## The bug (security)
`pressKeyTool` in `src/inline-tools.ts` accepted an optional `app`
parameter and embedded it raw in an AppleScript command:
if (app) {
execSync(`osascript -e 'tell application "${app}" to activate'`, ...);
}
A value containing `"` breaks out of the AppleScript string literal
and injects arbitrary AppleScript. AppleScript supports
`do shell script "..."`, so this is arbitrary code execution from a
tool-call argument.
PoC (pre-fix):
pressKey({
key: 'space',
app: '"); do shell script "rm -rf ~/sensitive"; tell application "Finder',
})
Resulting AppleScript:
tell application """); do shell script "rm -rf ~/sensitive"; tell application "Finder" to activate
…runs the shell command.
## Why this is the right fix
Two other call sites in the same file already use the correct escape
pattern (`safeKey` at line ~161, `safeApp` in switchAppTool around
line ~214). Only `pressKeyTool`'s app-activation branch was missing
the escape. Apply the same 3-step strip (backslash, single-quote,
double-quote). No new abstraction needed — local fix where the bug
lives, matches the established in-file pattern.
## Tests
`tests/inline-tools-press-key-app-escape.test.ts` — 4 source-grep
architectural assertions:
- raw `tell application "${app}"` pattern absent (pre-fix pattern
fails here)
- pressKey branch references `safeApp`
- `safeApp` computed via `.replace()` chain — pins all 3 strips
- Chain includes the `"` strip — the actual exploit-vector defense
Source-grep is the right shape: actual `execSync` call would spawn
osascript and is OS-dependent; the security contract is "escape `app`
before embedding," which grep verifies precisely.
All 4 pass locally. `npm run typecheck` clean.
Co-Authored-By: Vasiliy's Sutando <noreply@anthropic.com>
|
LGTM. Verified the bug: The fix is the right shape — same triple-replace Test is a source-grep regression guard rather than a runtime test — appropriate for "future refactor must not re-introduce the raw interpolation," since runtime AppleScript tests need a real Mac with the target app. |
…pt injection (sonichi#994) ## The bug (security) `pressKeyTool` in `src/inline-tools.ts` accepted an optional `app` parameter and embedded it raw in an AppleScript command: if (app) { execSync(`osascript -e 'tell application "${app}" to activate'`, ...); } A value containing `"` breaks out of the AppleScript string literal and injects arbitrary AppleScript. AppleScript supports `do shell script "..."`, so this is arbitrary code execution from a tool-call argument. PoC (pre-fix): pressKey({ key: 'space', app: '"); do shell script "rm -rf ~/sensitive"; tell application "Finder', }) Resulting AppleScript: tell application """); do shell script "rm -rf ~/sensitive"; tell application "Finder" to activate …runs the shell command. ## Why this is the right fix Two other call sites in the same file already use the correct escape pattern (`safeKey` at line ~161, `safeApp` in switchAppTool around line ~214). Only `pressKeyTool`'s app-activation branch was missing the escape. Apply the same 3-step strip (backslash, single-quote, double-quote). No new abstraction needed — local fix where the bug lives, matches the established in-file pattern. ## Tests `tests/inline-tools-press-key-app-escape.test.ts` — 4 source-grep architectural assertions: - raw `tell application "${app}"` pattern absent (pre-fix pattern fails here) - pressKey branch references `safeApp` - `safeApp` computed via `.replace()` chain — pins all 3 strips - Chain includes the `"` strip — the actual exploit-vector defense Source-grep is the right shape: actual `execSync` call would spawn osascript and is OS-dependent; the security contract is "escape `app` before embedding," which grep verifies precisely. All 4 pass locally. `npm run typecheck` clean. Co-authored-by: Vasiliy's Sutando <noreply@anthropic.com>
The bug (security)
pressKeyToolinsrc/inline-tools.tsaccepted an optionalappparameter and embedded it raw in an AppleScript command:```ts
if (app) {
execSync(`osascript -e 'tell application "${app}" to activate'`, ...);
}
```
A value containing
\"breaks out of the AppleScript string literal and injects arbitrary AppleScript. AppleScript supportsdo shell script \"...\", so this is arbitrary code execution from a tool-call argument.PoC (would have worked pre-fix):
```ts
pressKey({
key: 'space',
app: '"); do shell script "rm -rf ~/sensitive"; tell application "Finder',
})
```
The resulting AppleScript runs
do shell script \"rm -rf ~/sensitive\".Why this is the right fix
Two other call sites in the same file already use the correct escape pattern:
pressKeyTool(key fallback path)safeKeyswitchAppTool.executesafeAppOnly
pressKeyTool's app-activation branch was missing the escape. Apply the same 3-step strip (backslash, single-quote, double-quote). No new abstraction needed — local fix where the bug lives, matches the established in-file pattern.Tests
tests/inline-tools-press-key-app-escape.test.ts— 4 source-grep architectural assertions:tell application \"\${app}\"pattern absentsafeAppsafeAppcomputed via.replace()chain\"stripSource-grep is the right shape — the actual
execSynccall spawns osascript and is OS-dependent; the security contract is "escapeappbefore embedding," which grep verifies precisely.All 4 pass locally.
npm run typecheckclean.🤖 Generated with Claude Code