T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/ari.py:1474
- Finding
- Arbitrary File Overwrite and Symbolic-Link Following During Export## Vulnerability Details **File Location**: `scripts/ari.py:1444-1475` and `scripts/ari.py:1929` **Vulnerability Type**: Unrestricted file overwrite and symbolic-link following **Risk Level**: Medium ### Vulnerable Code ```python def request_download(path, params, dest): """Download a non-JSON response to a local file.""" query = {"method": "GET", "path": path, "params": {k: v for k, v in (params or {}).items() if v not in (None, "")}, "payload": None} url = base_url() + path if query["params"]: url += "?" + urllib.parse.urlencode(query["params"], doseq=True) headers = { "Authorization": "Bearer " + require_key(), "User-Agent": user_agent() } try: req = urllib.request.Request(url, headers=headers, method="GET") with urllib.request.urlopen(req, timeout=TIMEOUT_SEC) as resp: note_release(resp.headers) ctype = resp.headers.get("Content-Type", "") body = resp.read() if "application/json" in ctype: out = json.loads(body.decode("utf-8", errors="replace")) if isinstance(out, dict): out["_query"] = query return out tail = body[-300:].decode("utf-8", errors="replace") if "# export error:" in tail: return error_obj( "ARI_EXPORT_ERROR", 200, tail.split("# export error:", 1)[1].strip(), "Export failed before completion; no file was written.", query ) with open(dest, "wb") as fh: fh.write(body) ``` The destination is exposed directly through a command-line argument: ```python p.add_argument("--out", help="Output file path; defaults to an automatically generated path") ``` ### Technical Analy ...[truncated 2359 chars]
- Remediation
- ## Remediation Suggestions 1. Save exports in a dedicated, user-owned export directory by default. 2. Resolve and validate the destination with `pathlib.Path.resolve()` and reject paths outside the approved directory unless the user explicitly authorizes them. 3. Refuse to overwrite existing files by default. Add an explicit `--overwrite` option if replacement is required. 4. Create new files atomically and exclusively: ```python flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL if hasattr(os, "O_NOFOLLOW"): flags |= os.O_NOFOLLOW fd = os.open(dest, flags, 0o600) with os.fdopen(fd, "wb") as fh: fh.write(body) ``` 5. Verify that the resolved parent is a directory owned or trusted by the current user. 6. Download into a securely created temporary file in the destination directory, flush and synchronize it, and then perform an atomic rename after all response validation succeeds. 7. If overwriting is explicitly enabled, use platform-appropriate symbolic-link protections and revalidate the destination immediately before replacement.
