Back to skill

Security audit

Blog to Kindle

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its blog-to-Kindle purpose, but unsafe email defaults and unescaped Mail.app automation could send files to the wrong recipient or run unintended local actions.

Review this skill before installing. Do not use the send script until the hard-coded Kindle address is removed, the recipient is required and confirmed, AppleScript arguments are safely passed or escaped, and attachment types are enforced. Avoid untrusted custom URLs, and treat the keychain/Gemini cover step as sensitive credential and third-party API use.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/send_to_kindle.py:12
Finding

AppleScript Injection Through Unescaped User-Controlled Arguments

Content
View full analysis
bool: """Send file to Kindle using Mail.app AppleScript.""" applescript = f''' tell application "Mail" set newMessage to make new outgoing message with properties {{subject:"{subject}", visible:false}} tell newMessage make new to recipient at end of to recipients with properties {{address:"{kindle_email}"}} set content to "Sent from blog-to-kindle skill." make new attachment with properties {{file name:"{file_path}"}} at after the last paragraph end tell send newMessage end tell ''' result = subprocess.run( ["osascript", "-e", applescript], capture_output=True, text=True ) ``` ## Technical Analysis The `subject`, `kindle_email`, and `file_path` values are inserted directly into AppleScript source code using an f-string. None of these values are escaped before being placed inside AppleScript string literals. Although `subprocess.run` does not invoke a shell here, it executes the dynamically generated source through `osascript`. An attacker who controls one of the interpolated values can supply quotation marks and additional AppleScript statements that terminate the intended string and modify the script's behavior. The subject and recipient are directly controllable through command-line arguments. The file path can also contain attacker-selected characters if the attacker can choose or create the attachment filename. Injected AppleScript can invoke other applications or use `do shell script` to execute local commands. ### Attack Path 1. An attacker causes the script to be invoked with a malicious `--subject`, `--kindle-email`, ...[truncated 1207 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/send_to_kindle.py:8
Finding

Hard-Coded Personal Kindle Recipient and Insufficient Attachment Restrictions

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fetch_blog.py:33
Finding

Server-Side Request Forgery Through Unrestricted Custom URLs and Extracted Links

Content
View full analysis
str: """Fetch a page with retries.""" for i in range(retries): try: resp = httpx.get(url, follow_redirects=True, timeout=30) resp.raise_for_status() return resp.text except Exception as e: if i == retries - 1: raise time.sleep(2 ** i) return "" ``` Extracted links are converted to absolute URLs without validating their origin or destination: ```python for a in soup.select(config["link_selector"]): href = a.get("href", "") if not href or href.startswith("#"): continue full_url = urljoin(base_url, href) if full_url not in seen: seen.add(full_url) links.append(full_url) ``` The custom archive URL is accepted directly from the command line: ```python if args.site == "custom": if not args.url: print("Error: --url required for custom sites") return 1 config = SITES["paulgraham"].copy() # Use as template config["archive_url"] = args.url config["base_url"] = urljoin(args.url, "/") else: config = SITES[args.site] ``` Both the archive and discovered article URLs are fetched: ```python archive_html = fetch_page(config["archive_url"]) links = get_article_links(archive_html, config, config["base_url"]) print(f"Found {len(links)} article links") if args.limit: links = links[:args.limit] articles = [] for i, url in enumerate(links): print(f"[{i+1}/{len(links)}] Fetching {url}...") try: html = fetch_page(url) ``` ## Technical Analysis The custom-site feature allows an arbitrary archive URL. The HTTP client follows redirects, and neither the initial URL nor redirect destinations ...[truncated 2195 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (12)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

This mismatch also hides operational behavior not clearly declared in the user-facing description, including Mail.app/AppleScript-based email sending and broader file-type handling than the stated EPUB workflow. Undisclosed outbound-email behavior is especially sensitive because it can transmit user content externally, and hidden implementation differences reduce a user's ability to give informed consent.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

This mismatch also hides operational behavior not clearly declared in the user-facing description, including Mail.app/AppleScript-based email sending and broader file-type handling than the stated EPUB workflow. Undisclosed outbound-email behavior is especially sensitive because it can transmit user content externally, and hidden implementation differences reduce a user's ability to give informed consent.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding

The skill advertises workflows that require shell, network, and file operations, but it does not declare any explicit tool scope or permissions boundaries. This is dangerous because an agent may execute broad-capability actions implicitly, increasing the risk of over-privileged behavior, unintended network access, or writing files/sending content without clear user-visible constraints.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill instructs users to email generated content to a Kindle address but does not warn about privacy, data retention, misdelivery, or third-party processing of transmitted documents. In this context, scraped content and generated ebooks may contain sensitive reading history, personal annotations, or copyrighted material, so silent email transmission creates a meaningful confidentiality and compliance risk.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

The manifest describes scraping blogs, compiling EPUBs, and generating a cover, but this manual workflow specifically instructs reading a credential from the local macOS keychain via security find-generic-password. Accessing local credential storage is a distinct capability that is not declared in the manifest and goes beyond the core document-processing workflow itself.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The instructions access a local secret and invoke an external API without warning users that an API credential is being pulled from the system keychain and that prompt data will be transmitted off-device. This creates an avoidable transparency and secret-handling risk: users may run the command without understanding what credential is used or what content is shared with the third party.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The workflow instructs users to email the EPUB to a Kindle address but does not warn that the book contents, metadata, sender information, and attachments are transmitted through email infrastructure and then processed by Amazon. For a skill that may package arbitrary scraped or user-provided content, this omission can cause unintended disclosure of sensitive material.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
82% confidence
Finding

The manifest says the skill is for downloading blogs, compiling them into EPUB, and sending archives to Kindle, but the implementation guidance automates a desktop mail client via osascript. Controlling local applications is a stronger capability than ordinary file conversion or web fetching and is not explicitly scoped in the manifest.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

Line L055 sets the generated EPUB metadata to lang: en unconditionally. This forces a specific language/locale without user opt-in, which matches the policy-violation category for locale constraints.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/compile_epub.py (reported line 90)May include surrounding context.

python
print("⚠️  No cover specified! Use --cover to add one.")
    
    print(f"Running pandoc...")
    result = subprocess.run(cmd, capture_output=True, text=True)
    
    if result.returncode != 0:
        print(f"pandoc error: {result.stderr}")

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
92% confidence
Finding

The script builds AppleScript by directly interpolating user-controlled values (subject, kindle_email, and file_path) into a quoted AppleScript string, then executes it via osascript. Because AppleScript strings can be broken by embedded quotes and script syntax, an attacker supplying crafted input could inject additional AppleScript commands, causing arbitrary local actions through Mail.app or other automatable macOS applications.

Content

Scanner excerpt · scripts/send_to_kindle.py (reported line 26)May include surrounding context.

python
end tell
    '''
    
    result = subprocess.run(
        ["osascript", "-e", applescript],
        capture_output=True,
        text=True

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest describes scraping blogs, compiling them into Kindle-friendly EPUBs, and sending archives to Kindle, but it does not indicate that the skill will invoke local system automation through subprocess execution. Calling osascript to control Mail.app is a broader host-execution capability than the stated content-processing purpose requires.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.