Back to skill

Security audit

photo-scout

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed image-search and downloader skill, but it needs Review because it browses and downloads arbitrary web content with weak containment safeguards.

Use this only in an isolated environment with no sensitive files or credentials mounted, restricted outbound network access, and resource limits. Pin dependencies before installing, avoid --break-system-packages, review source domains before running discovery/verify, and do not use watermark-stripping behavior unless you have rights to the images.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/extract_page_images.py:15
Finding
Unrestricted URL Fetching Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/extract_page_images.py:15-16`; `scripts/vision_pipeline.py:66`; `scripts/webctx_verify.py:63-79`; `scripts/source_router.py:103-108` **Vulnerability Type**: Server-Side Request Forgery (SSRF) **Risk Level**: High ### Vulnerable Code `scripts/extract_page_images.py:15-16`: ```python r = requests.get(url, headers={"User-Agent": UA, "Accept-Language": "zh-CN,zh;q=0.9"}, timeout=15) ``` `scripts/vision_pipeline.py:66`: ```python r = requests.get(url, headers=headers, timeout=timeout, stream=True) ``` `scripts/webctx_verify.py:63-79`: ```python if not page_url or not page_url.startswith("http"): return None from playwright.sync_api import sync_playwright out_dir = Path(out_dir) out_dir.mkdir(parents=True, exist_ok=True) with sync_playwright() as pw: browser = pw.chromium.launch( executable_path=exe, headless=True, args=["--no-sandbox", "--disable-blink-features=AutomationControlled"]) ctx = browser.new_context(user_agent=se.UA, locale="zh-CN", viewport={"width": viewport_w, "height": viewport_h}) ctx.add_init_script( "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})") pg = ctx.new_page() try: pg.goto(page_url, timeout=40000, wait_until="domcontentloaded") ``` `scripts/source_router.py:103-108`: ```python for d in domains: for scheme in ("https", "http"): base = f"{scheme}://{d}" r = _get(base, timeout=12) if not r: continue ``` ### Technical Analysis The Skill fetches user-controlled or externally derived URLs without validating the destination network address. Relevant input channels include: - Direct URLs passed to `extract_page_images.py`. - Values supplied through `--extra-urls`. - URLs imported through `--extra-file`. - Domains supplied through `source_router.py --domains`. - Source-page URLs stored in candidate metadata and s ...[truncated 2062 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Accept only normalized `https` URLs unless plain HTTP is explicitly required. 2. Parse URLs with `urllib.parse.urlsplit` and reject: - Embedded usernames or passwords. - Missing or malformed hostnames. - Unsupported schemes. - Unexpected ports. 3. Resolve all destination hostnames before connecting. 4. Reject every resolved IPv4 and IPv6 address belonging to: - Loopback ranges. - Private ranges. - Link-local ranges. - Multicast ranges. - Reserved or unspecified ranges. - Known cloud metadata addresses. 5. Disable automatic redirects or validate each redirect target using the same policy. 6. Apply destination allowlists for fixed-purpose integrations such as App Store, Weibo, Baidu, and Bing. 7. Revalidate DNS immediately before connection to reduce DNS-rebinding exposure. 8. Restrict outbound network access at the container or host firewall layer as defense in depth. 9. Treat candidate and selected JSON files as untrusted input and revalidate every URL when consumed. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/webctx_verify.py:70
Finding
Chromium Sandbox Disabled While Processing Untrusted Web Content<![CDATA[ ## Vulnerability Details **File Location**: `scripts/search_engines.py:184-188`; `scripts/source_router.py:214-217`; `scripts/webctx_verify.py:70-74` **Vulnerability Type**: Unsafe Browser Isolation Configuration **Risk Level**: High ### Vulnerable Code `scripts/search_engines.py:184-188`: ```python browser = pw.chromium.launch( executable_path=exe, headless=headless, args=["--no-sandbox", "--disable-blink-features=AutomationControlled"]) ctx = browser.new_context(user_agent=UA, locale="zh-CN", viewport={"width": viewport_w, "height": viewport_h}) ``` `scripts/source_router.py:214-217`: ```python browser = pw.chromium.launch( executable_path=exe, headless=True, args=["--no-sandbox", "--disable-blink-features=AutomationControlled"]) ctx = browser.new_context(user_agent=HEADERS["User-Agent"], locale="zh-CN", ``` `scripts/webctx_verify.py:70-74`: ```python browser = pw.chromium.launch( executable_path=exe, headless=True, args=["--no-sandbox", "--disable-blink-features=AutomationControlled"]) ctx = browser.new_context(user_agent=se.UA, locale="zh-CN", viewport={"width": viewport_w, "height": viewport_h}) ``` ### Technical Analysis The Skill explicitly starts Chromium with `--no-sandbox`. This disables a central Chromium defense that normally isolates renderer processes from the host operating system. The affected browser instances process remote JavaScript and other active content from: - Baidu search pages. - Weibo pages. - Search-result source pages. - User-influenced URLs passed to source verification. Because source verification can open arbitrary HTTP or HTTPS pages, an attacker can cause the unsandboxed browser to process a page under the attacker's control. If that page exploits a Chromium renderer vulnerability, disabling the sandbox substantially reduces the barriers between renderer compromise and host-level process compromise. The `--disable-blink-features=A ...[truncated 1493 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `--no-sandbox` from every Chromium launch configuration. 2. Run Playwright and Chromium as a dedicated unprivileged operating-system user. 3. Ensure the deployment environment supports Chromium's normal namespace and setuid sandbox mechanisms. 4. If sandbox support is unavailable, isolate browser execution in a disposable container or virtual machine with: - No mounted credentials or sensitive host directories. - A read-only root filesystem where practical. - Dropped Linux capabilities. - A restrictive seccomp profile. - Strict memory and CPU limits. - Restricted outbound network access. 5. Keep Chromium and Playwright pinned to reviewed, patched versions. 6. Validate and restrict source-page URLs before navigation. 7. Consider blocking unnecessary resource types and downloads in Playwright. 8. Avoid automation-evasion flags unless they are demonstrably necessary and approved for the deployment context. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/vision_pipeline.py:66
Finding
Unbounded Response Buffering and Image Decompression Permit Resource Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `scripts/vision_pipeline.py:66-99`; `scripts/extract_page_images.py:15-21`; `scripts/source_router.py:64-75` **Vulnerability Type**: Uncontrolled Resource Consumption **Risk Level**: Medium ### Vulnerable Code `scripts/vision_pipeline.py:66-99`: ```python r = requests.get(url, headers=headers, timeout=timeout, stream=True) if r.status_code == 200: ctype = r.headers.get("Content-Type", "") if "text" in ctype or "html" in ctype: return None # 防 HTML 假响应 return r ... try: content = r.content except Exception: continue if len(content) < 1500: continue try: img = Image.open(io.BytesIO(content)) img.load() return img, content except Exception: continue ``` `scripts/extract_page_images.py:15-21`: ```python r = requests.get(url, headers={"User-Agent": UA, "Accept-Language": "zh-CN,zh;q=0.9"}, timeout=15) r.encoding = r.apparent_encoding or "utf-8" ... soup = BeautifulSoup(r.text, "html.parser") ``` `scripts/source_router.py:64-75`: ```python r = requests.get(url, headers=h, timeout=timeout, **kw) if r.status_code == 200 and r.content: return r ... img = Image.open(io.BytesIO(content)) w, h = img.size ``` ### Technical Analysis The code does not enforce maximum response sizes before buffering complete bodies into memory. In `vision_pipeline.py`, `stream=True` is specified, but the protection is negated by subsequently reading `r.content`, which buffers the entire response. Downloaded images are passed to Pillow and fully decompressed through `img.load()` without an application-level pixel or dimension limit. A small, highly compressed image can expand to a very large in-memory representation. The HTML extractor likewise evaluates encoding and parses the complete response body with BeautifulSoup without a maximum body size. The discovery workflow compounds this issue by fetching thumbnails concurrently with eight worker threads: ```python ...[truncated 1366 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Stream response bodies in bounded chunks rather than reading `r.content`. 2. Reject responses whose declared `Content-Length` exceeds a conservative limit. 3. Stop downloading once the cumulative streamed byte count exceeds that limit, including when `Content-Length` is absent or false. 4. Explicitly close every response through a context manager. 5. Enforce maximum image width, height, and total pixel count before full decoding. 6. Retain Pillow's decompression-bomb checks and convert `DecompressionBombWarning` into a hard failure for untrusted files. 7. Reject unsupported formats before expensive processing where possible. 8. Limit HTML response sizes before encoding detection and BeautifulSoup parsing. 9. Reduce concurrency or enforce an aggregate in-flight byte budget. 10. Apply container-level memory, CPU, process, and execution-time limits as defense in depth. ]]>

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding
Unpinned Dependencies and Browser Artifacts Create Supply-Chain Exposure<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1-5`; `SKILL.md:163-165` **Vulnerability Type**: Unpinned Third-Party Dependencies **Risk Level**: Medium ### Vulnerable Code `requirements.txt:1-5`: ```text Pillow requests beautifulsoup4 openpyxl playwright ``` `SKILL.md:163-165`: ```text - pip:`Pillow requests beautifulsoup4 openpyxl playwright`(openpyxl 可选) - Chromium:`python3 -m playwright install chromium`(主通道必需;缺失时降级为仅 Bing) - Linux 沙箱如遇 pip 权限问题可加 `--break-system-packages` ``` ### Technical Analysis All Python dependencies are specified without exact versions or integrity hashes. Consequently, separate installations of the same Skill may resolve to different package versions. The documented Playwright installation command also downloads a browser artifact according to the installed Playwright release rather than a project-controlled lockfile or verified artifact manifest. The listed package names appear to be legitimate and no typographical dependency-confusion package was identified. The risk arises from mutable resolution and lack of reproducibility rather than evidence that a currently listed package is malicious. The recommendation to use `--break-system-packages` may also modify a system-managed Python environment, increasing the effect of dependency conflicts and weakening environment isolation. ### Attack Path 1. A user follows the installation instructions at a later date. 2. The package manager resolves the latest versions available from the configured package index. 3. Playwright downloads the browser revision associated with the resolved Playwright package. 4. If an upstream package, index account, mirror, or browser distribution channel is compromised, malicious installation or runtime code may enter the environment. 5. Because versions and hashes are not fixed, the resulting installation can differ from the version originally audited. ### Impact Assessment A compromised dependency executes with the privilege ...[truncated 469 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every direct and transitive dependency to a reviewed version. 2. Generate a lockfile containing cryptographic hashes. 3. Install with hash verification, such as `pip --require-hashes`. 4. Configure an approved package index or internal mirror rather than relying on an unspecified source. 5. Pin the Playwright version and document the corresponding Chromium revision. 6. Verify downloaded browser artifacts through a trusted integrity mechanism where supported. 7. Perform automated vulnerability scanning and license review on every dependency update. 8. Use an isolated virtual environment or container instead of `--break-system-packages`. 9. Establish a controlled update process that reviews and tests new dependency versions before changing the lockfile. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (28)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill is advertised as a general-purpose multimodal image search workflow, but the implementation reportedly performs narrower direct-source harvesting, including undeclared Weibo and App Store retrieval. Hidden or understated data-source behavior increases risk because users may not realize the skill is contacting third-party services, collecting profile-derived assets, or bypassing the expected search-and-review flow.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill is advertised as a general-purpose multimodal image search workflow, but the implementation reportedly performs narrower direct-source harvesting, including undeclared Weibo and App Store retrieval. Hidden or understated data-source behavior increases risk because users may not realize the skill is contacting third-party services, collecting profile-derived assets, or bypassing the expected search-and-review flow.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill is advertised as a general-purpose multimodal image search workflow, but the implementation reportedly performs narrower direct-source harvesting, including undeclared Weibo and App Store retrieval. Hidden or understated data-source behavior increases risk because users may not realize the skill is contacting third-party services, collecting profile-derived assets, or bypassing the expected search-and-review flow.

Lp3

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding
The skill describes capabilities that involve environment access, file read/write, and network activity, but it declares no explicit tool scope or permission boundaries. In an agent setting, missing scope declarations can cause the skill to be invoked with broader privileges than users expect, increasing the risk of unintended web access, file modification, or data exposure.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The trigger phrases are very broad, such as generic requests to find or download images, which can cause the skill to activate on ordinary user requests without clear intent for networked scraping/downloading. Overbroad activation increases the chance of surprising tool use, unnecessary browsing, and accidental retrieval of copyrighted, sensitive, or irrelevant material.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The title and all required response templates are written as mandatory Chinese instructions, and L084-L096 says the model 'must' answer in that format. This imposes a specific language/locale behavior without user opt-in or an explicit justification that the skill is region-specific.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The instruction to remove a watermark suffix from image URLs explicitly guides the agent to obtain an unwatermarked version of copyrighted media. In a photo-downloading skill, this increases the likelihood of bypassing attribution, licensing, or source-imposed integrity controls and can facilitate unauthorized reuse of protected images.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
The general recommendation to remove '!watermark' from image URLs normalizes watermark removal as a retrieval technique rather than a rights-sensitive exception. Because this skill is specifically designed to find and download high-quality images at scale, the instruction materially increases the risk of copyright misuse and circumvention of publisher-imposed content markings.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
This code performs a network request to each provided URL and transmits request metadata, including a hard-coded Accept-Language value, but the only visible messaging is a progress print and the docstring does not disclose the network/privacy behavior. For a code file, outbound transmission of user/system data should have some user-facing disclosure unless clearly warned elsewhere, which is not evident in this file.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The request hard-codes "Accept-Language: zh-CN,zh;q=0.9", which imposes a specific language/locale choice for fetched content. The file does not offer a user option to choose locale or document a justified region-specific constraint, so this conflicts with the language/locale policy guidance.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The discover flow fetches remote thumbnails and writes screenshots, thumbnails, candidate metadata, and contact sheets to the user-supplied workdir without an explicit consent or safety notice about outbound requests and local persistence. In a photo-downloader skill this behavior is expected, but it still creates privacy and safety risk because arbitrary remote hosts learn the user's IP/user agent and untrusted image content is stored locally, potentially consuming disk or introducing harmful content into downstream workflows.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The select flow downloads original images from remote URLs and saves them into the final directory, again without an explicit warning that external content will be retrieved and persisted. Although core to the skill's purpose, this is still a real safety issue because selected URLs may point to attacker-controlled servers, causing privacy leakage, unexpected storage of untrusted files, and possible legal/compliance issues if users assume only visual inspection occurs.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The module hard-codes an Accept-Language header of "zh-CN,zh;q=0.9,en;q=0.8", which enforces a specific locale preference for all outbound requests. This is a natural-language/locale policy concern because the user is not offered any language choice or opt-in.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The Playwright browser context is created with locale="zh-CN", forcing a specific language/locale for rendered search pages. The file does not provide a user-selectable option or documented justification for mandating this locale.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The HTTP headers hard-code `Accept-Language: zh-CN,zh;q=0.9`, which forces a specific language preference for outbound requests. This is a natural-language/locale policy concern because the skill does not offer the user any language choice or document a justified region-specific constraint.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
The Playwright browser context is created with `locale="zh-CN"`, which enforces a specific locale for the session. The file does not present this as an opt-in choice or explain it as a necessary region-specific restriction, so it violates the language/locale policy criterion.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
This code performs outbound network requests to arbitrary image URLs and may automatically attach a forged Referer based on the target CDN. While the module docstring describes the technical behavior, there is no user-facing warning, confirmation, or visible disclosure that external requests will be made and metadata will be sent to third parties.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The request headers hard-code "Accept-Language: zh-CN,zh;q=0.9", which forces a specific locale preference for all outbound HTTP requests. This is a natural-language policy concern because it imposes a language/locale choice without offering the user selection or documenting a justified region-specific constraint.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The browser context is created with a hard-coded locale of "zh-CN", which imposes a specific language/locale behavior on all page loads. This is a natural-language policy concern because the file does not offer user opt-in or explain why a China-specific locale is required.

Unpinned Dependencies

Low
Category
Supply Chain
Content
Pillow
requests
beautifulsoup4
openpyxl
Confidence
98% confidence
Finding
`Pillow` is declared without a version pin, so builds are not reproducible and may resolve to a vulnerable or incompatible release at install time. In a skill that processes downloaded images, this matters more because image-parsing libraries have a history of memory corruption, resource exhaustion, and code-execution issues when handling untrusted 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
97% confidence
Finding
`Pillow` has multiple known advisories, but the manifest does not specify a version, making it impossible to verify whether the deployed package is safe. This is more dangerous in this skill than in a generic app because the core workflow downloads and inspects untrusted images, directly exercising a historically vulnerable image-processing library.

Unpinned Dependencies

Low
Category
Supply Chain
Content
Pillow
requests
beautifulsoup4
openpyxl
playwright
Confidence
97% confidence
Finding
`requests` is unpinned, which allows different environments to install different versions, including ones with known security defects. Because this skill downloads remote content from the internet, an outdated or vulnerable HTTP client could expose credentials, mishandle redirects, or weaken TLS-related protections.

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
97% confidence
Finding
`requests` has known advisories, but because no version is pinned, the actual installed release may still be vulnerable and cannot be verified from this manifest. Given that this skill performs remote downloads, any flaw in URL handling, redirect processing, credential use, or transport validation can directly affect confidentiality and integrity.

Unpinned Dependencies

Low
Category
Supply Chain
Content
Pillow
requests
beautifulsoup4
openpyxl
playwright
Confidence
94% confidence
Finding
`beautifulsoup4` is unpinned, creating supply-chain and reproducibility risk even if no specific advisory is cited here. Since the skill scrapes and parses remote pages, inconsistent parser behavior across versions can introduce security and reliability issues over time.

Unpinned Dependencies

Low
Category
Supply Chain
Content
Pillow
requests
beautifulsoup4
openpyxl
playwright
Confidence
98% confidence
Finding
`openpyxl` is unpinned, so installation may select a version affected by known XML-related flaws or other future advisories. Spreadsheet libraries can become dangerous when processing attacker-controlled files or embedded content, so version ambiguity is a real security concern.

Static analysis

No suspicious patterns detected.