Back to skill

Security audit

httrack

Security checks for vulnerabilities and agentic risk

Overview

This website-mirroring skill is mostly coherent, but its URL checks can still allow requests to private or internal network services through DNS or redirects.

Install only if you are comfortable running a web mirror with broad outbound network reach. Use trusted URLs, avoid attacker-supplied links, and prefer running it in an environment without access to sensitive internal services or metadata endpoints; treat all mirrored output as untrusted content.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mirror.py:44
Finding
Private-network SSRF through unchecked DNS resolution and redirects<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mirror.py:44-73`, with the validated URL passed to HTTrack at `scripts/mirror.py:112-118` **Vulnerability Type**: Server-Side Request Forgery (SSRF) **Risk Level**: Medium ### Vulnerable Code ```python def check_url(u, allow_private=False): u = u.strip() if any(c.isspace() for c in u): raise UsageError("url contains whitespace") p = urllib.parse.urlparse(u) if p.scheme not in ("http", "https"): raise UsageError(f"scheme {p.scheme or '(none)'} refused — only http/https are mirrored") if not p.netloc: raise UsageError("url has no host") if "@" in p.netloc: raise UsageError("userinfo (user@host) in url refused — phishing-shaped hosts are refused") if not allow_private: host = p.hostname or "" if host.lower() == "localhost" or _is_private_ip(host): raise UsageError( f"host {host!r} is loopback/link-local/private — refused by default; " "pass --allow-private for authorized LAN mirrors") return u.split("#", 1)[0] def _is_private_ip(host): import ipaddress h = host.strip("[]") try: ip = ipaddress.ip_address(h) except ValueError: return False # a DNS name — can't classify lexically return (ip.is_loopback or ip.is_link_local or ip.is_private or ip.is_multicast or ip.is_unspecified) ``` The accepted URL is subsequently passed to HTTrack: ```python def run_httrack(argv_extra, timeout): b = find_binary() if not b: raise SystemExit(3) argv = [b] + argv_extra t0 = time.time() try: p = subprocess.run(argv, capture_output=True, timeout=timeout or None) ``` ### Technical Analysis The private-network restriction examines only literal IP addresses. When the URL contains a DNS hostname, `ipaddress.ip_address()` raises `ValueError`, and `_is_private_ip()` immediately returns `False`. Th ...[truncated 2421 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve the URL hostname before invoking HTTrack using `socket.getaddrinfo()`. 2. Validate every returned IPv4 and IPv6 address. Reject loopback, private, link-local, multicast, unspecified, reserved, and otherwise non-global addresses unless `--allow-private` was explicitly supplied. 3. Treat mixed DNS results as unsafe: reject the hostname if any returned address is non-global. 4. Do not rely only on pre-resolution, because DNS can change between validation and connection. Pin the validated address where possible while preserving the intended HTTP `Host` header and TLS server name. 5. Validate every redirect target before following it. If HTTrack cannot provide a reliable redirect-validation hook, run it behind an egress proxy or network policy that blocks private, loopback, link-local, metadata, and reserved address ranges. 6. Apply the same controls to both initial URLs and asset/link requests discovered during crawling. 7. Add regression tests for: - Hostnames resolving to `127.0.0.1` - RFC1918 IPv4 destinations - IPv6 loopback, unique-local, and link-local destinations - Hostnames returning mixed public and private addresses - Public endpoints redirecting to private destinations - DNS responses changing between validation and connection 8. Update the README and manifest to avoid claiming that network access is limited to caller-supplied public URLs until destination enforcement covers DNS resolution and redirects. ]]>
Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (1)

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill advertises and orchestrates shell execution, filesystem output, environment checks, and unrestricted outbound network access, but it does not declare explicit permissions for those capabilities. This creates a policy and review gap: agents or platforms may treat the skill as lower-risk than it is, while the skill can still fetch arbitrary remote content and write untrusted mirrored data to disk.

Static analysis

No suspicious patterns detected.