T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/project.js:135
- Finding
- Arbitrary File Write Through Unvalidated Plan Paths## Vulnerability Details **File Location**: `scripts/project.js:135-151` **Vulnerability Type**: Path traversal and arbitrary file write **Risk Level**: High ### Vulnerable Code ```javascript function applyPlan(root, planId) { const projDir = path.join(root, '.project'); const planPath = path.join(projDir, 'history', 'plans', `${planId}.json`); if (!exists(planPath)) die(`Plan not found: ${planId}`); const plan = readJson(planPath); const applied = []; for (const w of plan.writes || []) { const dst = path.join(root, w.path); if (w.kind === 'json') { writeJson(dst, w.content); } else { fs.mkdirSync(path.dirname(dst), { recursive: true }); fs.writeFileSync(dst, String(w.content)); } applied.push({ path: w.path, kind: w.kind }); } ``` ### Technical Analysis The application treats the contents of stored plan files as trusted and writes every `writes[].path` entry without validating its type, format, or final resolved location. `path.join(root, w.path)` normalizes path traversal sequences but does not guarantee that the resulting destination remains inside `root`. A path such as `../../target-file` can escape the project directory. The implementation also does not reject destinations that traverse through symbolic links pointing outside the project. The plan has no schema validation, integrity protection, signature, ownership check, or restriction requiring writes to remain inside `.project`. Anyone able to create or modify a plan file can therefore control both the destination and content of subsequent writes. ### Attack Path 1. An attacker obtains the ability to create or modify a JSON plan under `.project/history/plans/`. This can occur through a malicious or shared repository, compromised workspace content, or another process with repository write access. 2. The attacker adds a plan entry similar to: ```json { "writes": [ ...[truncated 1241 chars]
- Remediation
- ## Remediation Suggestions 1. Validate every plan against a strict schema before applying it. Require a known `kind`, a relative string path, and bounded content size. 2. Resolve destinations and enforce containment: ```javascript const allowedRoot = path.resolve(root, '.project'); const dst = path.resolve(root, w.path); const relative = path.relative(allowedRoot, dst); if ( relative === '' || relative.startsWith(`..${path.sep}`) || relative === '..' || path.isAbsolute(relative) ) { throw new Error(`Plan path escapes allowed directory: ${w.path}`); } ``` 3. Restrict plan writes to an explicit allowlist of `.project` files and directories rather than the entire project root. 4. Reject absolute paths, null bytes, traversal components, device paths, and unsupported file types. 5. Detect symlink escapes by resolving the nearest existing parent with `fs.realpathSync` and verifying that it remains inside the canonical allowed root. 6. Open destination files using safe flags where appropriate, and avoid following symlinks when the platform supports such controls. 7. Protect saved plans against modification by recording a cryptographic digest or signature at creation and verifying it immediately before application. 8. Present the normalized list of destination files to the user and require confirmation before applying plans that modify sensitive locations.
