fix: prevent path traversal via workstream name sanitization
Validates workstream name at all entry points — CLI --ws flag, GSD_WORKSTREAM env var, active-workstream file, and cmdWorkstreamSet — blocking names that don't match [a-zA-Z0-9_-]+. Also fixes getActiveWorkstream to use planningRoot() consistently and validates names read from the active-workstream file before using them in path joins. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -205,6 +205,10 @@ async function main() {
|
||||
} else {
|
||||
ws = getActiveWorkstream(cwd);
|
||||
}
|
||||
// Validate workstream name to prevent path traversal attacks.
|
||||
if (ws && !/^[a-zA-Z0-9_-]+$/.test(ws)) {
|
||||
error('Invalid workstream name: must be alphanumeric, hyphens, and underscores only');
|
||||
}
|
||||
// Set env var so all modules (planningDir, planningPaths) auto-resolve workstream paths
|
||||
if (ws) {
|
||||
process.env.GSD_WORKSTREAM = ws;
|
||||
|
||||
@@ -568,11 +568,11 @@ function planningPaths(cwd, ws) {
|
||||
* Returns null if no active workstream or file doesn't exist.
|
||||
*/
|
||||
function getActiveWorkstream(cwd) {
|
||||
const filePath = path.join(cwd, '.planning', 'active-workstream');
|
||||
const filePath = path.join(planningRoot(cwd), 'active-workstream');
|
||||
try {
|
||||
const name = fs.readFileSync(filePath, 'utf-8').trim();
|
||||
if (!name) return null;
|
||||
const wsDir = path.join(cwd, '.planning', 'workstreams', name);
|
||||
if (!name || !/^[a-zA-Z0-9_-]+$/.test(name)) return null;
|
||||
const wsDir = path.join(planningRoot(cwd), 'workstreams', name);
|
||||
if (!fs.existsSync(wsDir)) return null;
|
||||
return name;
|
||||
} catch {
|
||||
|
||||
@@ -352,6 +352,11 @@ function cmdWorkstreamSet(cwd, name, raw) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
||||
output({ active: null, error: 'invalid_name', message: 'Workstream name must be alphanumeric, hyphens, and underscores only' }, raw);
|
||||
return;
|
||||
}
|
||||
|
||||
const wsDir = path.join(planningRoot(cwd), 'workstreams', name);
|
||||
if (!fs.existsSync(wsDir)) {
|
||||
output({ active: null, error: 'not_found', workstream: name }, raw);
|
||||
|
||||
Reference in New Issue
Block a user