Back to skill

Security audit

Apple Photos Cleaner

Security checks for vulnerabilities and agentic risk

Overview

This Apple Photos skill is mostly coherent, but it needs review because it can move or export photos through AppleScript and may act on unintended same-named assets despite broad read-only safety claims.

Install only if you are comfortable granting an agent access to sensitive Apple Photos metadata, including people, locations, hidden/favorite status, shared-library details, and iCloud status. Treat cleanup and export as review-required actions: run plan or preview mode first, avoid executing cleanup on libraries with reused filenames, and verify results in Photos.app before emptying Recently Deleted or sharing exported folders.

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/cleanup_executor.py:210
Finding
Filename-Only Asset Matching Can Delete Unintended Photos<![CDATA[ ## Vulnerability Details **File Location**: `scripts/cleanup_executor.py:210-224` **Vulnerability Type**: Non-unique identifier used for destructive operations **Risk Level**: High ### Vulnerable Code ```python # Build the filename matching list with proper escaping filename_list = ", ".join(f'"{escape_applescript(fn)}"' for fn in filenames) script = f""" set targetNames to {{{filename_list}}} set matchCount to 0 tell application "Photos" repeat with targetName in targetNames try set matchingItems to (search for targetName) repeat with anItem in matchingItems if (filename of anItem) is equal to (contents of targetName) then delete anItem ``` ### Technical Analysis Cleanup candidates are initially selected from the Photos database with a unique `Z_PK` value. However, the destructive AppleScript operation discards that identifier and searches the entire Photos library using only `ZFILENAME`. Photo filenames are not guaranteed to be unique. Cameras and phones commonly reuse names such as `IMG_0001.JPG`, particularly after counter resets, device migrations, or imports from multiple devices. The generated AppleScript iterates over every search result and deletes every asset whose filename equals the candidate filename. AppleScript string escaping reduces script-injection risk, but it does not address identifier ambiguity. The comment claiming that filename matching is the safest approach is therefore incorrect for destructive operations. The execution result is also potentially misleading. After a successful AppleScript batch, `success_count` is increased by the number of database candidates rather than by the number of Photos assets actually matched and moved. Consequently, the program may delete more items than it reports. ### Attack Path 1. The Photos library contains two or more assets with the same filename. 2. One of those assets satisfies a cleanup category, such as ...[truncated 1074 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not authorize deletion using filenames alone. 2. Preserve and use a stable Photos identifier that can be resolved through Photos.app if such an identifier is available. 3. If Photos.app cannot resolve the database identifier, use a composite identity containing filename, exact creation timestamp, media type, dimensions, and other stable metadata. 4. Treat every lookup producing more than one result as ambiguous and skip it rather than deleting all matches. 5. Resolve the final Photos.app asset set before confirmation and display every resolved item to the user. 6. Require a second confirmation when any ambiguity or candidate-count mismatch is detected. 7. Parse and validate the actual AppleScript match count instead of increasing `success_count` by the requested batch size. 8. Add tests with multiple assets sharing the same filename and verify that no unrelated asset can be deleted. 9. Update comments and documentation to disclose that filename searches are ambiguous unless additional identity checks are applied. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/smart_export.py:221
Finding
Filename-Only Export Can Expose Photos Outside the Selected Filters<![CDATA[ ## Vulnerability Details **File Location**: `scripts/smart_export.py:221-240` **Vulnerability Type**: Ambiguous asset selection causing unintended data export **Risk Level**: Medium ### Vulnerable Code ```python file_match_blocks = [] for fn in filenames: escaped = escape_applescript(fn) file_match_blocks.append( f' set targetName to "{escaped}"\n' f" set matchedItems to (search for targetName)\n" f" repeat with anItem in matchedItems\n" f" if filename of anItem is targetName then\n" f" copy anItem to end of toExport\n" f" end if\n" f" end repeat" ) search_code = "\n".join(file_match_blocks) applescript = ( 'tell application "Photos"\n' " set toExport to {}\n" f"{search_code}\n" f' set destFolder to POSIX file "{escaped_path}" as alias\n' " if (count of toExport) > 0 then\n" " export toExport to destFolder\n" " end if\n" "end tell" ) ``` ### Technical Analysis The database export plan records each selected asset's `Z_PK` and applies filters such as date range, favorite status, album, or detected person. During actual export, however, these identifiers and filters are discarded. Photos.app is searched globally using only each selected asset's filename. If multiple assets have the same filename, every exact match is appended to `toExport`, even if some matches do not satisfy the original date, person, album, or favorite filter. Therefore, the actual export set can be broader than the reviewed export plan. The destination path and filenames are escaped, which limits direct AppleScript injection. Folder names are also sanitized. These protections do not prevent incorrect asset selection. ### Attack Path 1. The Photos library contains multiple assets with an identical filename. 2. The user requests an export constrained by a person, album, date range, location, or f ...[truncated 958 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve export targets through unique Photos identifiers rather than filenames. 2. Where unique Photos identifiers are unavailable, match a composite identity containing filename, creation timestamp, media type, dimensions, and other stable metadata. 3. Abort or skip an export target whenever the Photos.app lookup returns multiple possible matches. 4. Reapply the original export filters to the resolved Photos.app objects where the API exposes the necessary properties. 5. Present the fully resolved export set and actual item count before writing files. 6. Add an explicit confirmation step for actual export, especially when exporting to shared or external locations. 7. Compare the resolved item count with the database plan and fail closed when they differ. 8. Add collision-focused tests covering album, person, favorites, and date filters. ]]>

other

Note
Location
SKILL.md:1186
Finding
Contradictory Read-Only Safety Claims Misrepresent Mutating Operations<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:1186-1192` **Vulnerability Type**: Misleading security and permission documentation **Risk Level**: Low ### Vulnerable Documentation ```markdown ## Safety & Permissions - ✅ **All operations are READ-ONLY** — No photos are modified or deleted - ✅ **No external dependencies** — Pure Python stdlib - ✅ **No Photos.app API** — Direct SQLite reads (safe) - ⚠️ **Smart export uses AppleScript** — Requires Photos.app to be running - ⚠️ **Cleanup executor uses AppleScript** — Moves items to Recently Deleted (recoverable) ``` A related top-level claim appears in `README.md:7`: ```markdown > **Safety:** All operations are read-only database queries. No photos are modified or deleted without explicit user action through the cleanup executor. ``` ### Technical Analysis The absolute statements that all operations are read-only and that no Photos.app API is used conflict with implemented behavior: - `cleanup_executor.py` invokes Photos.app through AppleScript and moves assets to Recently Deleted. - `smart_export.py` invokes Photos.app through AppleScript and writes exported media to the filesystem. Other sections do disclose these capabilities and the cleanup executor requires both `--execute` and interactive confirmation. Therefore, the behavior is not hidden malicious code or instruction hijacking. Nevertheless, the contradictory global safety language can cause users or automated Agents to assign an incorrect permission and risk profile to the Skill. ### Attack Path 1. A user or Agent reviews the global Safety and Permissions section. 2. It classifies the Skill as entirely read-only and non-mutating. 3. The same workflow later exposes cleanup or export functionality. 4. The user or Agent invokes those operations under the earlier incorrect assumption that the Skill cannot modify the Photos library or write photo data. 5. Cleanup changes Photos library state, while export creates copies on the filesy ...[truncated 640 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the absolute read-only claim with: “Database analysis operations open Photos.sqlite in read-only mode.” 2. Clearly separate operation classes: - Analysis: read-only SQLite access. - Cleanup: mutates the Photos library through Photos.app after confirmation. - Export: reads media through Photos.app and writes copies to the filesystem. 3. Replace “No Photos.app API” with language explaining that analysis does not use Photos.app, while cleanup and export use AppleScript automation. 4. Place the mutating-operation disclosure in the top-level description, Safety section, README, and command documentation. 5. Explicitly document the Photos automation permission and filesystem write permissions required by each operation. 6. Disclose the filename-collision limitations until unique asset resolution is implemented. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (25)

Intent-Code Divergence

High
Confidence
98% confidence
Finding
The skill repeatedly markets itself as read-only and safe, yet it also includes a cleanup executor that moves photos to Recently Deleted via AppleScript. This is dangerous because users or upstream agents may grant trust, skip safeguards, or invoke the skill under a false assumption that it cannot modify state, leading to unintended destructive actions against a personal photo library.

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The permissions and safety section says all operations are read-only and that no photos are modified or deleted, but the same file documents AppleScript-based export and cleanup actions that alter application state. False safety guarantees are especially risky in security-sensitive tooling because they can bypass policy checks, user caution, or sandboxing assumptions based on declared behavior rather than actual behavior.

Credential Access

High
Category
Privilege Escalation
Content
assert sanitize_folder_name("Vacation 2025") == "Vacation 2025"

    def test_strips_path_traversal(self):
        result = sanitize_folder_name("../../etc/passwd")
        assert "/" not in result
        assert "\\" not in result
        # Path separators removed — no traversal possible
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Vague Triggers

Medium
Confidence
89% confidence
Finding
The trigger list is very broad and includes generic photo-related phrases, which can cause the skill to activate in contexts where the user did not intend deep analysis of a Photos library. Because this skill accesses sensitive metadata and also contains state-changing cleanup capability, overbroad invocation increases the chance of privacy-invasive analysis or accidental exposure of destructive options.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill analyzes highly sensitive personal data including people, faces, locations, shared-library details, and iCloud sync state, but the description lacks a prominent privacy warning and informed-consent framing. This can cause users or orchestrating agents to underestimate the sensitivity of the data being processed and disclose or summarize intimate metadata without adequate notice.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The guidance tells assistants that users must delete via Photos.app, but the documented cleanup script can perform trashing automatically through AppleScript. This inconsistency can mislead an agent into presenting the skill as advisory-only when it is actually capable of changing user data, increasing the risk of accidental deletion or unsafe automation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
1. **Always preview first** — Run cleanup_executor without --execute
2. **Show counts and sizes** — "287 old screenshots, 2.1 GB"
3. **Explain safety** — Items go to Recently Deleted, recoverable for 30 days
4. **Get confirmation** — Never auto-execute cleanup

### Output Format Guidance
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The module docstring says the tool 'Supports moving junk photos to trash with interactive confirmation,' implying confirmation is part of the cleanup behavior. However, the actual deletion routine in execute_cleanup directly runs AppleScript deletions and relies on main() for prompting, so the documented behavior of the executor itself is broader/safer than what the code at the execution boundary guarantees.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
script = generate_trash_applescript(filenames)

        try:
            result = subprocess.run(
                ["osascript", "-e", script],
                capture_output=True,
                text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
This script enumerates named individuals from the Photos database and outputs ranked 'best' and 'worst' portraits, along with filenames, timestamps, favorites, and other metadata, without any explicit privacy warning, consent check, or output minimization. Even if intended for legitimate photo curation, this materially increases privacy risk because it exposes biometric/identity-linked inferences and sensitive personal metadata in a readily consumable form.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This script enumerates and summarizes highly sensitive Apple Photos metadata, including hidden items, favorites, location presence, date ranges, and named people, without any built-in user-facing notice, consent flow, or scope limitation. Even though it appears intended for legitimate analysis, exposing or processing this metadata silently can violate user expectations and leak intimate behavioral and relationship information if run in an agent context.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The skill’s stated purpose is location/travel mapping, but it additionally queries detected-face/person data and surfaces named individuals associated with each location. Combining geolocation clusters with identity data materially increases privacy sensitivity by enabling inference about where specific people were present, which exceeds the minimally necessary data for the advertised function.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The file outputs highly sensitive information: GPS-derived location clusters, travel timelines, and named people tied to those places, yet it provides no explicit privacy notice, consent flow, or output minimization. This creates a privacy exposure risk because the generated reports can reveal habitual locations, trips, and social associations that could be misused if viewed by an unauthorized person or logged elsewhere.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This code returns and summarizes highly sensitive photo metadata, including latitude/longitude coordinates and recognized people names, without any visible consent flow, warning, or minimization. In a memory-browsing skill this creates a real privacy risk because a caller can retrieve intimate historical location and relationship data that users may not expect to be exposed in aggregate outputs.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This script performs detailed analysis of named people in a photo library, including face counts, co-occurrence relationships, favorites, and timelines, all of which are highly sensitive biometric and social-graph data. The file contains no explicit user-facing privacy warning, consent prompt, minimization control, or indication that the output may reveal sensitive personal relationships and identity information, increasing the risk of unintended disclosure or misuse.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The script exposes Shared Library contributor identifiers directly in its output, which can reveal personal sharing relationships and account-linked metadata to whoever can run or view the report. In a forensic or analysis context this may be intentional functionality, but without an explicit warning, minimization, or redaction option, it creates a privacy and data-exposure risk beyond simple asset statistics.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
)

    try:
        result = subprocess.run(
            ["osascript", "-e", applescript],
            capture_output=True,
            text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Intent-Code Divergence

Low
Confidence
84% confidence
Finding
The safety statement says all operations are read-only database queries and that no photos are modified or deleted without explicit user action through the cleanup executor. However, elsewhere the README documents `cleanup_executor.py` as a command that batch-moves junk to trash, meaning the project is not uniformly read-only. This is a documentation-level contradiction in how the overall skill's behavior is described.

Unverifiable Dependency: setuptools has 10 known advisory(ies) (CVE-2013-1633 (Setuptools vulnerable to Man-in-the-middle attacks); CVE-2025-47273 (setuptools has a path traversal vulnerability in PackageIndex.download that lead); CVE-2024-6345 (setuptools vulnerable to Command Injection via package URL) +7 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unverifiable Dependency: pytest has 2 known advisory(ies) (CVE-2025-71176 (pytest has vulnerable tmpdir handling); CVE-2025-71176 (pytest has vulnerable tmpdir handling)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Missing User Warnings

Low
Confidence
83% confidence
Finding
This code performs a file write when `output_file` is provided, which is a safety-relevant operation under the rule. While it logs after writing and the CLI flag name suggests output behavior, this module itself does not include a confirmation prompt and its docstring does not clearly warn that an existing file may be created or overwritten.

Missing User Warnings

Low
Confidence
90% confidence
Finding
This script performs detailed behavioral analysis over sensitive photo-library metadata, including timestamps, favorites, screenshots, and activity streaks, which can reveal personal routines and habits. While there is no obvious exfiltration or covert behavior, the lack of any user-facing privacy notice or consent mechanism increases the risk of unintended privacy exposure when the skill is run.

Natural-Language Policy Violations

Low
Confidence
90% confidence
Finding
The comment and implementation establish Northern Hemisphere season mapping as the default behavior, with Southern Hemisphere support only via an explicit flag. This imposes a locale-specific interpretation by default, which can misclassify users' photos by season unless they know to opt in to the alternative.

Missing User Warnings

Low
Confidence
78% confidence
Finding
The code collects and returns a list of the largest assets including filenames, creation timestamps, and dimensions, which are user data attributes that may be privacy-sensitive. While the module docstring says it performs storage analysis, this file does not include any explicit warning, notice, or comment disclosing that detailed per-file metadata will be surfaced.

Missing User Warnings

Low
Confidence
83% confidence
Finding
This is a code file, so SQP-2 applies to safety-critical operations. The test explicitly validates creation of a script containing a Photos `delete` action, but the surrounding docstring and assertions do not include any warning, confirmation, or disclosure that the generated script is destructive/irreversible in effect.

Static analysis

No suspicious patterns detected.