Back to skill

Security audit

Wallhaven Downloader

Security checks for vulnerabilities and agentic risk

Overview

This Wallhaven wallpaper downloader is mostly purpose-aligned, but it can expose the Wallhaven API key in local process arguments and may follow redirects outside its stated host limits.

Review before installing. This skill downloads remote files into a folder you choose and may read WALLHAVEN_API_KEY. Avoid passing the API key on the command line, and be aware that even the environment key can appear in the child curl process URL until the implementation is changed. Use a low-risk Wallhaven key and a dedicated output directory.

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
scripts/wallhaven_download.py:146
Finding

Wallhaven API Key Exposed in Process Command-Line Arguments

Content
View full analysis
dict: proc = subprocess.run( ["curl", "-fsSL", url], capture_output=True, text=True, check=False, ) if proc.returncode != 0: raise RuntimeError(proc.stderr.strip() or f"curl failed: {proc.returncode}") return json.loads(proc.stdout) ``` ```python parser.add_argument("--apikey", default=os.environ.get("WALLHAVEN_API_KEY", "")) ``` ```python if args.apikey and "apikey" not in params: params["apikey"] = args.apikey safe_params = dict(params) if "apikey" in safe_params: safe_params["apikey"] = "***REDACTED***" try: validate_base_url(args.base_url) except ValueError as e: print(f"[error] invalid --base-url: {e}", file=sys.stderr) return 2 out_dir = Path(args.out) out_dir.mkdir(parents=True, exist_ok=True) requested = max(args.count, 0) if requested == 0: print("downloaded=0") return 0 page = int(params.get("page", "1")) ``` ```python query = dict(params) query["page"] = str(page) url = f"{args.base_url}?{urlencode(query)}" try: payload = fetch_json(url) ``` ### Technical Analysis The API key is inserted into the request query string. The resulting URL is then supplied directly as a command-line argument to a child `curl` process. Consequently, the credential may temporarily appear in process listings, process-monitoring systems, diagnostic tooling, audit records, or telemetry that captures command-line arguments. The documented `--apikey` invocation method also exposes the credential in the parent Python process's command line. Redacting the key in `manifest.json` prevents persistent disclosure through the manifest, but does not address command-line exposure. Sending the API key to Wallhaven i ...[truncated 1251 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/wallhaven_download.py:15
Finding

Redirects Followed Without Revalidating Destination Hosts

Content
View full analysis
dict: proc = subprocess.run( ["curl", "-fsSL", url], capture_output=True, text=True, check=False, ) if proc.returncode != 0: raise RuntimeError(proc.stderr.strip() or f"curl failed: {proc.returncode}") return json.loads(proc.stdout) def download_file(url: str, out_path: Path) -> None: proc = subprocess.run( ["curl", "-fsSL", url, "-o", str(out_path)], capture_output=True, text=True, check=False, ) if proc.returncode != 0: raise RuntimeError(proc.stderr.strip() or f"curl failed: {proc.returncode}") ``` ```python def validate_base_url(base_url: str) -> None: parsed = urlparse(base_url) if parsed.scheme != "https": raise ValueError("base-url must use https") if not parsed.hostname: raise ValueError("base-url must include hostname") host = parsed.hostname.lower() allowed_hosts = {"wallhaven.cc", "www.wallhaven.cc"} if host not in allowed_hosts: raise ValueError("base-url host is not allowed") if _is_private_or_local_host(host): raise ValueError("base-url resolves to private/local address") def validate_image_url(img_url: str) -> None: parsed = urlparse(img_url) if parsed.scheme != "https": raise ValueError("image url must use https") host = (parsed.hostname or "").lower() if host != "w.wallhaven.cc": raise ValueError("image host is not allowed") if _is_private_or_local_host(host): raise ValueError("image host resolves to private/local address") ``` ```python payload = fetch_json(url) ``` ```python download_file(img_url, target) ``` ### Techni ...[truncated 2202 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (14)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill declares execution requirements for shell commands, environment access, network use, and file writes, but does not declare an explicit tool scope or permission boundary. That creates an authorization gap where a broadly-invoked skill can perform higher-risk actions than a reviewer or policy engine may expect, increasing the chance of unintended downloads, filesystem writes, or secret use.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · SKILL.md (reported line 3)May include surrounding context.

md
---
name: wallhaven-downloader
description: Download wallpapers in batch from wallhaven.cc via API v1 with flexible query parameters (q, categories, purity, sorting, order, topRange, atleast, resolutions, ratios, colors, page, seed). Use when user asks to download one or many wallpapers from Wallhaven, especially with custom filters (e.g. purity/category/toplist/time range) and target folder requirements.
metadata: {"clawdbot":{"requires":{"commands":["python3","curl"],"env":["WALLHAVEN_API_KEY"]}}}
---

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · SKILL.md (reported line 20)May include surrounding context.

md
---
name: wallhaven-downloader
description: Download wallpapers in batch from wallhaven.cc via API v1 with flexible query parameters (q, categories, purity, sorting, order, topRange, atleast, resolutions, ratios, colors, page, seed). Use when user asks to download one or many wallpapers from Wallhaven, especially with custom filters (e.g. purity/category/toplist/time range) and target folder requirements.
metadata: {"clawdbot":{"requires":{"commands":["python3","curl"],"env":["WALLHAVEN_API_KEY"]}}}
---

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · SKILL.md (reported line 29)May include surrounding context.

md
---
name: wallhaven-downloader
description: Download wallpapers in batch from wallhaven.cc via API v1 with flexible query parameters (q, categories, purity, sorting, order, topRange, atleast, resolutions, ratios, colors, page, seed). Use when user asks to download one or many wallpapers from Wallhaven, especially with custom filters (e.g. purity/category/toplist/time range) and target folder requirements.
metadata: {"clawdbot":{"requires":{"commands":["python3","curl"],"env":["WALLHAVEN_API_KEY"]}}}
---

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · SKILL.md (reported line 53)May include surrounding context.

md
---
name: wallhaven-downloader
description: Download wallpapers in batch from wallhaven.cc via API v1 with flexible query parameters (q, categories, purity, sorting, order, topRange, atleast, resolutions, ratios, colors, page, seed). Use when user asks to download one or many wallpapers from Wallhaven, especially with custom filters (e.g. purity/category/toplist/time range) and target folder requirements.
metadata: {"clawdbot":{"requires":{"commands":["python3","curl"],"env":["WALLHAVEN_API_KEY"]}}}
---

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · SKILL.md (reported line 60)May include surrounding context.

md
---
name: wallhaven-downloader
description: Download wallpapers in batch from wallhaven.cc via API v1 with flexible query parameters (q, categories, purity, sorting, order, topRange, atleast, resolutions, ratios, colors, page, seed). Use when user asks to download one or many wallpapers from Wallhaven, especially with custom filters (e.g. purity/category/toplist/time range) and target folder requirements.
metadata: {"clawdbot":{"requires":{"commands":["python3","curl"],"env":["WALLHAVEN_API_KEY"]}}}
---

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · SKILL.md (reported line 87)May include surrounding context.

md
---
name: wallhaven-downloader
description: Download wallpapers in batch from wallhaven.cc via API v1 with flexible query parameters (q, categories, purity, sorting, order, topRange, atleast, resolutions, ratios, colors, page, seed). Use when user asks to download one or many wallpapers from Wallhaven, especially with custom filters (e.g. purity/category/toplist/time range) and target folder requirements.
metadata: {"clawdbot":{"requires":{"commands":["python3","curl"],"env":["WALLHAVEN_API_KEY"]}}}
---

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · SKILL.md (reported line 89)May include surrounding context.

md
---
name: wallhaven-downloader
description: Download wallpapers in batch from wallhaven.cc via API v1 with flexible query parameters (q, categories, purity, sorting, order, topRange, atleast, resolutions, ratios, colors, page, seed). Use when user asks to download one or many wallpapers from Wallhaven, especially with custom filters (e.g. purity/category/toplist/time range) and target folder requirements.
metadata: {"clawdbot":{"requires":{"commands":["python3","curl"],"env":["WALLHAVEN_API_KEY"]}}}
---

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/wallhaven_download.py (reported line 15)May include surrounding context.

python
def fetch_json(url: str) -> dict:
    proc = subprocess.run(
        ["curl", "-fsSL", url],
        capture_output=True,
        text=True,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/wallhaven_download.py (reported line 27)May include surrounding context.

python
def download_file(url: str, out_path: Path) -> None:
    proc = subprocess.run(
        ["curl", "-fsSL", url, "-o", str(out_path)],
        capture_output=True,
        text=True,

Vague Triggers

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

The invocation description is broad and can match many generic wallpaper-download requests without tight exclusion criteria. In an agent setting, that increases the likelihood of this skill being selected unexpectedly, which can trigger shell execution, network access, and local file writes when a simpler or safer response might have sufficed.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
83% confidence
Finding

The script accesses the WALLHAVEN_API_KEY environment variable as an alternate credential source. While the key is later redacted in the manifest, there is no docstring, comment, or user-facing notice explaining that the skill reads credentials from the environment.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
77% confidence
Finding

This code creates the output directory and later writes downloaded image files and a manifest.json file, which can modify the user's filesystem. Although there are some status prints, there is no docstring, comment, or explicit user-facing warning describing that the script will create directories and write multiple files to the specified path.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

The script issues HTTPS requests to the Wallhaven API and then downloads image files from a remote host using curl subprocesses. These network operations are central to the script's behavior, but the file itself lacks a clear user-facing warning or explanatory comment that it transmits query parameters and retrieves remote content.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.