T09 · Insecure Skill Coding Practices
- Location
lib/artifacts.js:15- Finding
Path Traversal Through User-Controlled Run ID Allows Writes Outside the Artifact Directory
- Content
View full analysis
s.trim()); if (key === '--model') args.model = value; if (key === '--run_id') args.runId = value; if (key === '--timeout_sec') args.timeoutSec = Number(value); if (key === '--poll_interval') args.pollInterval = Number(value); ``` The untrusted value is passed into artifact storage: ```js // lib/generate-video.js:181-198 async function runGenerateVideoStatus({ taskId, runId, skillDir, model = null, wait = false, pollInterval = 3, timeoutSec = 600, client = defaultClient(), }) { const artifacts = artifactsForRun(skillDir, runId); artifacts.ensure(); const raw = wait ? await pollGenerate(client, String(taskId), pollInterval, timeoutSec) : await client.getTaskStatus(String(taskId)); ``` The artifact root is constructed without validating the run ID or checking that the resulting path remains beneath `.artifacts`: ```js // lib/artifacts.js:30-35 function artifactsRootForSkill(skillDir) { return path.join(skillDir, '.artifacts'); } function artifactsForRun(skillDir, runId) { return new Artifacts(path.join(artifactsRootForSkill(skillDir), runId)); } ``` The resulting path is subsequently used for recursive directory creation and file writes: ```js // lib/artifacts.js:8-26 ensure() { for (const name of ['input', 'transcript', 'vision', 'outputs', 'logs']) { fs.mkdirSync(path.join(this.root, name), { recursive: true }); } } writeJson(relPa ...[truncated 3311 chars]- Remediation
View remediation
/.artifacts`. ]]>
