Back to skill

Security audit

claw2immich

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent Immich photo-library helper, but it exposes sensitive personal media through broad MCP authority and unauthenticated shared-link download flows without enough scoping or privacy guardrails.

Install only if you trust the configured Immich MCP server and can restrict it to the smallest useful access profile. Prefer read-only access for search/view use, avoid full_scope unless you explicitly need admin functions, and treat shared links and generated photo URLs as sensitive because they can expose private photos outside Immich authentication. Be cautious running the example scripts, especially downloads, until filename handling and JSON argument construction are hardened.

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

T09 · Insecure Skill Coding Practices

Warning
Location
examples/find-by-date.sh:29
Finding
Unsafe JSON Construction Allows MCP Query Manipulation## Vulnerability Details **File Location**: `examples/find-by-date.sh:29-39`; `examples/find-people-together.sh:52-57`; `examples/get-photo-urls.sh:37-42` **Vulnerability Type**: JSON injection through unvalidated string interpolation **Risk Level**: Medium ### Vulnerable Code `examples/find-by-date.sh:29-39`: ```bash QUERY="{ \"body_createdAfter\": \"$START_ISO\", \"body_createdBefore\": \"$END_ISO\", \"query_order\": \"desc\", \"query_size\": $LIMIT" if [[ -n "$CITY" ]]; then QUERY="$QUERY, \"body_city\": \"$CITY\"" fi ``` `examples/find-people-together.sh:52-57`: ```bash RESULTS=$(mcporter call immich.immich_searchassets \ --args "{ \"body_personIds\": [\"$PERSON1_ID\", \"$PERSON2_ID\"], \"query_order\": \"desc\", \"query_size\": $LIMIT }" \ ``` `examples/get-photo-urls.sh:37-42`: ```bash RESULTS=$(mcporter call immich.immich_searchassets \ --args "{ \"body_personIds\": [\"$PERSON_ID\"], \"query_order\": \"desc\", \"query_size\": $LIMIT }" \ ``` ### Technical Analysis The scripts construct JSON request bodies by directly interpolating command-line arguments and API-derived values. No JSON escaping is applied to string values, and `LIMIT` is inserted as an unquoted JSON token without validation that it is a bounded integer. A crafted `CITY` value containing quotes and additional JSON properties can alter the generated request structure. Similarly, a crafted `LIMIT` can append properties after an initially valid numeric value. This is JSON-level injection rather than shell command injection because the resulting request is still passed as one quoted argument to `mcporter`. The person IDs are obtained from Immich responses rather than directly from the command line, but they are also interpolated without JSON-safe encoding. A compromised or unexpectedly formatted service response could therefore produce malformed or manipulated requ ...[truncated 1042 chars]
Remediation
## Remediation Suggestions - Construct request bodies with `jq` instead of concatenating JSON strings. - Pass strings through `jq --arg` and numeric values through `jq --argjson`. - Validate `LIMIT` before use, for example by requiring a decimal integer within a reasonable range such as 1–100. - Validate date arguments against the expected `YYYY-MM-DD` format before appending timestamps. - Treat identifiers returned by remote services as untrusted and encode them with the same JSON-safe mechanism. Example hardening pattern: ```bash if ! [[ "$LIMIT" =~ ^[0-9]+$ ]] || (( LIMIT < 1 || LIMIT > 100 )); then echo "Limit must be an integer from 1 to 100." >&2 exit 1 fi QUERY=$(jq -n \ --arg after "$START_ISO" \ --arg before "$END_ISO" \ --arg city "$CITY" \ --argjson size "$LIMIT" \ '{ body_createdAfter: $after, body_createdBefore: $before, query_order: "desc", query_size: $size } + if $city != "" then {body_city: $city} else {} end') ``` Apply the same approach to person-ID arrays: ```bash QUERY=$(jq -n \ --arg first "$PERSON1_ID" \ --arg second "$PERSON2_ID" \ --argjson size "$LIMIT" \ '{ body_personIds: [$first, $second], query_order: "desc", query_size: $size }') ```

T09 · Insecure Skill Coding Practices

Warning
Location
examples/get-photo-urls.sh:69
Finding
Server-Controlled Asset Filename Can Escape the Download Directory## Vulnerability Details **File Location**: `examples/get-photo-urls.sh:69-74` **Vulnerability Type**: Path traversal and arbitrary relative-path file overwrite **Risk Level**: Medium ### Vulnerable Code ```bash if [[ $REPLY =~ ^[Yy]$ ]]; then FIRST_ID=$(echo "$RESULTS" | jq -r '.assets.items[0].id') FIRST_NAME=$(echo "$RESULTS" | jq -r '.assets.items[0].originalFileName') echo "Downloading: $FIRST_NAME" curl -o "$FIRST_NAME" "${IMMICH_SERVER}/api/assets/${FIRST_ID}/original" echo "✓ Saved to: $FIRST_NAME" fi ``` ### Technical Analysis `FIRST_NAME` is populated from the Immich response field `originalFileName` and then used directly as the path supplied to `curl -o`. Quoting prevents shell word splitting and command substitution, but it does not prevent filesystem path traversal. If an asset filename contains path separators, traversal components such as `../`, or an absolute path, the output may be written outside the intended working directory. The operation can overwrite an existing file where the executing user has write permission. Exploitation depends on a malicious or compromised Immich instance being able to return such a filename and on the user confirming the download prompt. ### Attack Path 1. An attacker creates or modifies an Immich asset so its returned `originalFileName` contains a path such as `../../target-file`. 2. A user runs `get-photo-urls.sh` for a person associated with that asset. 3. The malicious asset is selected as the first search result. 4. The user answers `y` to the download prompt. 5. The script passes the unvalidated filename to `curl -o`. 6. `curl` writes the downloaded content to the attacker-selected path, provided the current user has permission to write there. ### Impact Assessment Exploitation can create or overwrite files accessible to the user running the script. The scope is limited by that user's filesystem permissions, but affected files could in ...[truncated 351 chars]
Remediation
## Remediation Suggestions - Never use a remote metadata filename directly as an output path. - Download files into a dedicated directory created with restrictive permissions. - Reduce the supplied name to a basename and reject empty names, `.`, `..`, path separators, control characters, and unexpected filename patterns. - Prefer generating a local filename from the validated asset UUID and a trusted extension. - Use `curl --fail --show-error --location` and write to a temporary file before atomically moving it to the final validated destination. - Refuse to overwrite existing files unless the user explicitly authorizes that exact destination. Example hardening pattern: ```bash DOWNLOAD_DIR="./downloads" mkdir -p -- "$DOWNLOAD_DIR" chmod 700 -- "$DOWNLOAD_DIR" SAFE_ID=$(printf '%s' "$FIRST_ID" | tr -cd 'A-Za-z0-9-') if [[ -z "$SAFE_ID" || "$SAFE_ID" != "$FIRST_ID" ]]; then echo "Invalid asset ID." >&2 exit 1 fi OUTPUT_PATH="$DOWNLOAD_DIR/${SAFE_ID}.bin" if [[ -e "$OUTPUT_PATH" ]]; then echo "Refusing to overwrite existing file: $OUTPUT_PATH" >&2 exit 1 fi curl --fail --show-error --location \ --output "$OUTPUT_PATH" \ "${IMMICH_SERVER}/api/assets/${FIRST_ID}/original" ```
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (10)

Missing User Warnings

High
Confidence
98% confidence
Finding
The skill directly instructs operators to send unauthenticated shared links to users and states 'no auth required' without a corresponding privacy warning or consent check. In the context of a personal photo library, this is especially dangerous because it encourages operationalizing public access to sensitive media, increasing the chance of unauthorized viewing, redistribution, or exposure via chat systems and logs.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The README explicitly documents constructing direct photo URLs and mentions download/display behavior for personal photos, but provides no warning about privacy-sensitive handling, access control expectations, or the risk of exposing private family/media data through links, logs, or downstream agents. In the context of a photo-library skill, this omission increases the chance that users or agents will retrieve and share sensitive images in ways that bypass informed consent or safe handling practices.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The example script advertises generating thumbnail/original URLs and optional photo download without any accompanying warning about handling private photo content. Because this skill operates on a personal photo library, normalizing download flows without caution can lead to unintentional exfiltration, oversharing, or storage of sensitive images outside the protected Immich environment.

Lp3

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding
The skill documents shell-based usage via `mcporter call ...` and exposes operational capabilities, but the manifest does not declare an explicit tool scope such as allowed tools or permissions. That mismatch increases the risk that an agent or reviewer underestimates what the skill can invoke, weakening least-privilege controls and making misuse of local execution more likely.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The skill is described as a photo search/download helper, but the documentation explicitly states that 249 tools from the full Immich OpenAPI spec are available and later references `read_write`, `full_scope`, and admin-level access. This creates a scope/intent mismatch that can mislead users or agents into granting a much broader capability set than expected, including destructive or administrative actions unrelated to the stated purpose.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The documentation normalizes a workflow where `downloadAsset` returns a short-lived shared link that requires no authentication, but it does not clearly warn that anyone possessing the URL can access the photo until expiry. For a photo library skill handling potentially sensitive personal images, this can lead to unintentional data disclosure through link forwarding, logging, chat history retention, or accidental sharing.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This shell script makes external `mcporter call` requests using user-provided names and then fetches matching asset data, including photo paths and IDs. Although it prints progress messages, it does not disclose that personal names and photo metadata will be sent to and queried from the Immich service, which is a user-data/privacy-relevant operation.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The top-of-file documentation describes the script as an example for getting photo URLs and its usage text does not mention downloading. In contrast, lines 65-75 implement an interactive download of the first photo, creating a contradiction between the stated intent and the actual behavior.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The file is presented as an example to 'Get photo URLs for assets', and most of the script prints thumbnail/original URLs. However, the final block prompts the user and then uses curl to fetch the original asset and save it locally, which is a materially broader behavior than just retrieving or displaying URLs.

Description-Behavior Mismatch

Low
Confidence
94% confidence
Finding
The manifest description says the skill works with the Immich library to search photos by people, dates, locations, and albums, which frames the skill as search-oriented. However, the included example explicitly advertises 'Get photo URLs and download,' indicating the skill also supports retrieving asset URLs and downloading content, a behavior not reflected in the main description.

Static analysis

No suspicious patterns detected.