T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/generate.py:314- Finding
Backend-Controlled Output Reference Enables Arbitrary Local File Copying
- Content
View full analysis
Vulnerability Details
File Location:
scripts/generate.py:314-329, 342-357
Vulnerability Type: Backend-controlled arbitrary local file access
Risk Level: HighVulnerable Code
python def local_source_path(file_value: str) -> Path | None: if not file_value: return None parsed = urllib.parse.urlparse(file_value) if parsed.scheme in {"http", "https"}: query = urllib.parse.parse_qs(parsed.query) local_path = query.get("path", [None])[0] return expand_optional_path(local_path) if parsed.path == "/v1/audio": query = urllib.parse.parse_qs(parsed.query) local_path = query.get("path", [None])[0] return expand_optional_path(local_path) if file_value.startswith("/v1/audio?path="): query = urllib.parse.parse_qs(parsed.query) local_path = query.get("path", [None])[0] return expand_optional_path(local_path) path = expand_optional_path(file_value) if path and path.exists(): return path return Nonepython def save_outputs( entries: list[dict[str, Any]], base_url: str, out_dir: Path, headers: dict[str, str], ) -> list[Path]: out_dir.mkdir(parents=True, exist_ok=True) saved: list[Path] = [] for index, entry in enumerate(entries, start=1): file_value = entry.get("file") if not isinstance(file_value, str) or not file_value: continue source_path = local_source_path(file_value) suffix = Path(source_path.name).suffix if source_path else Path(urllib.parse.urlparse(file_value).path).suffix suffix = suffix or ".mp3" destination = out_dir / f"{index:02d}{suffix}" if source_path and source_path.exists(): shutil.copy2(source_path, destination) else: url = file_value if file_value.startswith("/"): ...[truncated 2327 chars]- Remediation
View remediation
Remediation Suggestions
- Treat all HTTP and HTTPS values exclusively as network URLs. Never derive a local filesystem path from an arbitrary URL query parameter.
- Allow local-file copying only when the backend is explicitly configured as a trusted loopback service.
- Configure a dedicated ACE-Step output root and require every local source to remain beneath it:
- Resolve both the approved root and candidate path with
Path.resolve(). - Verify the candidate using
candidate.is_relative_to(approved_root). - Reject traversal, symlinks escaping the approved root, and paths outside the allowlist.
- Resolve both the approved root and candidate path with
- Reject direct absolute paths received from remote backends.
- Prefer opaque output identifiers returned by the backend and retrieve files through a fixed API route.
- Validate that copied outputs are regular files with expected audio types and enforce a maximum file size.
- Add regression tests covering absolute paths,
..traversal, symlink escapes, and HTTP URLs containing maliciouspathparameters.
