T09 · Insecure Skill Coding Practices
- Location
scripts/rate-limiter.py:203- Finding
Predictable Temporary File Allows Symlink-Based File Truncation
- Content
View full analysis
Vulnerability Details
File Location:
scripts/rate-limiter.py, lines 203-210
Vulnerability Type: Predictable temporary file and unsafe symbolic-link handling
Risk Level: MediumVulnerable Code:
python def save_state(state): STATE_PATH.parent.mkdir(parents=True, exist_ok=True) tmp = STATE_PATH.with_suffix(".tmp") # Write with restricted permissions (0o600) fd = os.open(str(tmp), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) with os.fdopen(fd, "w") as f: f.write(json.dumps(state, indent=2) + "\n") tmp.rename(STATE_PATH)Technical Analysis
The state-saving operation always uses a predictable temporary path derived from
STATE_PATH. For the defaultrate-limit-state.jsonstate file, the temporary file israte-limit-state.tmp.The temporary path is opened with
O_CREAT | O_TRUNC, but without protections such asO_EXCLorO_NOFOLLOW. Consequently, if an attacker can write to the state directory, they can pre-create the temporary path as a symbolic link to another file. When the rate limiter next saves its state,os.open()follows that symbolic link, truncates the linked target, and writes JSON state data into it.The application-level lock does not prevent this attack because it coordinates cooperating rate-limiter processes through a separate
.lockfile; it does not stop another local process from creating or replacing the predictable temporary path.Attack Path
- The attacker obtains write access to the directory containing the configured state file.
- The attacker predicts the temporary filename by replacing the state file suffix with
.tmp. - The attacker creates that path as a symbolic link to a target file writable by the rate-limiter process.
- A user or automated process invokes any command that saves state, such as
gate,record,status,pause,resume,set-limit, orreset. os.open()follows the maliciou ...[truncated 978 chars]
- Remediation
View remediation
Remediation Suggestions
- Create a randomized temporary file in the same directory as the destination by using
tempfile.NamedTemporaryFileortempfile.mkstemp. - Open the temporary file with exclusive-creation and symbolic-link protections where supported, including
O_CREAT,O_EXCL, andO_NOFOLLOW. - Verify that the created object is a regular file with
os.fstat()before writing. - Preserve restrictive permissions such as mode
0600. - Flush Python buffers and call
os.fsync()before replacement to improve crash safety. - Atomically install the completed state using
os.replace(). - Optionally verify that the state directory is not writable by untrusted users.
Example hardened implementation:
python import tempfile import stat def save_state(state): STATE_PATH.parent.mkdir(parents=True, exist_ok=True) fd, tmp_name = tempfile.mkstemp( prefix=f".{STATE_PATH.name}.", suffix=".tmp", dir=str(STATE_PATH.parent), ) try: os.fchmod(fd, 0o600) file_info = os.fstat(fd) if not stat.S_ISREG(file_info.st_mode): raise OSError("Temporary state path is not a regular file") with os.fdopen(fd, "w") as f: fd = -1 f.write(json.dumps(state, indent=2) + "\n") f.flush() os.fsync(f.fileno()) os.replace(tmp_name, STATE_PATH) finally: if fd != -1: os.close(fd) try: os.unlink(tmp_name) except FileNotFoundError: pass- Create a randomized temporary file in the same directory as the destination by using
