T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/dancetech_post.js:360
- Finding
- Externally Generated File Paths Permit Writes Outside the Repository<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dancetech_post.js:156-173, 360-364` **Vulnerability Type**: Path traversal through untrusted model output **Risk Level**: High ### Vulnerable Code ```javascript const response = await fetch('https://openrouter.ai/api/v1/chat/completions', { method: 'POST', headers: { 'Authorization': `Bearer ${env.OPENROUTER_API_KEY}`, 'Content-Type': 'application/json', 'HTTP-Referer': 'https://openclaw.ai', 'X-Title': 'DanceTech Code Gen' }, body: JSON.stringify({ model: 'qwen/qwen3-coder', messages: [{ role: 'user', content: prompt }], max_tokens: maxTokens, temperature: 0.2 }) }); if (!response.ok) { const err = await response.text(); throw new Error(`OpenRouter ${response.status}: ${err}`); } const data = await response.json(); let content = data.choices[0].message.content; content = content.replace(/^```json\s*|\s*```$/g, '').trim(); return JSON.parse(content); ``` ```javascript Object.entries(files).forEach(([filePath, content]) => { const fullPath = path.join(repoDir, filePath); fs.mkdirSync(path.dirname(fullPath), { recursive: true }); fs.writeFileSync(fullPath, content, 'utf8'); }); ``` ### Technical Analysis The file map returned by an external language model is accepted without schema or path validation. Each model-controlled object key is treated as a relative file path and joined to `repoDir`. `path.join()` normalizes traversal segments but does not guarantee that the result remains below the intended base directory. A key such as `../../scripts/start_all.js` can therefore resolve outside the generated repository and overwrite another writable project file. The security railcard runs only after the files have been written. It scans for secret patterns, not path traversal or unauthorized file changes, so it does not prevent this vulnerability. ### Attack Path 1. The scheduled script submits a code-generation prompt to OpenRouter. 2. A compromis ...[truncated 1057 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Resolve and validate every generated path before creating directories or writing files: ```javascript const base = path.resolve(repoDir); for (const [generatedPath, content] of Object.entries(files)) { if (path.isAbsolute(generatedPath) || generatedPath.includes('\0')) { throw new Error(`Invalid generated path: ${generatedPath}`); } const destination = path.resolve(base, generatedPath); if (!destination.startsWith(base + path.sep)) { throw new Error(`Generated path escapes repository: ${generatedPath}`); } fs.mkdirSync(path.dirname(destination), { recursive: true }); fs.writeFileSync(destination, String(content), { encoding: 'utf8', flag: 'wx' }); } ``` 2. Use a strict schema that requires a plain object whose keys and values are strings. 3. Maintain an allowlist of files expected for each generation track. 4. Reject `..`, absolute paths, drive-prefixed paths, control characters, symbolic-link destinations, and unexpected filenames. 5. Generate files in an isolated temporary directory with restrictive permissions and no sensitive files nearby. 6. Review or sandbox generated code before publishing it. 7. Add tests for traversal keys including `../`, nested traversal, absolute paths, Windows path forms, and symbolic-link escape attempts. ]]>
