Back to skill

Security audit

Douyin Fetcher

Security checks for vulnerabilities and agentic risk

Overview

This Douyin downloader is not clearly malicious, but it needs Review because it combines browser, shell-style download commands, local persistence, and extra transcription/storage behavior with weak scoping and unsafe generated instructions.

Install only if you are comfortable with a Douyin-focused skill using browser access, network requests, local downloads, ffmpeg, and optional transcription/storage. Review or patch the generated command/tool-call handling before letting an agent execute its instructions automatically, and use an isolated browser profile plus a user-approved output directory.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/browser_dom.py:85
Finding

Agent Tool-Call Injection Through an Unescaped Page URL

Content
View full analysis

Vulnerability Details

File Location: scripts/browser_dom.py:85-108
Vulnerability Type: Agent instruction injection caused by unsafe string interpolation
Risk Level: High

Vulnerable Code

python
def generate_instruction(self, modal_id: str, page_url: Optional[str] = None,
                         output_path: Optional[str] = None) -> Dict:
    """Generate browser operation instructions."""
    if not page_url:
        page_url = f"https://www.douyin.com/video/{modal_id}"

    if not output_path:
        output_path = f"/path/to/temp/douyin\\douyin_{modal_id}.mp4"

    return {
        "needs_agent": True,
        "modal_id": modal_id,
        "page_url": page_url,
        "output_path": output_path,
        "steps": [
            {
                "step": 1,
                "description": "Open the Douyin video page",
                "tool_call": f"browser(action='open', profile='openclaw', targetUrl='{page_url}')"
            },

The original source contains Chinese descriptions, but the executable statement is reproduced without alteration.

Technical Analysis

page_url is inserted directly into a single-quoted, executable-looking browser tool call. The value is neither escaped nor represented as a structured argument object. A page URL containing a quote can terminate the targetUrl value and append attacker-controlled tool-call syntax.

The vulnerable method can receive a URL propagated by scripts/fetcher.py, and browser_dom.py also accepts page_url directly from a command-line argument. The generated instruction is explicitly intended for an OpenClaw Agent to execute. Consequently, the boundary between untrusted data and Agent instructions is not preserved.

This is distinct from ordinary URL redirection: the attacker can alter the textual structure of the generated tool instruction itself.

Attack Path

  1. An attacker supplies a crafted page_url con ...[truncated 1010 chars]
Remediation
View remediation

Remediation Suggestions

  • Do not serialize tool calls as executable strings. Return structured data, for example:

    python
    {
        "tool": "browser",
        "arguments": {
            "action": "open",
            "profile": "openclaw",
            "targetUrl": validated_url
        }
    }
    
  • Parse URLs with urllib.parse.urlsplit.

  • Require the https scheme.

  • Reject embedded credentials, control characters, quotes, unexpected ports, and malformed URLs.

  • Permit only an explicit hostname allowlist such as www.douyin.com and v.douyin.com, including only required subdomains.

  • Ensure the Agent treats every returned field as data rather than instructions that can introduce additional tool calls.

  • Add tests using quote characters, newlines, backslashes, and tool-call-like payloads.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/browser_dom.py:119
Finding

Shell Command Injection Through an Unescaped Output Path

Content
View full analysis

Vulnerability Details

File Location: scripts/browser_dom.py:119-127
Additional Affected Location: scripts/browser_dom.py:149-154; input accepted by scripts/fetcher.py:299-306
Vulnerability Type: Shell command injection in generated curl and ffmpeg commands
Risk Level: High

Vulnerable Code

python
{
    "step": 5,
    "description": "Download video",
    "command": f'{CURL_CMD} -L -H "Referer: https://www.douyin.com/" -o "{output_path}" "<CDN_URL>"',
    "note": "Replace <CDN_URL> with the address obtained in Step 4"
},

The same unsafe value is also used in the DASH fallback:

python
{
    "description": "Merge audio and video (optional)",
    "command": f'ffmpeg -i "{output_path}" -i "/path/to/temp/douyin\\audio_{modal_id}.m4a" -c copy "/path/to/temp/douyin\\merged_{modal_id}.mp4" -y'
}

The output path is accepted from the command line in fetcher.py:

python
parser.add_argument("-o", "--output", help="Output path")

args = parser.parse_args()

fetcher = DouyinFetcher()

if args.method:
    fetcher.config["downloader_priority"] = [args.method]

result = fetcher.fetch(args.input, args.output)

The original source contains Chinese descriptions and help text, but the executable statements are reproduced without alteration.

Technical Analysis

A caller-controlled output_path is interpolated into shell command text between double quotes. Double quotes do not make arbitrary input safe: an embedded quote can terminate the path argument, after which shell separators or command substitutions can introduce additional commands.

The commands are returned as instructions for an Agent or user rather than executed through a safe argument-vector API. If a downstream component executes the command field with a shell, the untrusted path becomes executable syntax.

The class already contains a safer pattern in download_video(), which i ...[truncated 1251 chars]

Remediation
View remediation

Remediation Suggestions

  • Do not return shell command strings containing user-controlled values.

  • Perform downloads and media processing in code with argument vectors and shell=False, for example:

    python
    subprocess.run(
        [CURL_CMD, "-L", "-H", referer, "-o", safe_output, cdn_url],
        shell=False,
        check=True,
        timeout=120,
    )
    
  • Apply the same argument-vector approach to ffmpeg.

  • Resolve output paths with Path.resolve() and require them to remain under a dedicated download directory.

  • Reject control characters, null bytes, and paths outside the approved directory.

  • Use generated server-side filenames where possible rather than accepting arbitrary paths.

  • If display-only commands are retained, clearly mark them as non-executable examples and use platform-appropriate quoting; quoting alone must not replace structured execution.

  • Add regression tests with quotes, command separators, substitutions, newlines, and traversal sequences.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fetcher.py:61
Finding

Insufficient URL Validation Allows Requests to Unintended Destinations

Content
View full analysis

Vulnerability Details

File Location: scripts/fetcher.py:61-74
Additional Affected Location: scripts/fetcher.py:151,271-274
Vulnerability Type: Arbitrary destination access caused by substring-based URL validation
Risk Level: Medium

Vulnerable Code

python
def resolve_short_url(url: str) -> Optional[str]:
    """Resolve a Douyin short URL."""
    if "v.douyin.com" not in url:
        return None
    try:
        cmd = ["curl.exe", "-sL", "-o", "NUL", "-w", "%{url_effective}", url]
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
        if result.returncode == 0:
            effective_url = result.stdout.strip()
            if "douyin.com" in effective_url:
                return effective_url
    except Exception:
        pass
    return None

The browser fallback can preserve the original input:

python
result = self._fetch_via_browser(
    modal_id,
    resolved_url if short_link_resolved else input_url,
    output_path
)

Technical Analysis

The code determines whether a URL belongs to Douyin using substring checks rather than parsing and comparing the normalized hostname. These checks can be satisfied when the expected text appears in a path, query, user-information section, or attacker-controlled hostname.

curl.exe -L follows redirects, but the code validates only whether the final URL string contains douyin.com. It does not enforce HTTPS, prohibit credentials, restrict ports, or ensure that each redirect remains within an approved destination set.

If short-link resolution fails, the browser fallback may receive the original unvalidated input. This permits network access beyond what is minimally necessary for the declared Douyin download functionality.

Attack Path

  1. An attacker supplies a URL whose textual representation contains v.douyin.com or douyin.com but whose actual hostname is attacker ...[truncated 1055 chars]
Remediation
View remediation

Remediation Suggestions

  • Parse every URL with urllib.parse.urlsplit() instead of using substring matching.
  • Require https.
  • Normalize the hostname and compare it against an explicit allowlist using exact matches or properly bounded subdomain checks.
  • Reject URLs containing user information, fragments where unnecessary, control characters, malformed hostnames, and unexpected ports.
  • Do not pass the original URL to browser fallback after validation fails.
  • Disable automatic redirects or validate every redirect target before following it.
  • Maintain separate allowlists for Douyin page hosts and required media CDN hosts.
  • Consider blocking loopback, link-local, private, multicast, and reserved IP ranges after DNS resolution where arbitrary URLs can reach a network client.
  • Use a fresh or isolated browser context rather than a persistent authenticated profile when opening externally influenced content.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (12)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill documents use of browser, curl, ffmpeg, local file paths, and network access, but the manifest declares no explicit tool scope or permissions. That creates an overbroad-trust problem: an orchestrator or agent may invoke shell, file, and network capabilities without clear least-privilege boundaries or review expectations.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The documented workflow calls a local ASR HTTP endpoint even though the skill is described as a video downloader. Sending extracted audio to another service changes the data-flow and trust boundary, and could expose private media contents to a local or proxied service without the user's informed consent.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill instructs transmission of audio to an HTTP ASR endpoint without an explicit privacy or data-sharing notice. Even when the endpoint is localhost, the audio may contain sensitive content, and users are not told that media data will be sent to another service for processing.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The instructions include downloading, moving, retaining, and deleting local media artifacts, but they do not clearly warn the user that local files will be modified and may persist. In agentic environments, silent file lifecycle actions can lead to accidental data loss, clutter, or retention of sensitive copyrighted media.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The skill is presented as a Douyin video fetcher, but it also defines downstream transcript storage in a knowledge-base path and video archival locations. This scope expansion can cause agents to write persistent files outside the user's immediate download task, increasing the chance of unintended data retention or unauthorized content placement.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The file’s docstrings and CLI usage/output messages are written exclusively in Chinese, which imposes a specific language on users and operators without any opt-in or alternative. Under the policy, locale or language constraints should either be optional for the user or clearly justified as region-specific.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The instruction payload returned by generate_instruction contains descriptions, notes, retry text, and troubleshooting guidance only in Chinese. Because this content is intended for agent/operator consumption, it enforces a language choice without opt-in or explicit justification.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/browser_dom.py (reported line 190)May include surrounding context.

python
]
        
        try:
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
            if result.returncode != 0:
                return False

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This code reads Douyin-related configuration from user home-directory JSON files, and later relies on a tikhub_api_token from that configuration. Although token presence is checked internally, there is no docstring, comment, prompt, or user-facing notice here explaining that credentials from local config will be accessed as part of execution.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The function resolves Douyin short links by invoking curl.exe, which causes the provided URL to be transmitted over the network. While there is a debug print that a short link is being resolved, there is no explicit disclosure in comments, docstrings, or CLI help that user-supplied links will be sent in a network request.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/fetcher.py (reported line 55)May include surrounding context.

python
return None
    try:
        cmd = ["curl.exe", "-sL", "-o", "NUL", "-w", "%{url_effective}", url]
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
        if result.returncode == 0:
            effective_url = result.stdout.strip()
            if "douyin.com" in effective_url:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
85% confidence
Finding

This code selects a local output path and downloads video content to disk, creating or overwriting files as part of normal execution. Although successful completion is printed afterward, there is no prior warning in the CLI help or function documentation that running the skill will write files locally.

Content

No source excerpt is available for this finding.