T09 · Insecure Skill Coding Practices
- 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: HighVulnerable 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_idThe 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
- 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- 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")-
Perform the containment check before
mkdir,open,copy2, FFmpeg execution, or rename operations. -
Apply the same validation consistently to
download,import,sendlist, and every future command that converts an RJ identifier into a filesystem path. -
Add regression tests for absolute paths, repeated
../, mixed separators, empty identifiers, Unicode separators, and identifiers containing non-digit suffixes.
