Back to skill

Security audit

mac-wallpaper-changer

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed Mac wallpaper tool, but it does download images, change desktop settings, and store local preference data.

Install only if you want an agent-run script to download remote images, change your macOS wallpaper, restart wallpaper-related UI processes, and keep wallpaper ratings/logs under ~/wallpaper-daily/. Avoid using untrusted --url values, review the uv dependencies in your environment, and add cron only if you intentionally want scheduled wallpaper changes.

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

Warning
Location
scripts/change.py:297
Finding

Unrestricted URL Retrieval Enables SSRF and Local Resource Access

Content
View full analysis
bool: """Download an image.""" try: headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' } req = urllib.request.Request(url, headers=headers) with urllib.request.urlopen(req, timeout=30) as response: with open(filepath, 'wb') as f: f.write(response.read()) return True except Exception as e: log_message(f"Download failed: {e}", "ERROR") return False ``` The function is reachable through the user-controlled `--url` command-line option: ```python parser.add_argument("--url", type=str, help="Directly set wallpaper from URL") if args.url: log_message(f"Setting wallpaper from URL: {args.url}") filename = f"wallpaper-{datetime.now().strftime('%Y%m%d-%H%M%S')}.jpg" today_dir = WALLPAPER_DIR / datetime.now().strftime("%Y-%m-%d") today_dir.mkdir(exist_ok=True) filepath = today_dir / filename if download_image(args.url, filepath): size = filepath.stat().st_size log_message(f"Download successful: {filepath} ({size // 1024} KB)") if set_wallpaper_all_displays(filepath): log_message("Wallpaper updated successfully from URL") return 0 ``` ### Technical Analysis The `--url` value is passed directly to `urllib.request.Request` and `urllib.request.urlopen` without validation of: - The URL scheme. - The destination hostname or resolved IP address. - Redirect destinations. - Whether the destination is loopback, link-local, private, or reserved. - The response MIME type or actual file format. - The response size. - Whether the URL refers to a local file rather than a re ...[truncated 2550 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
scripts/change.py:1
Finding

Runtime Dependency Resolution Uses Broad, Unlocked Version Constraints

Content
View full analysis
=3.12,<3.13" # dependencies = [ # "polars>=1.0.0", # "duckdb>=1.0.0", # "pyobjc-framework-Cocoa>=10.0", # ] # /// ``` The same lower-bound-only dependency constraints appear in `change.py`, `daily.py`, `preference.py`, and `recommend.py`. ### Technical Analysis The documented execution method uses `uv run`, allowing dependencies to be resolved when a script is executed. Every package uses only a minimum version constraint: - `polars>=1.0.0` - `duckdb>=1.0.0` - `pyobjc-framework-Cocoa>=10.0` There is no audited lockfile or artifact hash in the reviewed project. Future package releases that satisfy these constraints may therefore be selected without a corresponding source-code change in the Skill. This weakens reproducibility and creates a supply-chain trust boundary at execution time. A compromised upstream release, package repository, account, build pipeline, or configured package index could cause unreviewed code to execute under the user's account. The audit did not identify a currently malicious named dependency. The vulnerability is the absence of controls ensuring that future executions use the same dependency artifacts that were reviewed. ### Attack Path 1. A user invokes one of the documented commands, such as `uv run scripts/change.py`. 2. `uv` resolves packages satisfying the broad minimum-version constraints. 3. A newer or compromised package version is selected from the configured package index. 4. The package and any required build artifacts are downloaded. 5. Dependency code is ...[truncated 1116 chars]
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (23)

Tainted flow: 'req' from os.environ.get (line 230, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/change.py (reported line 133)May include surrounding context.

python
req = urllib.request.Request(api_url, headers={
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)'
        })
        with urllib.request.urlopen(req, timeout=10) as response:
            data = json.loads(response.read().decode())

        if data.get("images"):

Tainted flow: 'req' from os.environ.get (line 230, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/change.py (reported line 178)May include surrounding context.

python
req = urllib.request.Request(api_url, headers={
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)'
        })
        with urllib.request.urlopen(req, timeout=10) as response:
            data = json.loads(response.read().decode())

        if data.get("images"):

Tainted flow: 'req' from os.environ.get (line 230, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/change.py (reported line 234)May include surrounding context.

python
'Authorization': f'Client-ID {UNSPLASH_ACCESS_KEY}',
                'User-Agent': 'Mozilla/5.0'
            })
            with urllib.request.urlopen(req, timeout=15) as response:
                data = json.loads(response.read().decode())

            if data.get("urls"):

Tainted flow: 'req' from os.environ.get (line 230, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

The script downloads arbitrary content from a user-supplied URL (--url) and saves it locally without validating scheme, host, content type, size, or redirect target. In an agent context, this can be abused for SSRF-like access to internal services or local resources via file:/other URL handlers, and for writing unexpected content to disk under the user's account.

Content

Scanner excerpt · scripts/change.py (reported line 300)May include surrounding context.

python
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
        }
        req = urllib.request.Request(url, headers=headers)
        with urllib.request.urlopen(req, timeout=30) as response:
            with open(filepath, 'wb') as f:
                f.write(response.read())
        return True

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill advertises capabilities that imply shell, filesystem, environment, and network access, but it does not declare any explicit tool scope or permission boundaries. This makes it easier for an agent runtime to invoke the skill with broader privileges than necessary, increasing the blast radius if the skill is misused, modified, or triggered unexpectedly.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The activation text says wallpaper-related requests should '优先使用此技能', which is overly broad and can cause the skill to trigger for many loosely related prompts. Overbroad routing increases the chance of unintended execution of side-effecting actions like changing wallpaper, writing local files, or scheduling automation when the user only wanted information or alternatives.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The natural-language description and instructions are written in Chinese, while also including English trigger examples, but there is no statement that the skill supports multiple languages or that Chinese is a justified locale-specific requirement. This can amount to an implicit language policy constraint without user opt-in.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The skill documents commands that directly change the desktop background, write under ~/wallpaper-daily/, and support cron-based automation, but it does not clearly warn users that these actions modify system state and create persistent local data. In an agent setting, missing disclosure and confirmation around side effects can lead to surprising system changes and persistence beyond the current session.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The module docstring states the priority order is Unsplash first, Bing second, and Picsum third, while get_bing_wallpaper is separately documented as '优先级 1' at L118 and get_unsplash_wallpaper as '优先级 2' at L208. The actual selection logic in get_random_wallpaper at L510-L523 uses Unsplash first, then Bing, then Picsum, so the inline documentation is internally contradictory to the implemented behavior.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/wallpaper-sources.md (reported line 87)May include surrounding context.

md
"orientation": "landscape",
                "content_filter": "high"
            })
            api_url = f"https://api.unsplash.com/photos/random?{params}"
            req = urllib.request.Request(api_url, headers={
                'Authorization': f'Client-ID {UNSPLASH_ACCESS_KEY}',
                'User-Agent': 'Mozilla/5.0'

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/wallpaper-sources.md (reported line 141)May include surrounding context.

md
"orientation": "landscape",
                "content_filter": "high"
            })
            api_url = f"https://api.unsplash.com/photos/random?{params}"
            req = urllib.request.Request(api_url, headers={
                'Authorization': f'Client-ID {UNSPLASH_ACCESS_KEY}',
                'User-Agent': 'Mozilla/5.0'

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/change.py (reported line 229)May include surrounding context.

python
"orientation": "landscape",
                "content_filter": "high"
            })
            api_url = f"https://api.unsplash.com/photos/random?{params}"
            req = urllib.request.Request(api_url, headers={
                'Authorization': f'Client-ID {UNSPLASH_ACCESS_KEY}',
                'User-Agent': 'Mozilla/5.0'

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/change.py (reported line 323)May include surrounding context.

python
time.sleep(0.3)

        # macOS Tahoe: 重启 WallpaperAgent 让壁纸生效
        result = subprocess.run(
            ['killall', 'WallpaperAgent'],
            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/change.py (reported line 335)May include surrounding context.

python
else:
            # WallpaperAgent 可能不存在(旧版系统),回退到 Dock
            log_message("WallpaperAgent 未找到,尝试刷新 Dock", "WARN")
            result2 = subprocess.run(
                ['killall', 'Dock'],
                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/change.py (reported line 392)May include surrounding context.

python
# 方法 2:desktoppr(回退方案,需 brew install desktoppr)
    try:
        result = subprocess.run(
            ['desktoppr', abs_path],
            capture_output=True,
            text=True,

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The file's natural-language description states '用户配置 > 默认上海' and instructs users to change the city only if they choose, which means the skill imposes a specific locale by default. This is a language/locale policy concern because the locale is forced unless the user explicitly overrides it, and the file does not present this as an opt-in or justified region-specific constraint.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
85% confidence
Finding

The script's title, descriptions, prompts, and CLI help are presented in Chinese, which imposes a single language on users. There is no indication that the tool is region-specific or that users can opt into another language, matching the locale-policy concern for natural-language content.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The file’s docstring and many user-facing messages are written only in Chinese, indicating the skill assumes a fixed language for interaction. The policy for this category requires flagging language or locale constraints when the user is not given an explicit choice or opt-in.

Content

No source excerpt is available for this finding.

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/recommend.py (reported line 195)May include surrounding context.

python
# macOS Tahoe: 重启 WallpaperAgent 让壁纸生效
            import time
            time.sleep(0.3)
            subprocess.run(['killall', 'WallpaperAgent'], capture_output=True, timeout=10)
            return True
            
    except ImportError:

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/recommend.py (reported line 207)May include surrounding context.

python
# macOS Tahoe: 重启 WallpaperAgent 让壁纸生效
            import time
            time.sleep(0.3)
            subprocess.run(['killall', 'WallpaperAgent'], capture_output=True, timeout=10)
            return True
            
    except ImportError:

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/recommend.py (reported line 205)May include surrounding context.

python
# 方法 2: desktoppr 回退
    try:
        result = subprocess.run(['desktoppr', abs_path], capture_output=True, timeout=30)
        if result.returncode == 0:
            subprocess.run(['killall', 'WallpaperAgent'], capture_output=True, timeout=10)
            return True

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

This markdown file uses Chinese as the sole instruction language in the title and throughout the document. Under the stated policy, forcing a specific language without user opt-in can be a natural-language policy violation unless the locale restriction is documented and justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
97% confidence
Finding

The file’s natural-language interface is written in Chinese, including the module description and later user-facing prompts/log messages, with no indication that users can select another language. This can violate language/locale policy when a skill imposes a specific language without user opt-in or documented regional scope.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.