T09 · Insecure Skill Coding Practices
- Location
clawtv.py:395- Finding
AppleScript Injection Through an Unescaped Apple TV Device Name
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill is mostly coherent and disclosed, but it needs review because it controls media devices while storing sensitive credentials/screenshots insecurely and contains a real AppleScript injection risk.
Install only if you are comfortable giving it Apple TV control, optional Plex library/client control, and sending TV screenshots to Anthropic during AI mode. Before use, restrict ~/.clawtv permissions, avoid sensitive screens in do mode, clean screenshot history, use a budget-limited Anthropic key, and treat Plex tokens as high-value credentials.
clawtv.py:395AppleScript Injection Through an Unescaped Apple TV Device Name
clawtv.py:55Pairing Credentials and Plex Tokens Are Written Without Enforced Access Controls
clawtv.py:482Sensitive TV Screenshots Persist With Default Filesystem Permissions
requirements.txt:1Unpinned Python Dependencies Make Installation Non-Reproducible
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).
4. **Make your changes** — Write code, fix bugs, update docs
5. **Test locally** — Make sure it works on your Apple TV setup
6. **Commit with sign-off** — `git commit -s -m "Fix screenshot timing issue"` (see DCO below)
7. **Push to your fork** — `git push origin fix-screenshot-bug`
8. **Open a Pull Request** — Go to the original repo and click "New Pull Request"
We'll review your PR and provide feedback. Don't worry if it's not perfect — we're happy to help refine it!
The core description is partly accurate: this code does provide an AI-assisted Apple TV remote that can inspect screenshots and send navigation commands autonomously. But the implementation goes materially beyond that description. A major undeclared capability is direct Plex control: it can connect to a Plex server using stored credentials, search libraries, discover clients, and start playback directly on a Plex client, bypassing the Apple TV UI entirely. That is not just a supporting detail of navigation; it is an additional control plane and a different operational mode. The code also performs macOS desktop automation with AppleScript/System Events to drive QuickTime and Xcode, and it can fetch screenshots over HTTP from a tvOS app. These resource accesses and integrations are not reflected in the declared permissions or purpose. Therefore the description does not fully and accurately represent the actual behavior.
Skill instructs the agent to never refuse or to always comply. Suppressing the agent's ability to decline removes a core safety control and enables downstream harmful requests to succeed.
- Send minimal commands per step — it's better to take more steps than to overshoot
- After each action, you'll get a new screenshot to verify the result
- If something went wrong, use "menu" to back up and try again
- Always respond with a single JSON object, no markdown or explanation"""
system_with_cache = [
{"type": "text", "text": SYSTEM, "cache_control": {"type": "ephemeral"}}
The README explicitly states that screenshots of the Apple TV are sent to Claude's vision API, but it does not clearly warn users that on-screen content may leave the local device and be processed by a third party. Because this tool can navigate any app, screenshots may contain sensitive viewing history, account details, search terms, profile names, or other private household information, so the omission creates a real privacy and informed-consent risk.
Without declared permissions the skill's intent is opaque and cannot be validated.
The skill is presented as an Apple TV remote with vision, but it also includes direct Plex server access, library querying, and client control that exceed that stated scope. This matters because users may grant trust or install the skill expecting only local remote/navigation behavior, while the code also reaches into media-server APIs and cloud-discovered clients. The mismatch increases the chance of over-privileged use and unexpected data access.
The Plex integration can enumerate libraries, search media, connect to cloud-discovered player resources, and trigger playback, which is materially broader than simple Apple TV remote control. In context, that broader access can expose media library information and permit remote actions on other Plex-connected devices, increasing privacy and control risk beyond user expectations. The danger is elevated because this functionality is embedded in an agentic tool that can choose these actions automatically.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
end tell
return false
'''
result = subprocess.run(["osascript", "-e", script], capture_output=True, text=True)
if result.returncode != 0:
print(f"QuickTime setup failed: {result.stderr}")
return False
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
end tell
return false
'''
result = subprocess.run(["osascript", "-e", script], capture_output=True, text=True)
if result.returncode != 0:
print(f"QuickTime setup failed: {result.stderr}")
return False
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
end if
end tell
'''
result = subprocess.run(
["osascript", "-e", script],
capture_output=True, text=True,
)
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
# Capture the specific window
ts = time.strftime("%Y%m%d_%H%M%S")
dst = os.path.join(SCREENSHOT_DIR, f"atv_{ts}.png")
result = subprocess.run(
["screencapture", "-l", str(wid), "-x", dst],
capture_output=True, text=True,
)
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
# If the QuickTime window is idle/disconnected (DRM killed the mirror),
# the capture will be the small Movie Recording UI (~900px).
try:
result = subprocess.run(
["sips", "-g", "pixelWidth", dst],
capture_output=True, text=True,
)
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def _compress_screenshot(path):
"""Always convert to JPEG at 800px wide, quality 50. Returns (bytes, media_type)."""
jpeg_path = path.rsplit(".", 1)[0] + "_opt.jpg"
subprocess.run(
["sips", "--resampleWidth", "800", "--setProperty", "format", "jpeg",
"--setProperty", "formatOptions", "50", path, "--out", jpeg_path],
capture_output=True,
The code forwards both the user's natural-language goal and screenshot contents to the model without policy constraints or filtering to prevent disclosure of sensitive data visible on screen. Because the agent is designed to inspect arbitrary TV app screens, it may process credentials, personal recommendations, profile names, billing screens, or other sensitive context and can reflect or store that information in downstream responses and logs. The broad, unrestricted prompt design makes accidental over-collection more likely.
The AI agent sends Apple TV screenshots to the Anthropic API during the vision loop without any explicit runtime warning, confirmation, or consent gate. Screens may contain account names, watch history, recommendations, search queries, QR codes, or other sensitive on-screen content, so transmitting them to a third-party model provider creates a real privacy and data-governance risk. This is especially concerning because screenshot capture is repeated automatically across multiple steps.
The trigger guidance includes a catch-all condition for essentially any Apple TV navigation or control request, which can cause the skill to activate far more often than necessary. In an agent setting, broad auto-invocation increases the chance of unintended device control, screenshot capture, and external transmission of on-screen data to Anthropic when the user did not explicitly intend to use this high-privilege skill.
The documentation advises persisting the Anthropic API key in shell startup files such as ~/.zshrc or ~/.bashrc, which increases the lifetime and exposure surface of a sensitive credential. If those files are read by other tools, backed up insecurely, or exposed through local compromise, the key can be reused to access the user's Anthropic account and incur charges.
**"API key not found"**
- Set `export ANTHROPIC_API_KEY=sk-ant-...` in your shell
- Or add to ~/.zshrc or ~/.bashrc
**"Plex not found"**
- Verify `plex_url`, `plex_token`, and `plex_client` in config
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.
if cmd.startswith("sleep:"):
await asyncio.sleep(float(cmd.split(":")[1]))
continue
action = getattr(rc, cmd, None)
if action:
await action()
print(f" -> {cmd}")
The dependency is specified with a lower-bound only, which allows installation of any newer release, reducing build reproducibility and increasing supply-chain risk if a future version introduces a vulnerability or breaking behavior. In a skill that can control an Apple TV and interact with external services, unexpected dependency changes could affect integrity or reliability, though this file alone does not show direct exploitation.
pyatv>=0.15.0
anthropic>=0.40.0
plexapi>=4.15.0
pyobjc-framework-Quartz>=10.0 # optional: QuickTime screenshot method
The anthropic package is unpinned, so deployments may resolve to different versions over time, making it impossible to verify whether known vulnerable releases are being installed. Because this skill depends on an LLM SDK and may process screenshots or control decisions, pulling an affected version could expose local files, path handling, or other SDK-related security issues referenced by known advisories.
pyatv>=0.15.0
anthropic>=0.40.0
plexapi>=4.15.0
pyobjc-framework-Quartz>=10.0 # optional: QuickTime screenshot method
The manifest includes anthropic without a fixed version even though known advisories exist for that package, so the actual installed version may be vulnerable and cannot be verified from this file. In this skill's context, an LLM SDK may handle local files, prompts, screenshots, or tool interactions, making unresolved advisory exposure more concerning than a generic library drift issue.
The plexapi dependency is also unpinned, creating non-reproducible installs and unnecessary supply-chain exposure to future vulnerable or incompatible releases. Given the skill's automation context, dependency drift could alter network behavior or authentication handling in ways that are hard to audit.
pyatv>=0.15.0
anthropic>=0.40.0
plexapi>=4.15.0
pyobjc-framework-Quartz>=10.0 # optional: QuickTime screenshot method
No suspicious patterns detected.