Back to skill

Security audit

daily-fintech-brief

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its briefing purpose, but its crawler can follow untrusted links from source pages and bring that content into the agent and local storage.

Review before installing. Use it only in an environment where outbound requests to private/internal services are blocked, or harden the crawler with strict HTTPS host allowlists and redirect validation. Expect raw article JSON and permanent Markdown reports to be written under ~/.openclaw/workspace/skills/daily-fintech-brief, and treat fetched article text and cached reports as untrusted content.

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)

T01 · Skill Instruction Hijacking

Warning
Location
SKILL.md:42
Finding
Untrusted Web Content Is Passed Directly into the Agent Context<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:42-43`, `scripts/main.py:31-35, 82-90`, `scripts/fetch_and_store.py:48-52, 108-119, 238-244` **Vulnerability Type**: Indirect prompt injection through externally controlled article content **Risk Level**: Medium ### Vulnerable Code The Skill instructs the Agent to process all article content returned by the crawler: ```markdown ### Step 2: LLM 总结(由你执行) 仔细阅读爬虫传回的全部 Article 内容,按照以下要求进行深度提炼: ``` Both crawler implementations remove HTML markup but preserve arbitrary natural-language instructions: ```python def extract_pure_text(html_content): """通用正文提取:剥离脚本、样式表及HTML标签,留下高密度文本""" text = re.sub(r'<script[^>]*>([\s\S]*?)</script>', '', html_content) text = re.sub(r'<style[^>]*>([\s\S]*?)</style>', '', text) text = re.sub(r'<[^>]+>', ' ', text) text = re.sub(r'\s+', ' ', text).strip() return text[:2500] # 每篇详情页限制前 2500 字,防止大模型爆 Token ``` The extracted content is returned to the Agent without a trust boundary: ```python final_payload.append({ "source": src["title"], "title": item["title"], "content": pure_text }) ``` The storage-enabled implementation follows the same pattern: ```python def extract_pure_text(html_content): text = re.sub(r'<script[^>]*>([\s\S]*?)</script>', '', html_content) text = re.sub(r'<style[^>]*>([\s\S]*?)</style>', '', text) text = re.sub(r'<[^>]+>', ' ', text) text = re.sub(r'\s+', ' ', text).strip() return text[:2500] ``` ```python final_payload.append({ "source": src["title"], "title": item["title"], "content": pure_text, "url": item["url"] }) ``` ```python result = { "status": "success", "date": today, "article_count": len(articles), "articles": articles } print(json.dumps(result, ensure_ascii=False)) ``` ### Technical Analysis Article titles and bodies are controlled by external publishers or by anyone capable of compromising a source page. Removing scripts, styles, and HTML tags ...[truncated 2175 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Explicitly classify every fetched title, description, and article body as untrusted data. 2. Add a high-priority instruction stating that the Agent must never follow commands, policies, links, tool requests, or formatting directives found inside article content. 3. Enclose each article in a structured data boundary and ask the model to extract only predefined factual fields. 4. Perform summarization in an isolated model invocation that has no filesystem, network, shell, or other side-effecting tools. 5. Reject or flag instruction-like phrases in fetched content before presenting it to the Agent. 6. Preserve provenance for each extracted claim and require generated summaries to cite the corresponding source record. 7. Require explicit user approval before executing any tool call suggested by remotely fetched content. 8. Treat cached article content and existing generated reports as untrusted when loading them in later sessions. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fetch_and_store.py:96
Finding
Unrestricted Detail-Page URLs and Redirects Permit Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/main.py:68-77`, `scripts/fetch_and_store.py:96-109` **Vulnerability Type**: Missing URL, destination, and redirect validation **Risk Level**: Medium ### Vulnerable Code The crawler accepts any absolute URL beginning with `http` and later requests it: ```python for url, anchor_text in raw_links: clean_title = extract_pure_text(anchor_text) if len(clean_title) < 6 or not re.search(FILTER_KEYWORDS, clean_title): continue # 补全相对路径 URL full_url = url if url.startswith("http") else src["base_url"] + ("/" + url if not url.startswith("/") else url) if full_url in seen_urls: continue seen_urls.add(full_url) links_to_crawl.append({"title": clean_title, "url": full_url}) ``` ```python for item in links_to_crawl[:3]: detail_html = fetch_html(item["url"]) ``` The storage-enabled crawler contains the same behavior: ```python for url, anchor_text in raw_links: clean_title = extract_pure_text(anchor_text) if len(clean_title) < 6 or not re.search(FILTER_KEYWORDS, clean_title): continue full_url = url if url.startswith("http") else src["base_url"] + ("/" + url if not url.startswith("/") else url) if full_url in seen_urls: continue seen_urls.add(full_url) links_to_crawl.append({"title": clean_title, "url": full_url}) for item in links_to_crawl[:3]: detail_html = fetch_html(item["url"]) ``` The network helper performs the request through `urllib.request.urlopen`, which follows HTTP redirects by default: ```python def fetch_html(url): headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'} req = urllib.request.Request(url, headers=headers) try: with urllib.request.urlopen(req, timeout=8) as r: charset = r.headers.get_content_charset() or 'utf-8' return r.read().decode(charset, errors='ignore') except Exception as e: print(f"[WARN] fetch_h ...[truncated 2577 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse every URL with `urllib.parse.urlsplit` and permit only the `https` scheme. 2. Maintain an exact allowlist of approved hostnames for each configured source. 3. Reject URLs containing user information, unexpected ports, malformed hostnames, fragments, or unsupported schemes. 4. Resolve destination hostnames before connecting and reject loopback, private, link-local, multicast, unspecified, and reserved IPv4 and IPv6 addresses. 5. Disable automatic redirects or validate the scheme, hostname, port, and resolved address at every redirect hop. 6. Apply the same validation to source URLs, extracted detail links, and final redirect destinations. 7. Prefer same-origin relative links and reject absolute links when cross-origin navigation is not required. 8. Add response-size and content-type limits to reduce resource-exhaustion and binary-content risks. 9. Log rejected destinations without including credentials or sensitive URL components. 10. Consolidate the duplicated crawler logic into one hardened implementation so security checks cannot diverge between scripts. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (8)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill's high-level description emphasizes reviewing curated fintech/AI sources, but the instructions also direct automatic local persistence of fetched raw data and permanent storage of generated reports. That hidden or under-emphasized behavior changes the privacy and data-handling profile of the skill, creating a risk that users trigger network collection and durable filesystem writes without informed consent.

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill automatically fetches remote content and stores both collected source material and generated reports locally, but it does not provide a clear user-facing warning at the point of use. This is dangerous because users may unknowingly initiate external requests and persistent storage, which can create privacy, compliance, and data-governance issues in enterprise environments.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The primary natural-language description of the skill is entirely in Chinese, which imposes a specific language expectation on operators or maintainers without any opt-in, alternative language, or stated region-specific justification. Under the policy, language constraints should either be optional or clearly documented as necessary.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The skill persists fetched content to local storage and deletes files on disk even though its stated purpose is only briefing/review generation. In an agent-skill context, undeclared filesystem side effects reduce transparency, expand the trust boundary, and can surprise operators or downstream components that expect read-only behavior. The impact is limited here because writes are scoped to a fixed skill-local directory and cleanup only targets date-named JSON files in that directory.

Natural-Language Policy Violations

Medium
Confidence
85% confidence
Finding
The natural-language comments and descriptions indicate the skill is explicitly designed around Chinese-language sources and keyword filtering, such as Chinese-only source titles and filter terms. Because the file does not document any user opt-in or language selection, this can constitute a locale/language policy issue if the skill is expected to be language-neutral.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The trigger words and overall skill framing are specified entirely in Chinese, and the document presents a fixed Chinese-language report format without indicating that users may choose another language. This can be a language/locale policy concern when no opt-in or alternative language behavior is documented.

Intent-Code Divergence

Low
Confidence
88% confidence
Finding
The top-level documentation says the script performs data fetching, local storage, automatic cleanup, and returns JSON for the next step. In practice, main() may skip fetching entirely by returning an existing report or by serving yesterday's cached raw data, which contradicts the stated behavior of grabbing source data for this run.

Static analysis

No suspicious patterns detected.