Back to skill

Security audit

Douyin DL

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a genuine Douyin video downloader, but its script can execute local shell commands if given crafted URLs, filenames, output paths, or page-controlled video sources.

Review before installing. Do not run this skill on untrusted or pasted URLs, filenames, or output directories unless the script is fixed to avoid shell=True, validate Douyin domains and HTTPS media hosts structurally, sanitize explicit filenames, and pin or localize the agent-browser dependency.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/douyin_download.py:114
Finding

OS Command Injection Through the Input URL

Content
View full analysis
str: """Run a shell command and return stdout.""" r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout) return r.stdout.strip() ``` ```python # Fallback: try as-is return url, None ``` ```python out = run_check(f"agent-browser open '{url}'", timeout=args.timeout) ``` The corresponding command execution helper also enables shell interpretation: ```python def run_check(cmd: str, timeout: int = 30) -> str: """Run a shell command and raise on failure.""" r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout) if r.returncode != 0: raise RuntimeError(f"Command failed: {cmd}\n{r.stderr}") return r.stdout.strip() ``` ### Technical Analysis The positional URL argument is inserted into a shell command inside single quotes. The application attempts no shell escaping before passing the resulting string to `subprocess.run()` with `shell=True`. The URL normalization function also returns unsupported URLs unchanged. Consequently, a single quote in a supplied value can terminate the quoted argument. Shell metacharacters following that quote are then interpreted as commands by the operating-system shell. This is not limited to URLs from the documented Douyin domains because the fallback behavior accepts an arbitrary value as-is. ### Attack Path 1. An attacker causes the Skill to be invoked with a URL containing a single quote and shell syntax, such as a value conceptually shaped like: ```text https://example.invalid/x'; attacker_command; # ``` 2. `normalize_url()` does not recognize it as a supported Douyin URL and returns the entire value unchanged. 3. The program constructs a command resembling: ```sh agen ...[truncated 684 chars]
Remediation
View remediation
str: result = subprocess.run( args, shell=False, capture_output=True, text=True, timeout=timeout, check=True, ) return result.stdout.strip() out = run_check(["agent-browser", "open", url], timeout=args.timeout) ``` In addition: 1. Require the URL scheme to be `https`. 2. Normalize the hostname using `urllib.parse`. 3. Allow only exact approved hosts, such as `www.douyin.com` and `v.douyin.com`, or correctly bounded subdomains if operationally required. 4. Reject URLs containing credentials, unsupported ports, control characters, or malformed hostnames. 5. Remove the arbitrary-URL fallback and fail closed when the input does not match a supported Douyin URL format. 6. Never rely on manually adding quotes to make untrusted data safe for a shell. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/douyin_download.py:138
Finding

OS Command Injection Through the Output Directory or Explicit Filename

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/douyin_download.py:79
Finding

Page-Controlled Video Source Is Passed to a Shell Command

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
SKILL.md:16
Finding

Unpinned Globally Installed npm Dependency

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • 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
Findings (7)

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
99% confidence
Finding

This is a true tool-parameter abuse issue because the helper normalizes command execution through a shell, making every caller vulnerable if any parameter is attacker-controlled. In this skill, inputs originate from a user-provided Douyin URL and later from remote page content, so the downloader context makes the issue more dangerous rather than less.

Content

Scanner excerpt · scripts/douyin_download.py (reported line 22)May include surrounding context.

python
def run(cmd: str, timeout: int = 30) -> str:
    """Run a shell command and return stdout."""
    r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
    return r.stdout.strip()

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
99% confidence
Finding

This instance is especially risky because run_check is used with agent-browser open '{url}', where url is directly derived from a user argument and only lightly normalized. A maliciously crafted URL containing quotes or shell operators can escape the quoting and abuse the shell to run arbitrary commands under the agent's privileges.

Content

Scanner excerpt · scripts/douyin_download.py (reported line 28)May include surrounding context.

python
def run_check(cmd: str, timeout: int = 30) -> str:
    """Run a shell command and raise on failure."""
    r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
    if r.returncode != 0:
        raise RuntimeError(f"Command failed: {cmd}\n{r.stderr}")
    return r.stdout.strip()

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
99% confidence
Finding

The curl invocation abuses a shell with externally influenced parameters from both local user input and remote website-controlled data. Because the skill intentionally visits untrusted web content to extract media URLs, an attacker controlling a page, redirect, or title-derived filename can potentially achieve command execution or overwrite arbitrary files, making the downloader context materially dangerous.

Content

Scanner excerpt · scripts/douyin_download.py (reported line 159)May include surrounding context.

python
f"-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' "
        f"'{video_src}'"
    )
    r = subprocess.run(dl_cmd, shell=True, capture_output=True, text=True, timeout=300)
    if r.returncode != 0:
        print(f"❌ Download failed: {r.stderr}", file=sys.stderr)
        sys.exit(1)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill explicitly instructs use of both network access (agent-browser, curl) and shell execution, but the manifest does not declare any tool restrictions or permissions. That creates a real security gap because an agent may grant broader-than-necessary capabilities to a skill that fetches attacker-controlled URLs and executes external commands, increasing the blast radius if the skill is modified, misused, or chained with prompt-injection content from remote pages.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
98% confidence
Finding

The helper executes arbitrary shell strings with shell=True, and this function is later used with user-influenced values such as URLs and browser-evaluated content. That creates a command injection path where crafted input containing shell metacharacters or embedded quotes can break out of the intended command and execute arbitrary OS commands.

Content

Scanner excerpt · scripts/douyin_download.py (reported line 22)May include surrounding context.

python
def run(cmd: str, timeout: int = 30) -> str:
    """Run a shell command and return stdout."""
    r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
    return r.stdout.strip()

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
98% confidence
Finding

This wrapper repeats the same unsafe shell execution pattern and is directly used to open a browser on a user-supplied URL via string interpolation. Because shell=True interprets the full string, an attacker can supply a malicious URL that injects additional commands, leading to arbitrary code execution on the host running the skill.

Content

Scanner excerpt · scripts/douyin_download.py (reported line 28)May include surrounding context.

python
def run_check(cmd: str, timeout: int = 30) -> str:
    """Run a shell command and raise on failure."""
    r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
    if r.returncode != 0:
        raise RuntimeError(f"Command failed: {cmd}\n{r.stderr}")
    return r.stdout.strip()

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
97% confidence
Finding

The download command is built as a shell string containing output_path and video_src, both of which can be influenced by external input: filename/output-dir come from arguments, and video_src is extracted from an untrusted remote page. If either contains a single quote or shell metacharacters, the quoted curl command can be broken and arbitrary commands executed.

Content

Scanner excerpt · scripts/douyin_download.py (reported line 159)May include surrounding context.

python
f"-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' "
        f"'{video_src}'"
    )
    r = subprocess.run(dl_cmd, shell=True, capture_output=True, text=True, timeout=300)
    if r.returncode != 0:
        print(f"❌ Download failed: {r.stderr}", file=sys.stderr)
        sys.exit(1)