Back to skill

Security audit

ASMR.one 音频下载器

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its downloader purpose, but unsafe path and remote-download handling could write outside the intended folder or consume local/network resources unexpectedly.

Review before installing. Use a dedicated output directory and restricted user/workspace, only pass RJ identifiers matching RJ plus digits, avoid running it on sensitive networks, monitor disk usage during downloads, and be cautious with the obfuscation option and untrusted local media files.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/asmr_tool.py:373
Finding

Path Traversal Through Unvalidated RJ Identifier

Content
View full analysis

Vulnerability Details

File Location: scripts/asmr_tool.py, lines 373–374, 591–597, and 915–981
Vulnerability Type: Path traversal and write outside the configured output directory
Risk Level: High

Vulnerable Code

python
def normalize_rj(code: str) -> str:
    return f"RJ{code.upper().strip().lstrip('RJ')}"

The normalized value is used directly as a directory component:

python
def cmd_download(args):
    rj_id = normalize_rj(args.rj_code)
    cfg = load_config()
    r = resolve_args(cfg, args, ["proxy", "output_dir", "default_vbr", "default_bitrate"])
    session = make_session(r["proxy"])
    base_dir = Path(r["output_dir"])
    work_dir = base_dir / rj_id

The local import workflow creates and writes to the resulting path:

python
def cmd_import(args):
    """From a local directory, import downloaded audio files."""
    rj_id = normalize_rj(args.rj_code)
    src_dir = Path(args.dir)
    cfg = load_config()
    base_dir = Path(cfg.get("output_dir", str(DEFAULT_OUTPUT_DIR)))
    work_dir = base_dir / rj_id

    if not src_dir.is_dir():
        print(f"[x] Directory does not exist: {src_dir}")
        return

    audio_exts = {".mp3", ".wav", ".flac", ".ogg", ".m4a", ".opus"}
    files = sorted([f for f in src_dir.iterdir() if f.suffix.lower() in audio_exts])
    if not files:
        return

    work_dir.mkdir(parents=True, exist_ok=True)
    raw_dir = work_dir / "raw"
    raw_dir.mkdir(parents=True, exist_ok=True)

    downloaded = []
    for i, f in enumerate(files, 1):
        clean_title = sanitize_filename(f.stem)
        raw_name = f"{rj_id}_{i:03d}_{clean_title}{f.suffix}"
        out_name = f"{rj_id}_{i:03d}_{clean_title}.mp3"

        raw_path = raw_dir / raw_name
        out_path = work_dir / out_name

        shutil.copy2(f, raw_path)

        if f.stat().st_size > QQ_AUDIO_MAX_BYTES and f.suffix.lower() != ".mp3":
            _compress_cbr_mp3(raw_path, out_path, "64k")
        else:
            if f.suffix.l
...[truncated 2792 chars]
Remediation
View remediation

Remediation Suggestions

  1. Enforce the documented identifier format before using the value:
python
def normalize_rj(code: str) -> str:
    normalized = code.strip().upper()
    if not normalized.startswith("RJ"):
        normalized = f"RJ{normalized}"

    if not re.fullmatch(r"RJ\d+", normalized):
        raise ValueError("RJ identifier must match RJ followed by digits")

    return normalized
  1. Resolve and verify every work directory against the configured base directory:
python
base_dir = Path(configured_output).expanduser().resolve()
work_dir = (base_dir / rj_id).resolve()

if work_dir != base_dir and base_dir not in work_dir.parents:
    raise ValueError("Destination escapes the configured output directory")
  1. Perform the containment check before mkdir, open, copy2, FFmpeg execution, or rename operations.

  2. Apply the same validation consistently to download, import, sendlist, and every future command that converts an RJ identifier into a filesystem path.

  3. Add regression tests for absolute paths, repeated ../, mixed separators, empty identifiers, Unicode separators, and identifiers containing non-digit suffixes.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/asmr_tool.py:1030
Finding

Unrestricted Remote Media URLs and Redirects Enable Blind SSRF and Unbounded Downloads

Content
View full analysis

Vulnerability Details

File Location: scripts/asmr_tool.py, lines 646–655 and 1030–1047
Vulnerability Type: Server-side request forgery, unsafe redirect handling, and missing download-size restrictions
Risk Level: Medium

Vulnerable Code

Remote API data controls the media URL passed to the downloader:

python
def flatten(nodes):
    for node in nodes:
        if not isinstance(node, dict):
            continue
        if node.get("type") != "folder":
            url = (node.get("mediaDownloadUrl") or node.get("mediaUrl") or "").strip()
            if url:
                ext = os.path.splitext(urlparse(url).path)[1].lower()
                tracks.append({
                    "title": node.get("title", ""),
                    "url": url,
                    "ext": ext,
                })
        flatten(node.get("children", []))
flatten(tracks_raw)

The downloader enables redirects and streams the response without validating its final destination or enforcing a size limit:

python
def _download_file(session, url, dest) -> bool:
    try:
        session.allow_redirects = True
        resp = session.get(url, timeout=300, stream=True)
        session.allow_redirects = False
        if resp.status_code != 200:
            print(f"    [!] HTTP {resp.status_code}")
            return False

        total = int(resp.headers.get("content-length", 0))
        downloaded = 0
        dest.parent.mkdir(parents=True, exist_ok=True)

        with open(dest, "wb") as f:
            for chunk in resp.iter_content(chunk_size=8192):
                if chunk:
                    f.write(chunk)
                    downloaded += len(chunk)
                    if total > 0:
                        pct = downloaded * 100 // total
                        sys.stdout.write(
                            f"\r    {fmt_size(downloaded)} / "
                            f"{fmt_size(total)} ({pct}%)"
                        )
                        sys.stdout.flu
...[truncated 2895 chars]
Remediation
View remediation

Remediation Suggestions

  1. Permit only https media URLs.

  2. Maintain an explicit allowlist of expected ASMR media CDN hostnames. Reject embedded credentials, unexpected ports, malformed hostnames, and all destinations outside that allowlist.

  3. Resolve the hostname before connecting and reject loopback, private, link-local, multicast, reserved, and cloud metadata address ranges for both IPv4 and IPv6.

  4. Disable automatic redirects and validate each redirect target before following it. Apply the same scheme, hostname, port, and resolved-address checks at every hop. Set a small maximum redirect count.

  5. Enforce a strict maximum download size using both Content-Length and the actual number of streamed bytes:

python
MAX_DOWNLOAD_BYTES = 500 * 1024 * 1024

declared = int(resp.headers.get("content-length", 0) or 0)
if declared > MAX_DOWNLOAD_BYTES:
    raise ValueError("Download exceeds the permitted size")

downloaded = 0
for chunk in resp.iter_content(chunk_size=8192):
    downloaded += len(chunk)
    if downloaded > MAX_DOWNLOAD_BYTES:
        raise ValueError("Download exceeded the permitted size")
    output.write(chunk)
  1. Write to a temporary partial file and atomically rename it only after all validation succeeds. Delete partial files on failure.

  2. Validate the response MIME type and inspect the downloaded media before invoking FFmpeg.

  3. Avoid mutating shared session redirect state. Pass allow_redirects=False per request or restore state in a finally block.

  4. Keep FFmpeg patched and consider processing untrusted media in a sandbox with restricted filesystem, network, CPU, memory, and execution time.

Vulnerability Patterns
  • 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
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (23)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared purpose says the skill downloads and compresses RJ audio works, but the documented behavior also includes environment checks, external connectivity tests, configuration parsing, local file import/copy, and collection management. This mismatch weakens informed consent and can hide the real attack surface, making users less likely to anticipate network probing, persistent storage changes, or broader local file handling.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 205)May include surrounding context.

md
| `SKILL.md` | 本文件 |

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This markdown file states that the skill downloads audio from ASMR.one, compresses it, and manages favorites, which implies network activity and local file creation/modification. The description lists capabilities but does not include a user-facing warning about external network access, downloads to disk, or possible storage/overwrite effects.

Content

No source excerpt is available for this finding.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · README.md (reported line 28)May include surrounding context.

bash
pip3 install requests
# Ubuntu: sudo apt install ffmpeg
# macOS:  brew install ffmpeg

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill documentation advertises operations that require network access, shell execution, environment-variable access, and persistent file writes, but it does not declare any explicit tool scope or permissions boundary. In an agent setting, missing scope declarations increase the chance that the skill is run with broader privileges than users expect, enabling unintended filesystem modification or outbound network activity.

Content

No source excerpt is available for this finding.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · SKILL.md (reported line 28)May include surrounding context.

md
# 2. 安装 ffmpeg(用于压缩)
# Ubuntu/Debian:
sudo apt install ffmpeg
# macOS:
brew install ffmpeg

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/asmr_tool.py (reported line 247)May include surrounding context.

python
# 2. 安装 ffmpeg(用于压缩)
# Ubuntu/Debian:
sudo apt install ffmpeg
# macOS:
brew install ffmpeg

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest describes a downloader for ASMR.one audio works using RJ identifiers, focused on download, compression, and send-list generation. However, the file also exposes search, hot recommendations, collection listing, config management, environment probing, and especially local-directory import functionality, which materially expands behavior beyond the stated purpose.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This Python file contains natural-language instructions, help text, and prompts exclusively in Chinese, which effectively forces a specific language on users. Under the stated policy, a language constraint should either be user-selectable or clearly documented as a justified region-specific limitation.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

A downloader/compressor may reasonably need ffmpeg for transcoding, but this implementation additionally probes the host environment using which and version commands, and later uses subprocesses for media inspection. The manifest does not mention system-command execution or environment-auditing behavior, so this capability is broader than the user-facing purpose suggests.

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/asmr_tool.py (reported line 240)May include surrounding context.

python
# 3. ffmpeg
    print(f"\n  [ffmpeg]")
    for cmd, label in [("ffmpeg", "ffmpeg"), ("ffprobe", "ffprobe")]:
        r = subprocess.run(["which", cmd], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        if r.returncode == 0:
            ver = subprocess.run([cmd, "-version"], capture_output=True, text=True, timeout=5)
            first_line = ver.stdout.split("\n")[0] if ver.stdout else "?"

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/asmr_tool.py (reported line 242)May include surrounding context.

python
for cmd, label in [("ffmpeg", "ffmpeg"), ("ffprobe", "ffprobe")]:
        r = subprocess.run(["which", cmd], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        if r.returncode == 0:
            ver = subprocess.run([cmd, "-version"], capture_output=True, text=True, timeout=5)
            first_line = ver.stdout.split("\n")[0] if ver.stdout else "?"
            print(f"   {label}: ✅ {first_line[:60]}")
        else:

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The manifest says the skill is for downloading ASMR.one audio works and using RJ numbers. The import command instead copies audio files from any local directory, transcodes them, creates manifests, and adds them to the collection without contacting ASMR.one at all.

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/asmr_tool.py (reported line 1064)May include surrounding context.

python
try:
        cmd = ["ffmpeg","-y","-i",str(in_path),"-c:a","libmp3lame",
               "-b:a",bitrate,"-ar","44100","-ac","2","-map_metadata","0",str(out_path)]
        r = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, timeout=600)
        if r.returncode == 0 and out_path.exists():
            in_sz = in_path.stat().st_size
            out_sz = out_path.stat().st_size

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/asmr_tool.py (reported line 1082)May include surrounding context.

python
try:
        cmd = ["ffmpeg","-y","-i",str(in_path),"-c:a","libmp3lame",
               "-b:a",bitrate,"-ar","44100","-ac","2","-map_metadata","0",str(out_path)]
        r = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, timeout=600)
        if r.returncode == 0 and out_path.exists():
            in_sz = in_path.stat().st_size
            out_sz = out_path.stat().st_size

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/asmr_tool.py (reported line 1099)May include surrounding context.

python
try:
        cmd = ["ffmpeg","-y","-i",str(in_path),"-c:a","libmp3lame",
               "-b:a",bitrate,"-ar","44100","-ac","2","-map_metadata","0",str(out_path)]
        r = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, timeout=600)
        if r.returncode == 0 and out_path.exists():
            in_sz = in_path.stat().st_size
            out_sz = out_path.stat().st_size

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/asmr_tool.py (reported line 1113)May include surrounding context.

python
def _get_duration(file_path) -> float:
    try:
        r = subprocess.run(["ffprobe","-v","error","-show_entries","format=duration",
                           "-of","default=noprint_wrappers=1:nokey=1",str(file_path)],
                          stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=30)
        return float(r.stdout.decode().strip())

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The natural-language instructions and usage information are presented only in Chinese, which effectively forces a specific language for users without opt-in. There is no note that the skill is intended only for Chinese-speaking users or any alternative language option.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The skill persists downloads and imported files into local directories and stores metadata in configuration paths, but the description does not clearly warn users up front that operations will copy/write files to disk. In agent-driven environments, insufficient notice can lead to unexpected disk usage, retention of sensitive media, or confusion about where content was stored.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

The dependency is specified as requests>=2.25.0, which allows installation of any newer version without ensuring a tested and known-safe release. This creates supply-chain and reproducibility risk because builds may silently resolve to different versions over time, including versions with breaking changes or security issues.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
requests>=2.25.0

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
91% confidence
Finding

The manifest references requests without pinning to a specific version, and the package has multiple known advisories across its release history. Because the resolved version is not fixed, it is impossible to verify from this file alone whether installation will select a vulnerable release, which is a supply-chain security weakness.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/asmr_tool.py (reported line 126)May include surrounding context.

python
result = {}
    for key in keys:
        # CLI 参数优先
        cli_val = getattr(args, key, None)
        if cli_val:
            result[key] = cli_val
            continue

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The optional filename obfuscation feature is explicitly described as '防和谐' and renames files to opaque hashes while writing a reverse mapping file. In the context of a downloader for potentially policy-sensitive audio content, this can facilitate concealment of content identity on disk and hinder oversight or moderation, making the skill context more suspicious than a normal archival rename feature.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.install_untrusted_source

Install source points to URL shortener or raw IP.

Warn
Code
suspicious.install_untrusted_source
Location
example_config.json:2