T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/init-project.js:156
- Finding
- Unrestricted Project Path Allows Arbitrary File Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `scripts/init-project.js`, lines 156-172 and 193-194 **Vulnerability Type**: Path traversal and unsafe file overwrite **Risk Level**: High ### Vulnerable Code ```javascript const projectPath = options.path || path.join(process.cwd(), 'tasks', projectName); if (!fs.existsSync(projectPath)) { fs.mkdirSync(projectPath, { recursive: true }); } fs.writeFileSync(path.join(projectPath, 'PROJECT.md'), projectMd); fs.writeFileSync(path.join(projectPath, 'CHANGELOG.md'), changelogMd); const testsDir = path.join(projectPath, 'tests'); if (!fs.existsSync(testsDir)) { fs.mkdirSync(testsDir); } ``` The command-line parser assigns an arbitrary argument directly to the destination path: ```javascript } else if (args[i] === '--path') { options.path = args[++i]; } ``` ### Technical Analysis The script uses the user-controlled `projectName` and `--path` values without validation or containment checks. When `--path` is omitted, `projectName` is appended to `tasks` using `path.join()`. A value containing traversal components, such as `../../destination`, can resolve outside the intended tasks directory. When `--path` is supplied, it completely replaces the default destination and can point to any absolute or relative location writable by the process. The script then: 1. Recursively creates the selected directory. 2. Writes `PROJECT.md` with default overwrite behavior. 3. Writes `CHANGELOG.md` with default overwrite behavior. 4. Creates a `tests` directory. `fs.writeFileSync()` overwrites existing files unless an exclusive creation flag is specified. No canonical-path comparison, allowlist, collision check, or overwrite confirmation is performed. ### Attack Path 1. An attacker supplies a crafted project name or persuades the agent to initialize a project with one. 2. The attacker uses a traversal name such as `../../target-directory`, or supplies an arbitrary destination through `--path`. 3. `path.join()` reso ...[truncated 1227 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Establish a fixed, trusted workspace root and resolve every destination against it: ```javascript const workspaceRoot = path.resolve(process.cwd(), 'tasks'); const projectPath = path.resolve(workspaceRoot, projectName); const relative = path.relative(workspaceRoot, projectPath); if ( relative === '' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative) ) { throw new Error('Project path must remain inside the tasks directory'); } ``` 2. Restrict project names to a conservative allowlist, such as letters, digits, underscores, and hyphens. Reject path separators, `.` and `..` path components, null bytes, and absolute paths. 3. Remove `--path` unless arbitrary destinations are required. If it is required, limit it to configured workspace roots and apply the same canonical containment validation. 4. Refuse to overwrite existing project files by default: ```javascript fs.writeFileSync(path.join(projectPath, 'PROJECT.md'), projectMd, { flag: 'wx', mode: 0o600 }); ``` 5. Require an explicit `--force` option and clear confirmation before replacing existing files. 6. Validate the destination again after directory creation and account for symbolic links by resolving the nearest existing parent with `fs.realpathSync()`. 7. Add tests covering absolute paths, traversal sequences, symbolic-link escapes, existing files, missing option values, and nested destinations. ]]>
