Back to skill

Security audit

Price Watcher

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent price watcher, but it can persistently fetch any stored URL and optionally send alert contents to Discord without enough scoping or warning.

Install only if you are comfortable with a local script storing product URLs and price history, fetching those URLs repeatedly, and optionally sending formatted alerts to Discord. Avoid adding internal, localhost, metadata-service, credential-bearing, or untrusted URLs, and review webhook output before enabling scheduled notifications.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/add_product.py:96
Finding

Unrestricted URL Fetching Enables Server-Side Request Forgery

Content
View full analysis
= 3: try: manual_price = float(sys.argv[2]) except ValueError: print(f"[ERROR] Price must be a number, got: {sys.argv[2]}") sys.exit(1) if len(sys.argv) >= 4: manual_name = sys.argv[3] price = manual_price name = manual_name if price is None or name is None: print(f"[INFO] Fetching page: {url}") html, err = fetch_page(url) ``` ```python # scripts/check_prices.py:225-234 for i, product in enumerate(products): url = product["url"] name = product["name"] old_price = product.get("last_price") or product.get("baseline_price") if i > 0: time.sleep(1) # polite delay between requests print(f"[INFO] Checking: {name}", file=sys.stderr) html, err = fetch_page(url) ``` ### Technical Analysis Both scripts pass user- ...[truncated 2495 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/add_product.py:96
Finding

Unbounded HTTP Response Read Can Exhaust Process Memory

Content
View full analysis
Remediation
View remediation
MAX_RESPONSE_SIZE: return None, "Response exceeds maximum allowed size" ``` 3. Check `Content-Length` before reading, while retaining the runtime limit because the header may be absent or dishonest. 4. Stream the response in bounded chunks rather than allowing an unrestricted allocation. 5. Accept only expected content types such as `text/html` and `application/xhtml+xml`. 6. Apply consistent response-size, timeout, and content-type controls in both fetching scripts. 7. Consider process-level memory and CPU limits for scheduled or agent-triggered executions. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/format_alert.py:51
Finding

Untrusted Product Metadata Is Rendered as Raw Markdown

Content
View full analysis
tag.""" m = re.search(r']*>(.*?)', html, re.IGNORECASE | re.DOTALL) if m: title = m.group(1).strip() # Strip site suffixes like "| Amazon" or "- Best Buy" title = re.split(r'\s*[|–—-]\s*(?:Amazon|eBay|Walmart|Best Buy|Target|Costco|Newegg)', title)[0] return title[:120].strip() return None ``` ```python # scripts/add_product.py:172 name = extract_title_from_html(html) or url ``` ```python # scripts/format_alert.py:51-82 for item in sorted(drops, key=lambda x: x.get("change_pct", 0)): lines.append(f"### {item['name']}") lines.append(f"- **Was:** {format_price(item.get('old_price'))}") lines.append(f"- **Now:** {format_price(item.get('new_price'))}") lines.append(f"- **Change:** {format_change(item.get('change_amount'), item.get('change_pct'))}") lines.append(f"- **Link:** [{item['url']}]({item['url']})") lines.append("") if increases: lines.append("## 📈 Price Increases") lines.append("") for item in sorted(increases, key=lambda x: x.get("change_pct", 0), reverse=True): lines.append(f"### {item['name']}") lines.append(f"- **Was:** {format_price(item.get('old_price'))}") lines.append(f"- **Now:** {format_price(item.get('new_price'))}") lines.append(f"- **Change:** {format_change(item.get('change_amount'), item.get('change_pct'))}") lines.append(f"- **Link:** [{item['url']}]({item['url']})") lines.append("") if errors: lines.append("## ⚠️ Check Errors") lines.append("") for item in errors: status = item.get("status", "error") err_msg = item.get("e ...[truncated 2330 chars]
Remediation
View remediation
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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · references/setup-guide.md (reported line 45)May include surrounding context.

python3 scripts/format_alert.py >> /tmp/price-alerts.md 2>/tmp/price-watcher.log

text

Or to send results to Discord via curl (replace WEBHOOK_URL):

```cron
0 9 * * * cd /Users/openclaw/.openclaw/workspace/skills/price-watcher && \

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger phrases are broad enough to match common shopping or product-related requests, which can cause the skill to activate when the user did not explicitly consent to starting persistent monitoring. In this skill, activation can lead to local state changes and outbound fetching of arbitrary product URLs, so overbroad routing increases the chance of unintended file modification and network access.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The guide includes a cron example that posts generated output to a Discord webhook but does not clearly warn users that this sends skill-produced data to an external third-party service. Even if the payload appears limited to price-watch results, product names, URLs, and other derived content may be transmitted off-host, creating an avoidable data disclosure risk through documentation-driven configuration.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
88% confidence
Finding

This command sends the formatted results to the URL stored in DISCORD_WEBHOOK, which is an external network transmission of locally generated data. In context this is intentional functionality rather than covert exfiltration, but it still creates a real outbound data path that could disclose tracked products, pricing history, or injected content from watched pages to an external service.

Content

Scanner excerpt · references/setup-guide.md (reported line 50)May include surrounding context.

cron
0 9 * * * cd /Users/openclaw/.openclaw/workspace/skills/price-watcher && \
  RESULT=$(python3 scripts/check_prices.py | python3 scripts/compare.py | python3 scripts/format_alert.py) && \
  [ -n "$RESULT" ] && curl -s -X POST "$DISCORD_WEBHOOK" \
    -H "Content-Type: application/json" \
    -d "{\"content\": \"\`\`\`\n$RESULT\n\`\`\`\"}"

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

The skill explicitly states that it updates watchlist.json, but it does not prominently warn the user that running the workflow will modify local data. This can lead to surprising persistence of tracked URLs, product names, and pricing history, especially when the user may expect a read-only check.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The request headers hard-code "Accept-Language: en-US,en;q=0.9", which imposes a specific language/locale preference regardless of user choice. This is a natural-language policy concern because the skill does not offer any opt-in, override, or justification for forcing English.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

The HTTP request hard-codes the Accept-Language header to "en-US,en;q=0.9", which imposes a specific language/locale preference. This is a natural-language policy concern because the skill does not offer user opt-in or explain why English is required.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.