T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- scripts/generate.py:314
- Finding
- Backend-Controlled Output Reference Enables Arbitrary Local File Copying## Vulnerability Details **File Location**: `scripts/generate.py:314-329, 342-357` **Vulnerability Type**: Backend-controlled arbitrary local file access **Risk Level**: High ### Vulnerable 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 None ``` ```python 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
- ## Remediation Suggestions 1. Treat all HTTP and HTTPS values exclusively as network URLs. Never derive a local filesystem path from an arbitrary URL query parameter. 2. Allow local-file copying only when the backend is explicitly configured as a trusted loopback service. 3. 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. 4. Reject direct absolute paths received from remote backends. 5. Prefer opaque output identifiers returned by the backend and retrieve files through a fixed API route. 6. Validate that copied outputs are regular files with expected audio types and enforce a maximum file size. 7. Add regression tests covering absolute paths, `..` traversal, symlink escapes, and HTTP URLs containing malicious `path` parameters.
