Back to skill

Security audit

Geo Tag Photos

Security checks for vulnerabilities and agentic risk

Overview

This skill mostly matches its stated photo-geotagging purpose, but a crafted report file could make its write step alter JPG metadata outside the chosen photo folder.

Install only if you are comfortable reviewing the generated CSV and running it on trusted photo directories. Do not use report CSV files from other people or hand-edited/untrusted sources, run real writes on copies or with verified backups, and be aware that landmark/city/country text is sent to Nominatim and cached locally.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/photo_geolocator.py:320
Finding
Report CSV Path Traversal and Symlink Escape Permit Modification of Files Outside the Photo Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/photo_geolocator.py:320-341` **Vulnerability Type**: Path traversal and improper filesystem boundary validation **Risk Level**: High ### Vulnerable Code ```python for r in actionable: src = photo_dir / r["filename"] if not src.is_file() or not is_jpg(src): failures.append(f"missing or non-JPG: {r['filename']}") continue try: lat = float(r["inferred_lat"]) lon = float(r["inferred_lon"]) except ValueError: failures.append(f"bad coords for {r['filename']}") continue try: shutil.copy2(src, backup / r["filename"]) except Exception as e: failures.append(f"backup failed for {r['filename']}: {e}") continue description = f"{r['city']}, {r['country']}" user_comment = ( f"confidence={r['confidence']}; landmark={r['landmark']}; source=geo-tag-photos" ) try: write_location(src, lat=lat, lon=lon, description=description, user_comment=user_comment) ``` The extension validation used by this path is also insufficient: ```python def is_jpg(path: Path) -> bool: return path.suffix.lower() in (".jpg", ".jpeg") ``` ### Technical Analysis The `write` command treats the `filename` column of the supplied report CSV as a trusted relative filename. It joins the value directly to both `photo_dir` and `backup` without validating that the resolved paths remain inside those directories. A filename containing `../` can escape the selected directories. In addition, `Path.is_file()` follows symbolic links, while `is_jpg()` only validates the textual suffix. A symlink named with a `.jpg` extension inside the photo directory can therefore refer to a JPG outside that directory and pass both checks. The required backup does not eliminate the vulnerability. `shutil.copy2()` also receives a destination derived from the unvalidated CSV filename, allowing that destination to escap ...[truncated 2149 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require every report filename to be a simple basename: ```python raw_name = r.get("filename", "") candidate = Path(raw_name) if ( not raw_name or candidate.is_absolute() or candidate.name != raw_name or raw_name in {".", ".."} ): failures.append(f"unsafe filename: {raw_name!r}") continue ``` 2. Resolve the source and verify that it remains beneath the source directory: ```python source_root = photo_dir.resolve(strict=True) src = (source_root / raw_name).resolve(strict=True) try: src.relative_to(source_root) except ValueError: failures.append(f"source escapes photo directory: {raw_name!r}") continue ``` 3. Reject symbolic links unless following them is an explicit supported feature: ```python unresolved_src = source_root / raw_name if unresolved_src.is_symlink(): failures.append(f"symbolic links are not allowed: {raw_name!r}") continue ``` 4. Apply an independent containment check to the backup destination: ```python backup_root = backup.resolve() dst = (backup_root / raw_name).resolve() try: dst.relative_to(backup_root) except ValueError: failures.append(f"backup destination escapes backup directory: {raw_name!r}") continue ``` 5. Avoid overwriting existing backup files by opening destinations with exclusive creation semantics or explicitly rejecting `dst.exists()` before copying. 6. Validate that every actionable row corresponds to an actual filename discovered during a fresh scan of the selected photo directory. Do not rely solely on an earlier report. 7. Add regression tests for: - `../outside.jpg` - Absolute Unix and Windows paths - Nested path separators - Symlinks to files outside the source directory - Backup destination traversal - Existing backup destination files - Report replacement or modification between review and write ] ...[truncated 2 chars]

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding
Unpinned Dependency Ranges Create a Non-Reproducible Supply-Chain Trust Boundary<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1-4` **Vulnerability Type**: Unpinned third-party dependencies without integrity hashes **Risk Level**: Medium ### Vulnerable Code ```text piexif>=1.1.3 Pillow>=10.4.0 requests>=2.31.0 pytest>=8.0.0 ``` The documented installation procedure executes unconstrained dependency resolution: ```bash pip install -r requirements.txt ``` ### Technical Analysis Every dependency uses an open-ended lower-bound constraint. Consequently, installation may select any future version accepted by the configured package index, along with unpinned transitive dependencies. The project provides neither a lock file nor package hashes. This makes installations non-reproducible and prevents users from verifying that they are installing the same dependency artifacts reviewed or tested by the project. If a future package release or transitive dependency is compromised, malicious, or incompatible, it can be accepted automatically. `pytest` is also included in the primary requirements file even though it is test-only, unnecessarily increasing the production dependency and transitive dependency surface. This finding does not establish that any currently named package is malicious. The risk is the absence of version and artifact integrity controls around code that is installed and later imported by the application. ### Attack Path 1. A future release of a listed package, or one of its transitive dependencies, is compromised or malicious. 2. The malicious release remains compatible with the open-ended `>=` constraint. 3. A user follows the documented setup command: ```bash pip install -r requirements.txt ``` 4. The package resolver selects and installs the compromised release from the configured package index. 5. The Skill imports the installed dependency during normal execution. 6. Malicious dependency code executes with the permissions of the user running the Skill. A similar path exists if a user or ...[truncated 574 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin direct and transitive dependencies to reviewed versions using a generated lock file. 2. Require artifact hashes during installation. For example, generate a fully resolved requirements file and install it with: ```bash pip install --require-hashes -r requirements.lock ``` 3. Use a dependency-management workflow such as `pip-tools`, Poetry, or another lock-file-capable tool to separate: - Abstract dependency declarations - Fully resolved runtime dependencies - Development and test dependencies 4. Move `pytest` into a separate development requirements file, such as `requirements-dev.txt`. 5. Configure automated dependency vulnerability scanning and controlled update review. Dependency updates should run the full test suite before release. 6. In CI and release environments, use a trusted package index, disable unexpected extra indexes, and retain provenance or checksums for installed artifacts. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • 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
Findings (15)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
A second description-behavior mismatch finding reinforces that the skill's core claims—vision-based landmark recognition, geolocation, EXIF writing, Nominatim lookup, and guarded write behavior—are not evidenced by the implementation. In a security context, this undermines user consent and predictability: operators may expose private photos or modify files under false assumptions about what the skill actually does.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
A second description-behavior mismatch finding reinforces that the skill's core claims—vision-based landmark recognition, geolocation, EXIF writing, Nominatim lookup, and guarded write behavior—are not evidenced by the implementation. In a security context, this undermines user consent and predictability: operators may expose private photos or modify files under false assumptions about what the skill actually does.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill advertises file, network, and potential environment-affecting behavior but does not declare an explicit tool scope such as allowed tools or permissions. In an agent setting, that ambiguity can let the runtime or operator grant broader capabilities than users expect, increasing the risk of unintended file modification, data exposure, or network access.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The markdown instructs users to run a 'real write' step that writes GPS data into image files and creates backups, but it does not explicitly warn that the original photos will be modified in place. Because this affects user data and system state, the description should clearly disclose the destructive or irreversible nature of the action before the command is run.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The function modifies the original JPG in place by dumping EXIF data and inserting it into the file. This is a user-data-affecting write operation, but the code provides no confirmation prompt, user-facing log message, or warning near the operation to disclose that the image metadata will be altered.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
This code sends user-derived landmark, city, and country queries to the external Nominatim service over the network, but this file contains no consent, disclosure, or privacy gating at the point of transmission. In a photo geotagging skill, those place names may reveal sensitive travel history or personal location context, so undisclosed third-party sharing is a real privacy weakness even if the service is legitimate.

Unpinned Dependencies

Low
Category
Supply Chain
Content
piexif>=1.1.3
Pillow>=10.4.0
requests>=2.31.0
pytest>=8.0.0
Confidence
96% confidence
Finding
The dependency is specified with a lower-bound only, which allows future installs to resolve to different versions over time. This weakens reproducibility and can unintentionally introduce vulnerable or incompatible releases through the supply chain, though the file itself does not prove active compromise.

Unpinned Dependencies

Low
Category
Supply Chain
Content
piexif>=1.1.3
Pillow>=10.4.0
requests>=2.31.0
pytest>=8.0.0
Confidence
97% confidence
Finding
Pillow is not pinned to an exact version, so installations may pull different builds at different times. Because image libraries have a history of parsing-related vulnerabilities, leaving the version open increases exposure uncertainty for a skill that processes JPG files.

Unverifiable Dependency: Pillow has 16 known advisory(ies) (CVE-2016-2533 (Pillow buffer overflow in ImagingPcdDecode); CVE-2023-50447 (Arbitrary Code Execution in Pillow); CVE-2021-27922 (Pillow Uncontrolled Resource Consumption) +13 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.

Unpinned Dependencies

Low
Category
Supply Chain
Content
piexif>=1.1.3
Pillow>=10.4.0
requests>=2.31.0
pytest>=8.0.0
Confidence
97% confidence
Finding
Requests is specified as >=2.31.0 rather than an exact version, which permits non-reproducible installs and may introduce a later vulnerable release or behavior change. Since this skill makes network calls to external geolocation services, dependency integrity matters for transport and credential handling.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 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.

Unpinned Dependencies

Low
Category
Supply Chain
Content
piexif>=1.1.3
Pillow>=10.4.0
requests>=2.31.0
pytest>=8.0.0
Confidence
91% confidence
Finding
Pytest is also unpinned, which is a supply-chain hygiene issue even though it is usually a development/test dependency rather than runtime code. The direct exploitability is lower in this context, but it still reduces build reproducibility and may affect CI or developer environments.

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.

Intent-Code Divergence

Low
Confidence
76% confidence
Finding
The docstring says the module only touches specific EXIF fields, which implies narrowly scoped metadata changes. In practice, write_location loads the full EXIF structure, mutates selected fields, then dumps and reinserts the full EXIF blob, so existing EXIF data is rewritten as part of the operation.

Missing User Warnings

Low
Confidence
86% confidence
Finding
The cache persists geocoding results on disk without any user-facing notice in this code, creating a local record of location-derived lookups that could expose sensitive places if the machine or workspace is later accessed by another party. While this is only local storage and lower risk than external transmission, it still introduces privacy exposure through residual data retention.

Static analysis

No suspicious patterns detected.