Back to skill

Security audit

Web Fetcher

Security checks for vulnerabilities and agentic risk

Overview

This skill is a real web/video downloader, but it can fetch arbitrary URLs and reuse browser login cookies without strong warnings or scope limits.

Review before installing. Use it only with URLs you trust, avoid --cookies-browser unless you understand it can use your logged-in browser sessions, run it in an isolated environment or dedicated browser profile, and constrain output directories and network access where possible.

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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
lib/router.py:36
Finding
Unrestricted URL Fetching Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `lib/router.py:36-51`, `lib/article.py:74-83`, `lib/article.py:101-110`, `lib/article.py:262-284` **Vulnerability Type**: Server-Side Request Forgery (SSRF) **Risk Level**: High ### Vulnerable Code `lib/router.py:36-51`: ```python def route(url): """Parse URL and return routing config dict.""" parsed = urlparse(url) domain = parsed.hostname or "" # Exact match if domain in ROUTE_TABLE: return dict(ROUTE_TABLE[domain]) # Subdomain matching (e.g., *.feishu.cn) for key, config in ROUTE_TABLE.items(): if domain.endswith("." + key): return dict(config) return dict(_DEFAULT) ``` `lib/article.py:74-83`: ```python cmd_md = ["scrapling", "extract", "get", url, md_file] cmd_html = ["scrapling", "extract", "get", url, html_file] if selector: cmd_md += ["-s", selector] cmd_html += ["-s", selector] print(f"[*] Scrapling GET: {url}") r1 = subprocess.run(cmd_md, capture_output=True, text=True, timeout=60) r2 = subprocess.run(cmd_html, capture_output=True, text=True, timeout=60) ``` `lib/article.py:262-284`: ```python def _download_image(url, local_path, referer=None): """Download image with appropriate headers. Returns True on success.""" try: req = urllib.request.Request(url) req.add_header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36") if referer: req.add_header("Referer", referer) with urllib.request.urlopen(req, timeout=30) as resp: data = resp.read() if len(data) < 100: # Too small, likely an error return False with open(local_path, "wb") as f: f.write(data) return True except Exception as e: print(f"[!] Image download failed: {url} - {e}") return False ``` ### Technical Analysis The routing function accepts arbitrary URL input and routes every unreco ...[truncated 2567 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Permit only explicit `http` and `https` schemes. 2. Require a nonempty hostname and reject embedded credentials and malformed ports. 3. Resolve the hostname before connecting and reject every address in loopback, private, link-local, reserved, multicast, and unspecified ranges. 4. Explicitly deny known metadata destinations, including link-local metadata addresses. 5. Disable redirects or validate the scheme, hostname, DNS result, and resolved IP after every redirect. 6. Protect against DNS rebinding by connecting only to previously validated resolved addresses where supported. 7. Apply the same validation to initial article URLs, image URLs, and every browser navigation or subresource-fetch path. 8. Consider an allowlist of supported public platforms rather than applying a generic fetcher to every unknown host. 9. For generic article images, enforce an origin allowlist or require explicit user approval before contacting a different host. 10. Run browser and downloader components inside a sandbox with outbound network restrictions that prevent access to internal and metadata networks. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
lib/article.py:262
Finding
Unbounded Remote Resource Processing Can Exhaust Memory and Disk<![CDATA[ ## Vulnerability Details **File Location**: `lib/article.py:262-284`, `lib/feishu.py:103-114`, `lib/feishu.py:162-171` **Vulnerability Type**: Uncontrolled Resource Consumption **Risk Level**: Medium ### Vulnerable Code `lib/article.py:262-284`: ```python def _download_image(url, local_path, referer=None): """Download image with appropriate headers. Returns True on success.""" try: req = urllib.request.Request(url) req.add_header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36") if referer: req.add_header("Referer", referer) with urllib.request.urlopen(req, timeout=30) as resp: data = resp.read() if len(data) < 100: # Too small, likely an error return False with open(local_path, "wb") as f: f.write(data) return True except Exception as e: print(f"[!] Image download failed: {url} - {e}") return False ``` `lib/feishu.py:103-114`: ```python b64_data = page.evaluate(""" async (url) => { try { const resp = await fetch(url, {credentials: 'include'}); const blob = await resp.blob(); return new Promise((resolve) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.readAsDataURL(blob); }); } catch(e) { return null; } } """, img_url) ``` `lib/feishu.py:162-171`: ```python match = re.match(r'data:image/(\w+);base64,(.+)', b64_data) if match: ext = match.group(1) if ext == "jpeg": ext = "jpg" img_data = base64.b64decode(match.group(2)) local_name = f"img_{i:02d}.{ext}" local_path = os.path.join(img_dir, local_name) with open(local_path, "wb") as f: f.write(img_data) ``` ### Technical Analysis The generic image downloader calls `resp.read()` without a byte limit. The entire ...[truncated 1894 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Stream response bodies in fixed-size chunks instead of using an unrestricted `read()`. 2. Enforce a conservative maximum size per resource and abort once the limit is exceeded. 3. Enforce an aggregate byte quota and maximum image count for each Skill invocation. 4. Reject a response before reading when a valid `Content-Length` exceeds the configured limit. 5. Treat missing or untrusted `Content-Length` as requiring strict streamed-byte accounting. 6. Validate the response MIME type against an allowlist of supported image formats. 7. Verify image signatures rather than trusting the URL extension or data-URL media type. 8. Replace Feishu data-URL conversion with a bounded streaming download where the browser API permits it. 9. If base64 transfer is unavoidable, check the encoded string length before decoding and cap the decoded output. 10. Use temporary files and atomic renames so interrupted downloads do not leave misleading final files. 11. Apply process-level memory, disk, and execution-time limits to browser and downloader subprocesses. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:25
Finding
Unpinned Third-Party Dependencies and Browser Assets Create Supply-Chain Risk<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:25-34`, `README.md:84-93`, `lib/router.py:52-71` **Vulnerability Type**: Unpinned and Unverified Dependencies **Risk Level**: Medium ### Vulnerable Code and Instructions `SKILL.md:25-34`: ```markdown ## Install Dependencies Install only what you need — dependencies are checked at runtime: | Dependency | Purpose | Install | |-----------|---------|---------| | scrapling | Article fetching (HTTP + browser) | `pip install scrapling` | | yt-dlp | Video download | `pip install yt-dlp` | | camoufox | Anti-detection browser (Xiaohongshu, Weibo) | `pip install camoufox && python3 -m camoufox fetch` | | html2text | HTML to Markdown conversion | `pip install html2text` | ``` `README.md:84-93`: ```markdown ## Dependencies Install only what you need: ```bash pip install scrapling # Article fetching pip install yt-dlp # Video downloads pip install html2text # HTML→Markdown (needed for camoufox/feishu) pip install camoufox # Anti-bot sites (Xiaohongshu, Weibo) python3 -m camoufox fetch # Download camoufox browser ``` ``` `lib/router.py:52-71`: ```python def check_dependency(name): """Check if a dependency is available, print install hint if not. Returns bool.""" hints = { "scrapling": "pip install scrapling", "yt-dlp": "pip install yt-dlp", "camoufox": "pip install camoufox && python3 -m camoufox fetch", "html2text": "pip install html2text", } if name == "yt-dlp": if shutil.which("yt-dlp"): return True print(f"[!] yt-dlp not found. Install: {hints[name]}") return False try: __import__(name) return True except ImportError: hint = hints.get(name, f"pip install {name}") print(f"[!] {name} not found. Install: {hint}") return False ``` ### Technical Analysis The project instructs users to install current package releases by name without: - Exa ...[truncated 1920 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin all direct and transitive dependencies to reviewed versions. 2. Generate a lockfile containing cryptographic hashes for every distribution artifact. 3. Install with hash enforcement, such as `pip install --require-hashes -r requirements.txt`. 4. Use a trusted, explicitly configured package index or an internally controlled package mirror. 5. Pin Camoufox browser assets to a reviewed version and verify checksums or signatures before use. 6. Document a controlled dependency-update process that includes vulnerability scanning and code review. 7. Run installation and browser components in isolated virtual environments or containers. 8. Avoid granting dependency processes access to unrelated browser profiles, credentials, or sensitive host directories. 9. Record and expose installed dependency versions in diagnostic output to support reproducibility and incident response. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (21)

YARA rule 'info_stealer': Information stealer patterns (credential harvesting, browser data theft) [malware]

High
Category
YARA Match
Content
--quality N        Video quality: 1080, 720, 480 (default: 1080)
  --method METHOD        Force method: scrapling, camoufox, ytdlp, feishu
  --selector CSS         Force CSS selector for content extraction
  --urls-file FILE       File with URLs (one per line, # for comments)
  --audio-only           Extract audio only (video downloads)
  --no-images            Skip image download (articles)
  --cookies-browser NAME Browser for cookies (e.g., chrome, firefox)
```

## How It Works

```
URL → router.py (domain matching) → handler
                                      ├── article.py (scrapling GET → browser → camoufox)
                                      ├── feishu.py  (virtual scroll + authenticated image fetch)
                                      └── video.py   (yt-dlp wrapper)
```

Articles are converted to Markdown with images downloaded locally. Videos are downloaded as MP4 (or MP3 with `--audio-only`).

## Dependencies

Install only what you need:

```
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The declared description presents the skill as a web fetcher/downloader for articles and videos across multiple services. However, the supplied code chunk is the Mozilla/Arc90-style Readability parser: it accepts an HTMLDocument object and extracts readable article content and metadata from that document. Its functions focus on DOM cleanup, content scoring, metadata extraction, image/lazy-load handling, and preserving embedded video elements when cleaning content. There is no code for HTTP requests, URL fetching, authentication, platform integration, saving content, or media/video downloading. Therefore the code’s actual behavior is a content-extraction helper, not a web-fetching/downloading skill as declared.

YARA rule 'info_stealer': Information stealer patterns (credential harvesting, browser data theft) [malware]

High
Category
YARA Match
Content
--quality N        Video quality, e.g. 1080, 720 (default: 1080)
  --method METHOD        Force method: scrapling, camoufox, ytdlp, feishu
  --selector CSS         Force CSS selector for content extraction
  --urls-file FILE       File with URLs (one per line, # for comments)
  --audio-only           Extract audio only (video downloads)
  --no-images            Skip image download (articles)
  --cookies-browser NAME Browser for cookies (e.g., chrome, firefox)
```

## Platform Notes

### WeChat (mp.weixin.qq.com)
- Images use `data-src` attribute with `mmbiz.qpic.cn` URLs
- Visible `<img>` tags contain SVG placeholders (lazy loading)
- Image download requires `Referer: https://mp.weixin.qq.com/` header
- Scrapling GET usually works; no browser needed

### Feishu (*.feishu.cn)
- Uses virtual scroll — content blocks are rendered on-demand
- The fetcher scrolls through the entire document, collecting `[data-block-id]` elements
- Images require authenticated fetch (cookies), downloaded v
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

YARA rule 'info_stealer': Information stealer patterns (credential harvesting, browser data theft) [malware]

High
Category
YARA Match
Content
lp", "feishu"], help="Force method")
    parser.add_argument("--selector", help="Force CSS selector")
    parser.add_argument("--urls-file", help="File with URLs (one per line)")
    parser.add_argument("--audio-only", action="store_true", help="Extract audio only (video)")
    parser.add_argument("--no-images", action="store_true", help="Skip image download (article)")
    parser.add_argument("--cookies-browser", help="Browser for cookies (e.g., chrome, firefox)")
    args = parser.parse_args()

    urls = []
    if args.urls_file:
        with open(args.urls_file) as f:
            urls = [line.strip() for line in f if line.strip() and not line.startswith("#")]
    elif args.url:
        urls = [args.url]
    else:
        parser.print_help()
        sys.exit(1)

    os.makedirs(args.output, exist_ok=True)

    results = []
    for url in urls:
        r = route(url)
        if args.method:
            r["method"] = args.method
        if args.selector:
            r["selector"] = ar
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
titleHadHierarchicalSeparators = / [\\\/>»] /.test(curTitle);
      curTitle = origTitle.replace(/(.*)[\|\-\\\/>»] .*/gi, "$1");

      // If the resulting title is too short (3 words or fewer), remove
      // the first part instead:
      if (wordCount(curTitle) < 3)
        curTitle = origTitle.replace(/[^\|\-\\\/>»]*[\|\-\\\/>»](.*)/gi, "$1");
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

YARA rule 'info_stealer': Information stealer patterns (credential harvesting, browser data theft) [malware]

High
Category
YARA Match
Content
- **Selector:** `.WB_text`
- **Method:** camoufox (recommended for reliable access)
- **Notes:** Some content requires login

## Bilibili (bilibili.com / b23.tv)

- **Method:** yt-dlp
- **Format selection:** `bestvideo[height<=N]+bestaudio/best[height<=N]`
- **Output:** Merged to MP4 via ffmpeg
- **Short links:** b23.tv redirects are handled by yt-dlp automatically
- **Premium content:** Use `--cookies-browser chrome` to pass login cookies
- **Audio extraction:** Use `--audio-only` flag for MP3 output

## YouTube (youtube.com / youtu.be)

- **Method:** yt-dlp
- **Notes:** Standard yt-dlp usage, no special handling

## Douyin (douyin.com)

- **Method:** yt-dlp
- **Notes:** May need cookies for some content
Confidence
83% confidence
Finding
The `cookies-browser chrome` pattern matches information-stealer behavior because it directs tooling to read authenticated cookies from the user's Chrome profile. In this skill context, that is especially sensitive: the agent is a web/video fetcher, so leveraging local browser credentials can enable access to private or premium content and normalize credential-adjacent data access beyond the expected scope.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The README explicitly documents authenticated fetching and browser-cookie use, including Feishu authenticated image access, but does not warn users that the tool may access logged-in session data and private content. In a web-fetching skill, this increases the risk of unintentionally pulling sensitive account data or normalized developer acceptance of session-backed scraping without consent boundaries.

Session Persistence

Medium
Category
Rogue Agent
Content
## Adding New Platforms

1. Add entry to `ROUTE_TABLE` in `lib/router.py`
2. If needed, add image post-processing hook in `lib/article.py`
3. If needed, create dedicated handler in `lib/`
Confidence
80% 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.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill advertises and documents capabilities that require network, filesystem, and shell access, but the manifest does not declare any explicit tool scope or permission boundaries. This increases the chance of overbroad execution in an agent environment and makes it harder for users or reviewers to understand what the skill is allowed to do.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger phrases are broad enough to match many ordinary user requests, which can cause the skill to activate in contexts where users did not intend web fetching or downloading. In an agent system with network and file-write capability, over-triggering can lead to unnecessary external requests, data collection, or local file creation.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill explicitly documents use of authenticated browser cookies to access and download content, but it does not present a clear warning about privacy, account scope, or the sensitivity of browser-derived session data. This is dangerous because users may unknowingly authorize access to private or account-restricted content, and cookie-backed requests can expose personal data or violate least-privilege expectations.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The `--cookies-browser` option enables the tool to read authentication cookies from a local browser profile, but the interface gives no explicit warning that sensitive browser-stored session data will be accessed and then potentially used against arbitrary user-supplied URLs. In a web-fetching skill, this materially increases the risk of unintended authenticated requests, privacy exposure, or misuse of a logged-in session if users do not understand what the flag does.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd_html += ["-s", selector]

        print(f"[*] Scrapling GET: {url}")
        r1 = subprocess.run(cmd_md, capture_output=True, text=True, timeout=60)
        r2 = subprocess.run(cmd_html, capture_output=True, text=True, timeout=60)

        md_text = _read_if_exists(md_file)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
79% confidence
Finding
The manifest describes fetching and saving web articles/videos, which justifies network access and file writes. However, this implementation invokes external binaries via subprocess and uses a headless/anti-detection browser stack, adding execution capabilities that are not explicitly part of the stated purpose and are broader than ordinary fetching logic.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print(f"[*] Scrapling GET: {url}")
        r1 = subprocess.run(cmd_md, capture_output=True, text=True, timeout=60)
        r2 = subprocess.run(cmd_html, capture_output=True, text=True, timeout=60)

        md_text = _read_if_exists(md_file)
        html_text = _read_if_exists(html_file)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd_html += ["-s", selector]

        print(f"[*] Scrapling fetch (browser): {url}")
        r1 = subprocess.run(cmd_md, capture_output=True, text=True, timeout=120)
        r2 = subprocess.run(cmd_html, capture_output=True, text=True, timeout=120)

        md_text = _read_if_exists(md_file)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print(f"[*] Scrapling fetch (browser): {url}")
        r1 = subprocess.run(cmd_md, capture_output=True, text=True, timeout=120)
        r2 = subprocess.run(cmd_html, capture_output=True, text=True, timeout=120)

        md_text = _read_if_exists(md_file)
        html_text = _read_if_exists(html_file)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Content
return False

    try:
        __import__(name)
        return True
    except ImportError:
        hint = hints.get(name, f"pip install {name}")
Confidence
75% confidence
Finding
Dynamic __import__() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print(f"[*] Downloading: {url}")
    print(f"[*] Command: {' '.join(cmd)}")

    result = subprocess.run(cmd, capture_output=True, text=True)

    if result.returncode != 0:
        print(f"[!] yt-dlp error:\n{result.stderr}")
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The documentation explicitly instructs use of `--cookies-browser chrome` to reuse a local browser's authenticated Bilibili session for premium content. That expands the skill from ordinary public-content fetching into accessing account-scoped or paid content via local credentials, creating risk of unauthorized access and unintended exposure of sensitive browser session data.

Missing User Warnings

Low
Confidence
88% confidence
Finding
The skill retrieves arbitrary remote content, follows through multiple network-fetch mechanisms including a browser engine, downloads images, and writes results to local storage with only status prints. In an agent setting, this can surprise users, enable unintended access to internal or sensitive URLs, and cause filesystem changes without clear consent or policy checks, making the context more dangerous than a standalone CLI utility.

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
lib/article.py:178