T09 · Insecure Skill Coding Practices
Error
- Location
- deploy.js:109
- Finding
- Build output path traversal can publish files outside the project## Vulnerability Details **File Location**: `deploy.js:109-112`, `deploy.js:492-496`, `deploy.js:525-527` **Vulnerability Type**: Unrestricted deployment path / path traversal **Risk Level**: High ### Vulnerable Code ```js pagesBuildOutputDir: process.env.PAGES_BUILD_OUTPUT_DIR || fileConfig.pagesBuildOutputDir || DEFAULTS.pagesBuildOutputDir ``` ```js const buildOutputDir = config.pagesBuildOutputDir || '.'; const buildPath = path.resolve(projectPath, buildOutputDir); if (!fs.existsSync(buildPath)) { error(`Build output directory does not exist: ${buildPath}`); } ``` ```js ['pages', 'deploy', buildOutputDir, `--project-name=${name}`, `--branch=${branch}`] ``` ### Technical Analysis The build output directory is accepted from an environment variable or configuration file without enforcing that it remains inside the selected project directory. `path.resolve(projectPath, buildOutputDir)` permits both absolute paths and parent-directory traversal such as `../`. The code only verifies that the resolved path exists. It does not: - Verify that the path is a directory. - Confirm that it is contained within `projectPath`. - Reject absolute paths. - Detect symbolic links that resolve outside the project. - Pass the validated canonical path to Wrangler. Consequently, Wrangler can be instructed to deploy an arbitrary directory that the current operating-system user can read. ### Attack Path 1. An attacker influences the environment or deployment configuration, or convinces the operator to use an unsafe configuration. 2. The attacker sets `PAGES_BUILD_OUTPUT_DIR` or `pagesBuildOutputDir` to a value such as `../`, `../../sensitive-directory`, or an absolute path. 3. `path.resolve()` resolves the value outside the intended project. 4. The existence check succeeds because the external path exists. 5. The untrusted path is passed to `wrangler pages deploy`. 6. Wrangler uploads files from that directory to Cloudflare Pages, making them remotely accessible. # ...[truncated 633 chars]
- Remediation
- ## Remediation Suggestions 1. Resolve and canonicalize both the project path and requested output path: ```js const projectRoot = fs.realpathSync(projectPath); const requestedPath = path.resolve(projectRoot, buildOutputDir); const outputPath = fs.realpathSync(requestedPath); const relative = path.relative(projectRoot, outputPath); if ( relative === '' || (!relative.startsWith('..' + path.sep) && !path.isAbsolute(relative)) ) { // Path is contained within the project. } else { error('Build output directory must be inside the project directory'); } ``` 2. Use `fs.statSync(outputPath).isDirectory()` to require a directory. 3. Reject absolute configuration values unless there is a documented, explicitly approved use case. 4. Resolve symbolic links before performing the containment check. 5. Pass the validated `outputPath`, rather than the original untrusted value, to Wrangler. 6. Display the canonical directory and require explicit confirmation before deployment. 7. Prefer a fixed build directory such as `dist` or `public` over deploying arbitrary configurable paths.
