T09 · Insecure Skill Coding Practices
Warning
- Location
- index.mjs:6
- Finding
- Predictable Temporary Files Allow Symlink-Based File Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `index.mjs:6,13-15`; `get_joke.sh:13,19` **Vulnerability Type**: Predictable temporary-file path and unsafe file creation **Risk Level**: Medium ### Vulnerable Code `index.mjs:6,13-15`: ```js const LAST_JOKE_FILE = '/tmp/last-joke.txt'; function setLastJoke(id) { writeFileSync(LAST_JOKE_FILE, id); } ``` `get_joke.sh:13,19`: ```bash # Salva em temp file e processa com python echo "$CONTENT" > /tmp/reddit_tiodopave.json PIADA=$(python3 << 'PYEOF' import json, random try: with open('/tmp/reddit_tiodopave.json', 'r') as f: data = json.load(f) ``` ### Technical Analysis Both implementations use fixed, attacker-predictable paths inside the shared `/tmp` directory. Neither implementation securely creates the destination with exclusive semantics nor verifies that the path is a regular file owned by the current user. The Node.js `writeFileSync` operation and the shell redirection both follow symbolic links. A local attacker who can create one of these predictable paths before the skill runs may replace it with a symbolic link to another file writable by the victim account. Running the skill then truncates or overwrites the symlink target. The shell implementation has an additional time-of-check/time-of-use exposure because it writes the response and subsequently reopens the same shared path from Python. Another local process could replace or modify the file between those operations. ### Attack Path 1. A local attacker predicts that the victim will execute the skill. 2. The attacker creates `/tmp/last-joke.txt` or `/tmp/reddit_tiodopave.json` as a symbolic link to a file writable by the victim. 3. The victim invokes the Node.js or shell implementation. 4. The skill opens the predictable path while following the symbolic link. 5. The linked target is truncated and overwritten with a Reddit post ID or downloaded JSON. 6. For the shell implementation, an attacker may alternatively replace the J ...[truncated 984 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Do not place persistent state in a globally predictable `/tmp` path. Store the last-post ID in a private application state directory owned by the user, such as a directory beneath `XDG_STATE_HOME`. - Create the state directory with restrictive permissions, such as mode `0700`, and the state file with mode `0600`. - If temporary storage is required, create a private temporary directory using `fs.mkdtemp()` in Node.js or `mktemp -d` in the shell. - Use exclusive file creation where applicable and reject symbolic links. In Node.js, use appropriate `open` flags and validate the resulting file with `fstat`. - Write state atomically by creating a securely named file in the same private directory and renaming it over the destination. - Remove temporary files and directories using a cleanup handler. - Avoid the intermediate JSON file in `get_joke.sh`. Pipe the `curl` response directly into Python or perform the HTTP request and parsing in one process. - If an intermediate file remains necessary, pass its securely generated name as an argument rather than embedding a fixed path. ]]>
