T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/tts_cli.js:195
- Finding
- Path Traversal and Arbitrary File Overwrite Through --temp-id## Vulnerability Details **File Location**: `scripts/tts_cli.js`, lines 89-96, 195-203, and 273-312 **Vulnerability Type**: Path traversal leading to writes outside the intended output directory **Risk Level**: Medium ### Vulnerable Code ```js return { text: values.text.trim(), voice, instructions: values.instructions ? values.instructions.trim() : undefined, optimizeInstructions: values["optimize-instructions"], outputDir: values["output-dir"].trim(), tempId: values["temp-id"] ? values["temp-id"].trim() : undefined }; ``` ```js function buildOutputPaths({ outputDir, tempId }) { const folderName = tempId ? `tmp-${tempId}` : `tmp-${Date.now()}`; const folderPath = path.resolve(outputDir, folderName); return { folderPath, sourcePath: path.join(folderPath, "source.mp3"), opusPath: path.join(folderPath, "audio.opus"), requestPath: path.join(folderPath, "request.json"), responsePath: path.join(folderPath, "response.json") }; } ``` ```js const paths = buildOutputPaths({ outputDir: args.outputDir, tempId: args.tempId }); await fsImpl.mkdir(paths.folderPath, { recursive: true }); await fsImpl.writeFile( paths.requestPath, JSON.stringify( { text: args.text, voice: args.voice, instructions: args.instructions }, null, 2 ), "utf8" ); await fsImpl.writeFile( paths.responsePath, JSON.stringify( { audio_url: ttsResult.audioUrl }, null, 2 ), "utf8" ); const audioBuffer = await downloadAudio({ audioUrl: ttsResult.audioUrl, fetchImpl, fsImpl }); await fsImpl.writeFile(paths.sourcePath, audioBuffer); ``` ### Technical Analysis The caller-controlled `--temp-id` value is trimmed but is not otherwise validated. It may therefore contain path separators and parent-directory components such as `../`. The code prefixes the ...[truncated 1989 chars]
- Remediation
- ## Remediation Suggestions 1. Prefer removing the caller-controlled temporary directory name and generate it internally with `fs.mkdtemp()` or a cryptographically random identifier. 2. If `--temp-id` must remain supported, restrict it to a single safe path component, for example: ```js if (!/^[A-Za-z0-9_-]+$/.test(tempId)) { throw new CliError("Invalid temporary ID", { code: "INVALID_TEMP_ID" }); } ``` 3. Explicitly reject absolute paths, `/`, `\`, `.` and `..` path components. 4. Perform a containment check after resolution: ```js const base = path.resolve(outputDir); const folderPath = path.resolve(base, `tmp-${tempId}`); if (!folderPath.startsWith(base + path.sep)) { throw new CliError("Temporary path escapes output directory", { code: "INVALID_TEMP_PATH" }); } ``` 5. Resolve or canonicalize the output base and consider symlink-related boundary violations when the output directory may be controlled by another user. 6. Use exclusive file creation where overwriting is unnecessary, and apply restrictive directory and file permissions. 7. Add tests covering parent traversal, absolute paths, Windows separators, mixed separators, repeated traversal components, and symlink-based escape attempts.
