T09 · Insecure Skill Coding Practices
Warning
- Location
- personanexus_skill/cli.py:43
- Finding
- Predictable Temporary File Allows Symlink-Based File Overwrite## Vulnerability Details **File Location**: `personanexus_skill/cli.py`, lines 43-59 **Vulnerability Type**: Predictable temporary file and symlink-following file write **Risk Level**: Medium ### Vulnerable Code ```python def _atomic_write(path: Path, content: str) -> None: """Write content to a file atomically via temp-and-rename. On POSIX systems ``os.replace`` is atomic within the same filesystem, preventing partial writes from corrupting the target file. """ tmp = path.with_suffix(path.suffix + ".tmp") try: tmp.write_text(content, encoding="utf-8") os.replace(str(tmp), str(path)) except BaseException: tmp.unlink(missing_ok=True) raise ``` ### Technical Analysis The temporary file name is deterministically derived from the destination by appending `.tmp`. The application neither creates this file exclusively nor verifies that it is a regular file rather than a symbolic link. In a directory writable by another local user or process, an attacker can create the predictable temporary path as a symbolic link to another file. `Path.write_text()` follows symbolic links, so the content is written to the link target using the privileges of the user running PersonaNexus. Although `os.replace()` makes the final rename atomic, it does not protect the preceding write. After the linked target has been overwritten, the rename only replaces the requested destination with the symlink itself. This helper is used by the `compile` and `init` CLI operations, making the issue reachable whenever output is written into an attacker-accessible directory. ### Attack Path 1. A victim plans to compile an identity to `/shared/result.md`. 2. The attacker has write access to `/shared` and predicts that the temporary path will be `/shared/result.md.tmp`. 3. The attacker creates a symbolic link: ```bash ln -s /home/victim/.config/example.conf /shared/result.md.tmp ``` 4. The victim runs: ```bash python -m pe ...[truncated 997 chars]
- Remediation
- ## Remediation Suggestions Create an unpredictable temporary file securely and exclusively in the destination directory: 1. Use `tempfile.NamedTemporaryFile()` or `tempfile.mkstemp()` with `dir=path.parent`. 2. Ensure exclusive creation so an existing path cannot be reused. 3. Do not follow symbolic links. Where available, use `O_NOFOLLOW` with `os.open()`. 4. Apply restrictive permissions such as `0o600`. 5. Flush buffered data and call `os.fsync()` before replacement. 6. Replace the destination with `os.replace()` only after the temporary file is safely closed. 7. Clean up the unique temporary file on failure. For example: ```python import os import tempfile from pathlib import Path def _atomic_write(path: Path, content: str) -> None: path = path.resolve() fd, tmp_name = tempfile.mkstemp( prefix=f".{path.name}.", suffix=".tmp", dir=path.parent, text=True, ) tmp_path = Path(tmp_name) try: with os.fdopen(fd, "w", encoding="utf-8") as stream: stream.write(content) stream.flush() os.fsync(stream.fileno()) os.replace(tmp_path, path) except BaseException: tmp_path.unlink(missing_ok=True) raise ``` If output directories may be shared, additionally verify directory ownership and permissions before writing.
