Back to skill

Security audit

Smart Web Monitor (智能网页监控)

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent web monitor, but it needs review because it fetches arbitrary URLs and feeds untrusted page text to a command-capable cron agent without enough safeguards.

Install only if you are comfortable with a skill that can fetch user-configured URLs, create recurring monitoring tasks, write local monitor/report files, and send summaries to configured channels. Use it only with trusted public URLs, avoid authenticated or internal dashboards, and add strong confirmation and prompt-injection safeguards before scheduling LLM-based monitors.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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

Error
Location
scripts/monitor.py:47
Finding
Unrestricted URL Fetching Enables SSRF and Local Resource Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `scripts/monitor.py:47-55`, with attacker-controlled URLs accepted at `scripts/monitor.py:353-356` and `scripts/monitor.py:404-406` **Vulnerability Type**: Server-Side Request Forgery and local resource access **Risk Level**: High ### Vulnerable Code ```python def fetch_url(url: str, headers: dict = None) -> str: req_headers = {"User-Agent": USER_AGENT} if headers: req_headers.update(headers) req = Request(url, headers=req_headers) try: with urlopen(req, timeout=30) as resp: charset = resp.headers.get_content_charset() or 'utf-8' return resp.read().decode(charset, errors='replace') ``` The URL is supplied without validation: ```python p.add_argument("--url", required=True) ``` Additional URLs are also stored without validation: ```python def cmd_add_url(args): config = load_monitor(args.event) config["urls"].append({"url": args.url, "label": args.label or args.url}) save_monitor(config) print(f"✅ Added URL to '{args.event}'") ``` ### Technical Analysis The monitor passes a user-controlled URL directly to `urllib.request.Request` and `urlopen`. It does not restrict the scheme to HTTP or HTTPS, resolve and validate the destination address, block loopback or private address ranges, or validate redirect destinations. Consequently, a crafted monitor can request resources that are not intended to be exposed through the web-monitoring feature. Depending on the URL handlers and network environment available to the process, targets may include: - Local files through a supported local-file URL scheme. - Services listening on loopback interfaces. - Private or link-local network services. - Cloud instance metadata endpoints. - Internal HTTP applications inaccessible to the external attacker. The fetched response is subsequently printed by `cmd_fetch`, included in JSON output, passed to an LLM agent, or written into reports. This creates ...[truncated 1451 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Permit only explicitly supported `http` and `https` schemes. 2. Reject URLs containing credentials or ambiguous host representations. 3. Resolve the hostname before connecting and reject every address in loopback, private, link-local, multicast, reserved, unspecified, and other non-public ranges for both IPv4 and IPv6. 4. Block known cloud metadata destinations, including link-local metadata addresses and provider-specific metadata hostnames. 5. Disable automatic redirects or validate the scheme, hostname, and resolved address after every redirect. 6. Consider an administrator-controlled domain allowlist for scheduled monitors. 7. Apply strict response-size limits while streaming rather than reading the entire response before truncation. 8. Reject unsupported protocols and local-file handlers before constructing the request. 9. Apply the same validation in `create`, `add-url`, configuration loading, and immediately before each request. Validation only at creation time is insufficient because configuration files can be edited directly. 10. Run the monitor in a sandbox with minimal filesystem access and restricted outbound networking as defense in depth. ]]>

T01 · Skill Instruction Hijacking

Error
Location
SKILL.md:130
Finding
Untrusted Web Content Is Passed Directly to a Tool-Capable LLM Agent<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:130-136`, supported by the content extraction flow at `scripts/monitor.py:174-190` **Vulnerability Type**: Indirect prompt injection **Risk Level**: High ### Vulnerable Instructions and Code The documented cron integration instructs an agent to read and evaluate fetched page content, while the same agent is also instructed to execute a state-changing command: ```bash openclaw cron add \ --name "Monitor: hk-fintech-news" \ --cron "0 */3 * * *" \ --tz "Asia/Hong_Kong" \ --session isolated \ --message 'You are a web monitor agent. Run: cd /home/node/.openclaw/workspace/skills/web-monitor && python3 scripts/monitor.py fetch --event hk-fintech-news. Read the output JSON. For each URL, evaluate if the page content matches the condition: "是否有香港金融科技相关新闻?排除广告。" If matched: output a brief summary of what matched. If not matched: say "no match". If matched, also run: python3 scripts/monitor.py pause --event hk-fintech-news' \ --announce --channel discord --to "user:YOUR_ID" --light-context ``` The fetch command places arbitrary remote text directly into agent-consumed JSON: ```python def cmd_fetch(args): """Fetch URL(s) and output extracted text as JSON (for LLM processing).""" config = load_monitor(args.event) results = [] for url_entry in config["urls"]: url = url_entry["url"] label = url_entry.get("label", url) headers = url_entry.get("headers") html = fetch_url(url, headers=headers) if html: text = extract_text(html) results.append({ "url": url, "label": label, "text": text[:20000], # Cap at ~20k chars for LLM context "truncated": len(text) > 20000, "full_length": len(text) }) else: results.append({"url": url, "label": label, "text": "", "error": "fetch_failed"}) # Output as JSON for the agent ...[truncated 2613 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use a dedicated, tool-free model invocation for classification. The component reading page content should not have shell, filesystem, network, messaging, or monitor-management tools. 2. Add explicit higher-priority instructions that fetched content is untrusted data and that instructions, commands, role declarations, or requests found inside it must never be followed. 3. Enclose page text in clear structured boundaries and identify it as data, not instructions. 4. Require a strict machine-readable response schema, such as: ```json {"matched": false, "summary": "", "evidence": []} ``` 5. Validate the response with deterministic code before taking any action. 6. Move state changes such as pausing into trusted application logic. The classifier should return only a decision; it should never be asked to execute `monitor.py pause`. 7. Require evidence spans from the source content and reject decisions unsupported by those spans. 8. Remove or neutralize instruction-like content before model evaluation where practical, while recognizing that filtering alone cannot reliably prevent prompt injection. 9. Run scheduled monitoring in a sandbox with the smallest possible tool set and no access to secrets or unrelated workspace data. 10. Avoid announcing raw fetched content. Notifications should contain only validated, length-limited summaries and evidence. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (19)

Ae1

High
Category
analysis-evasion
Content
python3 scripts/monitor.py create \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/monitor.py create \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/monitor.py create \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/monitor.py create \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/monitor.py create \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/monitor.py create \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/monitor.py create \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/monitor.py create \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/monitor.py create \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python3 scripts/monitor.py create \
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill documents commands that perform network access and likely write monitor state/configuration, but it declares no explicit tool scope or allowed-tools boundaries. In an agent ecosystem, this can lead to the skill being invoked with broader-than-necessary capabilities, increasing the chance of unintended web access, persistent state changes, or misuse by downstream automation.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The description includes many broad trigger phrases such as 'monitor website', 'watch page', 'check for changes', and generic Chinese equivalents, which can cause accidental invocation in unrelated conversations. Because this skill performs network fetching and can modify monitor state or schedule recurring actions, an unintended trigger could lead to unauthorized monitoring, noisy automation, or persistent tasks being created without clear user intent.

Intent-Code Divergence

Medium
Confidence
93% confidence
Finding
The cmd_fetch docstring says fetched URL text is output as JSON for LLM processing, which frames the behavior as transient output. However, in the LLM run flow the script writes page text to reports/_pending_*.txt files for later processing, creating local persistence of monitored content rather than only outputting it.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
In LLM mode, fetched webpage text is saved to local files under reports without an explicit warning, consent flow, retention policy, or access controls. This can expose sensitive page contents, internal dashboards, personal data, or authenticated responses to other local users, backups, or downstream tooling, especially because this skill is designed to monitor arbitrary user-specified URLs.

Natural-Language Policy Violations

Low
Confidence
94% confidence
Finding
The natural-language matching instruction is written entirely in Chinese, which imposes a specific language/locale on the skill's evaluation behavior. There is no indication that users can choose the language or that the Chinese-only prompt is required for a documented region-specific purpose.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
This manifest contains user-facing natural language exclusively in Chinese, including the description and the LLM matching instruction. Under the policy rule for language/locale constraints, this can be a violation because it imposes a specific language without indicating user choice or documenting why Chinese is required.

Intent-Code Divergence

Low
Confidence
88% confidence
Finding
The module documentation suggests LLM handling is fully external to this script. In practice, the script contains explicit LLM-mode branching, marks matches as pending, includes page text in reports, and saves text chunks for later LLM processing, so the script participates materially in the LLM workflow.

Natural-Language Policy Violations

Low
Confidence
98% confidence
Finding
The script emits mandatory Chinese-only user-facing messages during monitor pause handling, and the report formatter also uses Chinese labels and instructions. This imposes a specific language on users without opt-in or locale selection, which is a natural-language policy violation under the stated rules.

Natural-Language Policy Violations

Low
Confidence
96% confidence
Finding
The generated markdown report includes Chinese-only fields such as descriptions, timestamps, match summaries, and resume instructions. Because the file provides no documented locale choice or justification for a Chinese-only workflow, it enforces a specific language by default.

Static analysis

No suspicious patterns detected.