T09 · Insecure Skill Coding Practices
Warning
- Location
- deep-search-report.py:185
- Finding
- Unsafe User-Specified Stream File Handling Enables Local Disclosure and Symlink-Based File Modification<![CDATA[ ## Vulnerability Details **File Location**: `deep-search-report.py`, lines 185–196 and 278–306 **Vulnerability Type**: Unsafe file creation, permissive file permissions, and symbolic-link following **Risk Level**: Medium ### Vulnerable Code ```python def resolve_stream_file_path(specified_path: Optional[str]) -> Optional[str]: """Return stream file path, preferring user-specified path.""" if specified_path: abs_path = os.path.abspath(specified_path) parent_dir = os.path.dirname(abs_path) or "." if not os.path.isdir(parent_dir): raise UniFuncsDeepSearchError(f"Stream file directory does not exist: {parent_dir}") if not os.access(parent_dir, os.W_OK): raise UniFuncsDeepSearchError(f"Stream file directory is not writable: {parent_dir}") if not os.path.exists(abs_path): with open(abs_path, "w", encoding="utf-8"): pass return abs_path return create_temp_stream_file() ``` The returned path is subsequently reopened in append mode and populated with API response data: ```python temp_path = resolve_stream_file_path(stream_file_path) content_parts: list[str] = [] done = False started_at = time.monotonic() try: with urllib.request.urlopen(req, timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS) as response: writer = open(temp_path, "a", encoding="utf-8") if temp_path else None try: while True: if time.monotonic() - started_at >= stream_timeout_seconds: break line = response.readline() if not line: done = True break decoded = line.decode("utf-8", errors="replace") if writer: writer.write(decoded) ``` ### Technical Analysis When `--stream-file` is supplied, the implementation validates the path and ...[truncated 2253 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Create user-selected stream files atomically with mode `0600`. - Use `os.open()` with appropriate flags, such as `O_WRONLY`, `O_CREAT`, `O_EXCL`, and `O_NOFOLLOW`, where supported. - Convert the securely opened file descriptor into a Python file object with `os.fdopen()` rather than closing and reopening it by pathname. - If existing stream files must be supported, use `lstat()` to reject symbolic links and verify the opened descriptor with `os.fstat()`. - Require the parent directory to be owned by or exclusively writable by the invoking user; reject unsafe shared directories. - Apply `os.chmod(path, 0o600)` to existing approved files before writing sensitive stream content. - Keep the descriptor open for the entire streaming lifecycle to eliminate the validation-to-open race. - Prefer the existing `tempfile.mkstemp()` behavior unless the user explicitly requires a custom location. - Add tests covering symbolic links, path replacement races, unsafe directory permissions, and restrictive file modes. ]]>
