T09 · Insecure Skill Coding Practices
- Location
- src/clam/scanner/menu_scanner.py:65
- Finding
- AppleScript Injection Through Unescaped Application and Menu Metadata<![CDATA[ ## Vulnerability Details **File Location**: `src/clam/scanner/menu_scanner.py:65-164` **Vulnerability Type**: AppleScript source injection **Risk Level**: High ### Vulnerable Code Application names are inserted directly into executable AppleScript: ```python def _get_process_name(app_name: str) -> str | None: """Get the System Events process name for an app. Prefers foreground (non-background-only) processes to avoid picking helper agents like "figma_agent" instead of the main "Figma" process. """ script = f''' tell application "System Events" set procs to every process whose name contains "{app_name}" and background only is false if (count of procs) > 0 then return name of item 1 of procs end if -- Fallback to any matching process set procs to every process whose name contains "{app_name}" if (count of procs) > 0 then return name of item 1 of procs end if end tell''' return _run_osascript(script) ``` Detected process names are also inserted directly: ```python # Step 1: Get menu bar item names bar_script = f''' tell application "System Events" tell process "{process_name}" get name of every menu bar item of menu bar 1 end tell end tell''' raw = _run_osascript(bar_script) ``` Process and menu names are again inserted when scanning individual menu groups: ```python def _scan_menu_group(process_name: str, bar_name: str) -> MenuGroup | None: """Scan a single menu bar item for its menu items (including one level of submenus).""" script = f''' tell application "System Events" tell process "{process_name}" set output to "" try set menuItems to every menu item of menu 1 of menu bar item "{bar_name}" of menu bar 1 repeat with mi in menuItems try set n to name of mi if n is not missing value then -- Check for submenu ...[truncated 4548 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Stop interpolating values into AppleScript source.** Pass application, process, and menu names as separate `osascript` arguments and access them through an `on run argv` handler. Example pattern: ```python script = ''' on run argv set targetName to item 1 of argv tell application "System Events" set procs to every process whose name contains targetName if (count of procs) > 0 then return name of item 1 of procs end if end tell end run ''' subprocess.run( ["osascript", "-e", script, app_name], capture_output=True, text=True, timeout=timeout, ) ``` 2. **Apply the same argument-passing design to all dynamic values**, including `process_name`, `bar_name`, menu item names, and submenu names used by generated wrappers. 3. **If source interpolation cannot be eliminated**, implement a single audited AppleScript string-encoding routine that escapes at least backslashes, quotation marks, carriage returns, line feeds, and relevant Unicode control characters. Argument passing remains preferable because manual escaping is error-prone. 4. **Validate discovered identities before use.** Resolve the target application through canonical bundle paths or bundle identifiers and verify that process names and menu metadata belong to that application. 5. **Constrain application identifiers and generated package names** to a conservative allowlist where possible, such as ASCII letters, digits, periods, underscores, and hyphens. Reject unexpected control characters and quoting characters. 6. **Add regression tests** using application, process, and menu names containing quotes, backslashes, newlines, Unicode separators, and attempted AppleScript statement termination. Tests should confirm that each value remains data and cannot alter program structure. 7. **Limit permission exposure.** Clearly separate full AppleScript ...[truncated 204 chars]
