Back to skill

Security audit

Apple Notes (AppleScript)

Security checks for vulnerabilities and agentic risk

Overview

This Apple Notes skill is purpose-related, but it needs review because it can modify/delete personal notes, silently export attachments, and contains input-handling bugs that could run unintended commands.

Review carefully before installing. Use only with trusted note names, folder names, queries, and limits; keep operations scoped to specific folders; back up Notes before edit/delete; and avoid read/attachment/PDF export workflows until exports use a private directory and require explicit user approval.

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 (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/notes-list.sh:29
Finding
AppleScript Injection Through Unvalidated Limit Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/notes-list.sh:8-12,21-29`; `scripts/notes-search.sh:10-20,93-102` **Vulnerability Type**: AppleScript source injection **Risk Level**: High ### Vulnerable Code ```bash # scripts/notes-list.sh FOLDER="${1:-}" LIMIT="${2:-20}" resolve_folder "$FOLDER" osascript <<EOF tell application "Notes" try $FOLDER_SCRIPT on error errMsg return "Error: " & errMsg end try set maxCount to $LIMIT as integer ``` ```bash # scripts/notes-search.sh for arg in "$@"; do case "$arg" in --title-only) TITLE_ONLY=true ;; --spotlight) SPOTLIGHT=true ;; *) if [ -z "$QUERY" ]; then QUERY="$arg" elif [ -z "$FOLDER" ]; then FOLDER="$arg" else LIMIT="$arg" fi ;; esac done osascript <<EOF tell application "Notes" try $FOLDER_SCRIPT on error errMsg return "Error: " & errMsg end try set searchTerm to "$ESCAPED_QUERY" as text set maxCount to $LIMIT as integer ``` ### Technical Analysis Both scripts insert the attacker-controlled `LIMIT` argument directly into an unquoted AppleScript heredoc. No numeric validation is performed before the value becomes part of executable AppleScript source. Shell quoting at the point where the command-line argument is received does not make this safe. Variable expansion still occurs while constructing the heredoc. An argument containing a newline, an AppleScript statement, and a comment marker can terminate the expected assignment and introduce additional statements. AppleScript's `do shell script` command can then be used to invoke local shell commands. ### Attack Path 1. An attacker causes the skill or a user to invoke `notes-list.sh` or `notes-search.sh` with a crafted limit argument. 2. The script stores the complete value in `LIMIT` without checking that it contains only decimal digits. 3. The heredoc expands `$LIMIT` directly into ...[truncated 862 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Validate the limit before invoking AppleScript: ```bash if ! [[ "$LIMIT" =~ ^[0-9]+$ ]] || (( LIMIT < 1 || LIMIT > 1000 )); then echo "Error: limit must be an integer between 1 and 1000" >&2 exit 1 fi ``` - Avoid generating AppleScript source from command-line values. Pass values through `osascript` arguments: ```bash osascript - "$LIMIT" <<'EOF' on run argv set maxCount to (item 1 of argv) as integer -- Remaining logic end run EOF ``` - Use a quoted heredoc delimiter so shell expansion cannot unexpectedly modify the AppleScript source. - Apply the same validation and argument-passing pattern in both `notes-list.sh` and `notes-search.sh`. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/notes-search.sh:52
Finding
AppleScript Injection in the Spotlight Search Branch<![CDATA[ ## Vulnerability Details **File Location**: `scripts/notes-search.sh:29-39,47-57` **Vulnerability Type**: AppleScript source injection **Risk Level**: High ### Vulnerable Code ```bash if [ "$SPOTLIGHT" = "true" ]; then # Search in Notes domain using mdfind (OCR-enabled search) if [ -n "$FOLDER" ]; then echo "⚠ Spotlight search ignores folder filter (searching all notes)" >&2 fi echo "Searching via Spotlight (OCR-enabled)..." >&2 results=$(mdfind "kMDItemTextContent == '*$QUERY*' && kMDItemContentType == 'com.apple.notes.note'" 2>/dev/null | head -$LIMIT) for notePath in $results; do noteName=$(basename "$notePath" .note) osascript <<EOF tell application "Notes" try repeat with n in every note if name of n contains "$QUERY" then ``` ### Technical Analysis The regular search path creates `ESCAPED_QUERY` using `escape_as`, but the Spotlight branch uses the original `QUERY` directly inside a generated AppleScript string literal. A query containing a double quote can escape the intended string. The attacker can then insert AppleScript operators or statements. Because the generated program is executed by `osascript`, injected AppleScript may invoke `do shell script` and execute arbitrary local commands. The `mdfind` invocation passes its predicate as one shell argument, so the principal command-execution issue is not shell metacharacter handling in `mdfind`; it is the later raw interpolation into the AppleScript heredoc. ### Attack Path 1. The attacker supplies a crafted search query and enables `--spotlight`. 2. The query is incorporated into the Spotlight predicate and then retained unchanged. 3. If Spotlight returns at least one result, the script enters the result loop. 4. The raw query is inserted into: ```applescript if name of n contains "$QUERY" then ``` 5. A malicious quote closes the string and introduces attacker-controlled AppleScript. ...[truncated 625 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Never place `QUERY` directly into AppleScript source. - Pass the query through `argv` using a quoted heredoc: ```bash osascript - "$QUERY" <<'EOF' on run argv set searchTerm to item 1 of argv tell application "Notes" repeat with n in every note if name of n contains searchTerm then -- Process match end if end repeat end tell end run EOF ``` - Validate `LIMIT` independently as a bounded positive integer. - Avoid `for notePath in $results`, which splits paths on whitespace. Read results line by line instead: ```bash while IFS= read -r notePath; do ... done <<< "$results" ``` - Where possible, avoid regenerating the same Notes query once per Spotlight result. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/notes-create.sh:48
Finding
AppleScript Injection Through Raw Folder Values in Output Statements<![CDATA[ ## Vulnerability Details **File Location**: `scripts/notes-create.sh:41-48`; `scripts/notes-delete.sh:29-42` **Vulnerability Type**: AppleScript source injection **Risk Level**: High ### Vulnerable Code ```bash # scripts/notes-create.sh if bodyText is "" then set htmlBody to "<h1>$ESCAPED_TITLE</h1>" else set htmlBody to "<h1>$ESCAPED_TITLE</h1><br>" & bodyText end if set newNote to make new note at targetFolder with properties {name:"$ESCAPED_TITLE", body:htmlBody} set newId to id of newNote return "Created: $ESCAPED_TITLE" & linefeed & "Folder: $FOLDER" & linefeed & "ID: " & newId ``` ```bash # scripts/notes-delete.sh set searchTerm to "$ESCAPED_NAME" as text repeat with n in noteList if name of n contains searchTerm then set noteTitle to name of n try set noteFolder to name of container of n on error set noteFolder to "unknown" end try delete n return "Deleted: " & noteTitle & " (from " & noteFolder & ")" end if end repeat return "Error: No note matching '" & searchTerm & "' found in $FOLDER" ``` ### Technical Analysis Folder components used by `resolve_folder` are escaped before being inserted into folder lookup statements. However, both scripts later insert the original, unescaped `FOLDER` value into separate AppleScript string literals. A folder value containing a double quote and AppleScript syntax can close the string and alter the generated program. The escaping performed inside `_resolve_folder.sh` does not protect these later raw uses. In `notes-create.sh`, the vulnerable return statement is reached after a note is created successfully. In `notes-delete.sh`, the vulnerable return statement is reached when no matching note is found in the resolved folder. ### Attack Path 1. The attacker supplies a crafted folder argument containing a quote and AppleScript syntax. 2. `resolve_folder` safely escapes the value only while producing `FOLDER_SCRIPT`. ...[truncated 1033 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not interpolate the raw `FOLDER` value into AppleScript. - For immediate mitigation, apply the same escaping routine before every string interpolation: ```bash ESCAPED_FOLDER="$(escape_as "$FOLDER")" ``` Escaping must then be used consistently in both scripts. - Prefer passing folder names through `osascript` arguments instead of embedding them in source. - Construct user-facing status output in Bash after `osascript` returns structured data. This eliminates the need to include the user-supplied folder in executable AppleScript. - Audit every heredoc interpolation, not only folder resolution, because escaping is context-specific and does not automatically protect later uses. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/notes-attachment.sh:35
Finding
Sensitive Note Attachments Exported to a Predictable Shared Temporary Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/notes-attachment.sh:8-10,30-36,153-167`; `scripts/notes-read.sh:32-34,76-91` **Vulnerability Type**: Unsafe temporary-file handling and sensitive-data exposure **Risk Level**: Medium ### Vulnerable Code ```bash # scripts/notes-attachment.sh NAME="${1:-}" FOLDER="${2:-}" OUTPUT_DIR="${3:-/tmp/notes-export/}" FALLBACK_BASE="$ACCOUNTS_DIR/$ACCOUNT_UUID/FallbackPDFs" PREVIEW_BASE="$ACCOUNTS_DIR/$ACCOUNT_UUID/Previews" # Create output directory mkdir -p "$OUTPUT_DIR" ``` ```bash if [ -n "$FOUND_FILE" ]; then safe_title=$(echo "$NOTE_TITLE" | tr -cd '[:alnum:]._-' | cut -c1-50) output_filename="${safe_title}-${EXTRACTED_COUNT}.${ext}" output_path="$OUTPUT_DIR/$output_filename" counter=1 while [ -f "$output_path" ]; do output_filename="${safe_title}-${EXTRACTED_COUNT}(${counter}).${ext}" output_path="$OUTPUT_DIR/$output_filename" counter=$((counter + 1)) done cp "$FOUND_FILE" "$output_path" echo "Extracted: $output_path" fi ``` ```bash # scripts/notes-read.sh OUTPUT_DIR="/tmp/notes-export/" mkdir -p "$OUTPUT_DIR" local output_path="$OUTPUT_DIR/$output_filename" while [ -f "$output_path" ]; do output_filename="${safe_title}-${extracted_count}(${counter}).${file_type}" output_path="$OUTPUT_DIR/$output_filename" counter=$((counter + 1)) done cp "$found_file" "$output_path" ``` ### Technical Analysis The scripts copy potentially sensitive scanned documents and images from the private Notes container into the fixed path `/tmp/notes-export/`. They do not set a restrictive `umask`, verify ownership or permissions of an existing directory, create a private per-run directory, reject symbolic links, or clean exported files after use. The destination filenames are predictable because they are derived from a sanitized note title and a small counter. The check `[ -f "$output_path" ]` is not a safe exclusive-creation mechanism. It introdu ...[truncated 1554 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create a private directory for each invocation: ```bash umask 077 OUTPUT_DIR="$(mktemp -d "${TMPDIR:-/tmp}/notes-export.XXXXXX")" || exit 1 trap 'rm -rf -- "$OUTPUT_DIR"' EXIT ``` - If files must remain after execution, require an explicit caller-selected destination and document that behavior. - Verify that any caller-supplied output directory: - is owned by the current user; - is not a symbolic link; - has mode `0700` or comparably restrictive permissions. - Create output files atomically and exclusively rather than checking with `-f` before copying. - Reject symbolic links at destination paths and avoid predictable names where possible. - Apply the same secure export helper to both `notes-read.sh` and `notes-attachment.sh`. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/notes-export-pdf.sh:17
Finding
PDF Export Wrapper Can Return an Unrelated Stale Attachment<![CDATA[ ## Vulnerability Details **File Location**: `scripts/notes-export-pdf.sh:10,17-30` **Vulnerability Type**: Insecure shared-state handling and unintended data disclosure **Risk Level**: Medium ### Vulnerable Code ```bash OUTPUT_DIR="/tmp/notes-export/" # Run attachment extraction silently "$(dirname "$0")/notes-attachment.sh" "$NAME" "$FOLDER" "$OUTPUT_DIR" > /dev/null 2>&1 if [ $? -ne 0 ]; then exit 1 fi # Find the PDF file in output directory PDF_FILE=$(ls -t "$OUTPUT_DIR"/*.pdf 2>/dev/null | head -1) if [ -n "$PDF_FILE" ]; then echo "$PDF_FILE" else echo "Error: No PDF found for note '$NAME'" >&2 exit 1 fi ``` ### Technical Analysis The wrapper does not capture the exact file produced by the current extraction. Instead, after `notes-attachment.sh` exits successfully, it searches the shared export directory and returns whichever PDF has the newest modification time. `notes-attachment.sh` can exit successfully when a requested note has no attachments, when its attachments are images rather than PDFs, or when no PDF was extracted. In those cases, a PDF left by a previous operation can still match the wildcard and be returned. Concurrent invocations create an additional race: another process can place or export a PDF after extraction but before the `ls -t` command. ### Attack Path 1. A sensitive PDF from note A already exists in `/tmp/notes-export/`, or another process places a PDF there. 2. The victim requests PDF export for note B. 3. Note B exists, but it has no PDF attachment or only a preview image. 4. `notes-attachment.sh` completes with a successful status. 5. The wrapper runs `ls -t /tmp/notes-export/*.pdf`. 6. The stale or concurrently created PDF for note A is selected. 7. The wrapper reports that unrelated file as the export result for note B. ### Impact Assessment The caller may receive and subsequently disclose, upload, parse, or otherwise process a PDF belonging to a different note. This violates data isolati ...[truncated 233 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create a unique private directory for each export invocation: ```bash umask 077 OUTPUT_DIR="$(mktemp -d "${TMPDIR:-/tmp}/notes-export.XXXXXX")" || exit 1 trap 'rm -rf -- "$OUTPUT_DIR"' EXIT ``` - Modify `notes-attachment.sh` to return the exact paths it created in a machine-readable format. - Select only a path emitted by the current child process; do not scan a shared directory by modification time. - Treat “no PDF extracted” as a distinct nonzero result. - If the returned file must survive process exit, atomically move only the verified current export to a caller-selected private destination. - Add tests for stale files, image-only notes, notes without attachments, and concurrent exports. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (8)

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This skill exposes powerful read, search, edit, create, and delete operations over a large personal Apple Notes corpus, including categories that strongly imply sensitive content such as medical, receipts, property, and intimate material. The documentation provides operational guidance and performance tips but no explicit privacy warning, consent boundary, or destructive-action safeguards, which increases the chance an agent or user will access or modify highly sensitive data without informed approval.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The script writes extracted note attachments to a caller-controlled output directory, defaulting to /tmp, which creates a straightforward exfiltration path for note contents and attachments. In the context of a note-management skill, exporting attachments to arbitrary local paths is more dangerous because it enables copying sensitive documents and images outside the Notes trust boundary without strong safeguards or clear necessity for basic CRUD/search behavior.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The script directly reads Apple Notes' private on-disk storage under the user's Library group container and derives attachment paths from internal account UUIDs and content identifiers. This expands the skill's effective capability beyond normal AppleScript note operations into raw filesystem access, enabling extraction of attachment data from private app storage in a way that is not obvious from the stated skill scope.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
This shell script performs an irreversible delete operation on a matched Apple Notes item, but the code provides no confirmation prompt before deletion and no clear user-facing warning that the first partial match will be deleted immediately. The existing comments and usage text mention folder safety for search scope, but they do not disclose the destructive behavior strongly enough at the point of action.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The comment claims the script only shows attachment information, but the implementation actually copies attachment files to disk. This mismatch is security-relevant because users, reviewers, or calling agents may authorize a low-risk read action while the script performs higher-risk data exfiltration to local storage.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script enumerates the Apple Notes account container and prepares filesystem paths for attachment extraction without any invocation-time warning that reading a note may also access backing storage and export files. In a skill context, hidden side effects are especially risky because an agent or user may invoke a seemingly harmless read action and unintentionally disclose sensitive note attachments.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The script labeled as a read operation creates /tmp/notes-export and later copies note attachments there, which expands its behavior from passive read access to local data extraction and persistence. Writing potentially sensitive note attachments to a world-accessible or shared temporary location can expose private data to other local processes or users and increases the risk of unintended retention.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The copy operation writes extracted attachments to /tmp without explicit user consent or prominent disclosure. Temporary directories are commonly scanned, backed up, or accessed by other local tooling, so sensitive note content may be exposed beyond the original Notes application and retained longer than expected.

Static analysis

No suspicious patterns detected.