T09 · Insecure Skill Coding Practices
- Location
scripts/scaffold-skill.mjs:26- Finding
Attacker-Controlled Output Directory Allows Existing File and Symbolic-Link Target Overwrites
- Content
View full analysis
Vulnerability Details
File Location:
scripts/scaffold-skill.mjs, lines 26-47
Vulnerability Type: Unrestricted filesystem destination and unsafe file overwrite
Risk Level: MediumVulnerable Code
js const outDir = outDirArg || path.resolve(process.cwd(), "skills"); const skillDir = path.join(outDir, slug); const refsDir = path.join(skillDir, "references"); const scriptsDir = path.join(skillDir, "scripts"); fs.mkdirSync(refsDir, { recursive: true }); fs.mkdirSync(scriptsDir, { recursive: true }); const skillMd = `---\nname: ${slug}\ndescription: ${JSON.stringify(desc)}\n---\n\n# ${slug}\n\n## What this does\n\n- ${desc}\n\n## First run setup (premium wizard)\n\nIf this skill is not configured yet, run the setup wizard before doing real work.\n\n- Config file (workspace): <workspace>/.skill-config/${slug}.json\n\nWizard rules:\n- Ask only what is necessary to personalize the skill.\n- Keep it to 3 to 8 questions.\n- Show a short summary and ask for confirmation.\n- Persist answers to the config file.\n- Run a tiny test.\n\n## Reconfigure\n\nIf the user says: setup, configure, reconfigure, change settings then re-run the wizard.\n\n## References\n\nSee references/ for examples and deeper docs.\n`; fs.writeFileSync(path.join(skillDir, "SKILL.md"), skillMd, "utf8"); const setupStub = `#!/usr/bin/env node\n/*\nOptional CLI setup helper.\nThis is a fallback for users who prefer terminal over chat.\nWrite config to: <workspace>/.skill-config/${slug}.json\n*/\n\nconsole.log("This skill is configured via chat by default. If you need CLI setup, implement prompts here.");\n`; fs.writeFileSync(path.join(scriptsDir, "setup.mjs"), setupStub, "utf8"); fs.writeFileSync(path.join(refsDir, "README.md"), `# ${slug} references\n\nPut longer docs and examples here.\n`, "utf8"); console.log(JSON.stringify({ ok: true, skillDir }, null, 2));Technical Analysis
The optional
outDirArgis accepted ...[truncated 2362 chars]- Remediation
View remediation
Remediation Suggestions
- Resolve and canonicalize both the approved skills root and requested destination using
path.resolveand, where applicable,fs.realpathSync. - Reject destinations outside the approved root unless the user gives explicit, informed confirmation.
- Refuse to scaffold into an existing Skill directory by default. Require an explicit
--forceoption before replacing any content. - Create new files with exclusive mode, such as
fs.writeFileSync(path, data, { encoding: "utf8", flag: "wx" }), so existing files are not silently overwritten. - Inspect every destination component with
fs.lstatSyncand reject symbolic links before creating directories or files. - Revalidate the resolved parent directory immediately before each write to reduce symbolic-link race opportunities.
- For overwrite mode, display the exact files that will be replaced and require explicit confirmation.
- Where practical, scaffold into a newly created temporary directory under the approved root and atomically rename it into place only after all validation and writes succeed.
- Resolve and canonicalize both the approved skills root and requested destination using
