T09 · Insecure Skill Coding Practices
- Location
scripts/render_report.py:1703- Finding
Predictable Temporary File Allows Symlink-Based Arbitrary File Overwrite
- Content
View full analysis
Vulnerability Details
File Location:
scripts/render_report.py, lines 1703–1711
Vulnerability Type: Predictable temporary file and symlink-following file write
Risk Level: HighVulnerable Code
python def write_output(path: Path, content: str, overwrite: bool) -> None: if path.exists() and not overwrite: fail(f"Refusing to overwrite existing file without --overwrite: {path}") if path.exists() and path.is_dir(): fail(f"Output path is a directory: {path}") path.parent.mkdir(parents=True, exist_ok=True) temporary = path.with_name(path.name + ".tmp") temporary.write_text(content, encoding="utf-8", newline="\n") temporary.replace(path)Technical Analysis
The renderer constructs a deterministic temporary pathname by appending
.tmpto the output filename. It neither creates this file exclusively nor verifies that it is a regular file rather than a symbolic link.Python's
Path.write_text()follows symbolic links. An attacker who can write to the output directory can therefore create the predictable temporary path as a symbolic link to another file before rendering begins. When the renderer writes the generated report, it follows the link and truncates or replaces the target file's contents using the renderer process's privileges.The subsequent
temporary.replace(path)renames the temporary directory entry, which may itself be the attacker-created symlink, over the intended report path. Consequently, both unintended target-file corruption and an unexpected symlink at the final output location may occur.This behavior contradicts the documented path contract, which states that symbolic-link targets must be refused.
Attack Path
- Identify or influence the report output path, such as
/shared/reports/report.html. - Obtain write access to its parent directory.
- Create the predictable temporary pathname as a symboli ...[truncated 1349 chars]
- Identify or influence the report output path, such as
- Remediation
View remediation
Remediation Suggestions
- Replace the deterministic
.tmppathname with a securely generated temporary file in the destination directory usingtempfile.mkstemp()ortempfile.NamedTemporaryFile(delete=False). - Use exclusive creation semantics so an existing file or symlink cannot be reused.
- Verify the output path, temporary path, and relevant parent directories with
lstat()rather than checks that follow symbolic links. - Refuse output paths that are symbolic links, including dangling symbolic links.
- Write and flush the content through the securely opened file descriptor, optionally call
os.fsync(), and then useos.replace()for atomic installation. - Apply restrictive permissions to the temporary file.
- Remove the temporary file in a
finallyblock if validation, writing, or replacement fails. - Where output directories are shared, ensure they are not writable by untrusted users.
- Add regression tests that pre-create both the output and temporary paths as symbolic links and verify that rendering fails without modifying their targets.
A secure implementation should follow this pattern:
python import os import tempfile def write_output(path: Path, content: str, overwrite: bool) -> None: if path.is_symlink(): fail(f"Output path must not be a symbolic link: {path}") if path.exists() and not overwrite: fail(f"Refusing to overwrite existing file without --overwrite: {path}") if path.exists() and path.is_dir(): fail(f"Output path is a directory: {path}") path.parent.mkdir(parents=True, exist_ok=True) fd, temporary_name = tempfile.mkstemp( prefix=f".{path.name}.", suffix=".tmp", dir=path.parent, text=True, ) temporary = Path(temporary_name) try: with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as handle: handle.write(content) handle.flush() ...[truncated 158 chars]- Replace the deterministic
