From e9503fcb2a907d19224d52ce03ea00e5329cd58d Mon Sep 17 00:00:00 2001 From: Hardik Dholariya <66554974+webdevdot@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:48:32 +0530 Subject: [PATCH 1/5] Potential fix for code scanning alert no. 2: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- api/dashboard.js | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/api/dashboard.js b/api/dashboard.js index c597f02..a519b4b 100644 --- a/api/dashboard.js +++ b/api/dashboard.js @@ -320,12 +320,33 @@ export function triggerIndexing(folderPath) { try { const config = getConfig(); - // Validate folder path exists - if (!fs.existsSync(folderPath)) { + if (typeof folderPath !== 'string' || !folderPath.trim()) { + throw new Error('Invalid folder path'); + } + + // Resolve user-provided path to a canonical absolute path + const requestedPath = path.resolve(folderPath); + if (!fs.existsSync(requestedPath)) { throw new Error(`Folder does not exist: ${folderPath}`); } + const canonicalRequestedPath = fs.realpathSync(requestedPath); + + // Allow indexing only within configured project folders + const allowedRoots = Array.isArray(config.PROJECT_FOLDERS) ? config.PROJECT_FOLDERS : []; + const canonicalAllowedRoots = allowedRoots + .map(root => path.resolve(root)) + .filter(root => fs.existsSync(root)) + .map(root => fs.realpathSync(root)); + + const isAllowed = canonicalAllowedRoots.some(root => + canonicalRequestedPath === root || canonicalRequestedPath.startsWith(root + path.sep) + ); + + if (!isAllowed) { + throw new Error(`Folder is outside configured project folders: ${folderPath}`); + } - if (!fs.statSync(folderPath).isDirectory()) { + if (!fs.statSync(canonicalRequestedPath).isDirectory()) { throw new Error(`Path is not a directory: ${folderPath}`); } @@ -333,21 +354,21 @@ export function triggerIndexing(folderPath) { const { Indexer } = require('../indexer/indexer.js'); // Create and run indexer for the folder - const indexer = new Indexer(folderPath); + const indexer = new Indexer(canonicalRequestedPath); // Run indexing (asynchronous) indexer.index() .then(() => { - console.log(`✓ Indexing completed for ${folderPath}`); + console.log(`✓ Indexing completed for ${canonicalRequestedPath}`); }) .catch(err => { - console.error(`✗ Indexing failed for ${folderPath}: ${err.message}`); + console.error(`✗ Indexing failed for ${canonicalRequestedPath}: ${err.message}`); }); return { success: true, - message: `Indexing started for ${folderPath}`, - folder: folderPath, + message: `Indexing started for ${canonicalRequestedPath}`, + folder: canonicalRequestedPath, startedAt: new Date().toISOString(), }; } catch (error) { From 23447c8e9f5767d6916f471fbff7b0f6cb4caa54 Mon Sep 17 00:00:00 2001 From: Hardik Dholariya <66554974+webdevdot@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:54:43 +0530 Subject: [PATCH 2/5] Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- api/dashboard.js | 23 +++++++++++++++-------- mcp/server.js | 6 +++--- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/api/dashboard.js b/api/dashboard.js index a519b4b..21549c0 100644 --- a/api/dashboard.js +++ b/api/dashboard.js @@ -324,20 +324,27 @@ export function triggerIndexing(folderPath) { throw new Error('Invalid folder path'); } - // Resolve user-provided path to a canonical absolute path - const requestedPath = path.resolve(folderPath); + // Build canonical allowlist roots first + const allowedRoots = Array.isArray(config.PROJECT_FOLDERS) ? config.PROJECT_FOLDERS : []; + const canonicalAllowedRoots = allowedRoots + .filter(root => typeof root === 'string' && root.trim()) + .map(root => path.resolve(root)) + .filter(root => fs.existsSync(root)) + .map(root => fs.realpathSync(root)) + .filter(root => fs.statSync(root).isDirectory()); + + if (canonicalAllowedRoots.length === 0) { + throw new Error('No valid configured project folders available'); + } + + // Resolve user-provided path and canonicalize + const requestedPath = path.resolve(folderPath.trim()); if (!fs.existsSync(requestedPath)) { throw new Error(`Folder does not exist: ${folderPath}`); } const canonicalRequestedPath = fs.realpathSync(requestedPath); // Allow indexing only within configured project folders - const allowedRoots = Array.isArray(config.PROJECT_FOLDERS) ? config.PROJECT_FOLDERS : []; - const canonicalAllowedRoots = allowedRoots - .map(root => path.resolve(root)) - .filter(root => fs.existsSync(root)) - .map(root => fs.realpathSync(root)); - const isAllowed = canonicalAllowedRoots.some(root => canonicalRequestedPath === root || canonicalRequestedPath.startsWith(root + path.sep) ); diff --git a/mcp/server.js b/mcp/server.js index 6433d12..2a53b52 100644 --- a/mcp/server.js +++ b/mcp/server.js @@ -477,9 +477,9 @@ function createExpressServer(port = 3000) { app.post('/api/index', (req, res) => { try { - const { folderPath } = req.body; - if (!folderPath) { - return res.status(400).json({ error: 'folderPath is required' }); + const { folderPath } = req.body ?? {}; + if (typeof folderPath !== 'string' || !folderPath.trim()) { + return res.status(400).json({ error: 'folderPath must be a non-empty string' }); } const result = triggerIndexing(folderPath); res.json(result); From 4633cc43405dfb7f82e5f88d174f5b1d02d80e82 Mon Sep 17 00:00:00 2001 From: Hardik Dholariya <66554974+webdevdot@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:00:25 +0530 Subject: [PATCH 3/5] Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- api/dashboard.js | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/api/dashboard.js b/api/dashboard.js index 21549c0..5aaf09f 100644 --- a/api/dashboard.js +++ b/api/dashboard.js @@ -337,24 +337,40 @@ export function triggerIndexing(folderPath) { throw new Error('No valid configured project folders available'); } - // Resolve user-provided path and canonicalize - const requestedPath = path.resolve(folderPath.trim()); - if (!fs.existsSync(requestedPath)) { - throw new Error(`Folder does not exist: ${folderPath}`); + // Accept only a simple folder name (no path separators or traversal) + const requestedFolderName = folderPath.trim(); + if ( + requestedFolderName === '.' || + requestedFolderName === '..' || + requestedFolderName.includes('/') || + requestedFolderName.includes('\\') || + requestedFolderName.includes('\0') + ) { + throw new Error('Invalid folder path'); } - const canonicalRequestedPath = fs.realpathSync(requestedPath); - - // Allow indexing only within configured project folders - const isAllowed = canonicalAllowedRoots.some(root => - canonicalRequestedPath === root || canonicalRequestedPath.startsWith(root + path.sep) - ); - if (!isAllowed) { - throw new Error(`Folder is outside configured project folders: ${folderPath}`); + // Resolve from trusted roots only + let canonicalRequestedPath = null; + for (const root of canonicalAllowedRoots) { + const candidatePath = path.join(root, requestedFolderName); + if (!fs.existsSync(candidatePath)) { + continue; + } + const candidateCanonicalPath = fs.realpathSync(candidatePath); + const isWithinRoot = + candidateCanonicalPath === root || candidateCanonicalPath.startsWith(root + path.sep); + if (!isWithinRoot) { + continue; + } + if (!fs.statSync(candidateCanonicalPath).isDirectory()) { + continue; + } + canonicalRequestedPath = candidateCanonicalPath; + break; } - if (!fs.statSync(canonicalRequestedPath).isDirectory()) { - throw new Error(`Path is not a directory: ${folderPath}`); + if (!canonicalRequestedPath) { + throw new Error(`Folder does not exist in configured project folders: ${folderPath}`); } // Import the Indexer class From 1a6ffcbb4deba3df9176948da787d536fe655415 Mon Sep 17 00:00:00 2001 From: Hardik Dholariya <66554974+webdevdot@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:58:05 +0530 Subject: [PATCH 4/5] Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- api/dashboard.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api/dashboard.js b/api/dashboard.js index 5aaf09f..e0d409e 100644 --- a/api/dashboard.js +++ b/api/dashboard.js @@ -7,6 +7,7 @@ import { updateConfig } from '../config/manager.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const PROJECT_ROOT = path.dirname(__dirname); +const SAFE_FOLDER_NAME_RE = /^[A-Za-z0-9._ -]+$/; /** * Get database connection @@ -337,14 +338,15 @@ export function triggerIndexing(folderPath) { throw new Error('No valid configured project folders available'); } - // Accept only a simple folder name (no path separators or traversal) + // Accept only a simple safe folder name (no separators, traversal, or special chars) const requestedFolderName = folderPath.trim(); if ( requestedFolderName === '.' || requestedFolderName === '..' || requestedFolderName.includes('/') || requestedFolderName.includes('\\') || - requestedFolderName.includes('\0') + requestedFolderName.includes('\0') || + !SAFE_FOLDER_NAME_RE.test(requestedFolderName) ) { throw new Error('Invalid folder path'); } From a66416c51e2c1fd0025d80ab3fc757dbd6479d2b Mon Sep 17 00:00:00 2001 From: Hardik Dholariya <66554974+webdevdot@users.noreply.github.com> Date: Tue, 14 Apr 2026 23:00:13 +0530 Subject: [PATCH 5/5] Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- api/dashboard.js | 18 +++++++++++------- package.json | 3 ++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/api/dashboard.js b/api/dashboard.js index e0d409e..e0adfdc 100644 --- a/api/dashboard.js +++ b/api/dashboard.js @@ -2,6 +2,7 @@ import fs from 'fs'; import path from 'path'; import Database from 'better-sqlite3'; import { fileURLToPath } from 'url'; +import sanitizeFilename from 'sanitize-filename'; import { getConfig } from '../config/loader.js'; import { updateConfig } from '../config/manager.js'; @@ -340,13 +341,16 @@ export function triggerIndexing(folderPath) { // Accept only a simple safe folder name (no separators, traversal, or special chars) const requestedFolderName = folderPath.trim(); + const sanitizedFolderName = sanitizeFilename(requestedFolderName); if ( - requestedFolderName === '.' || - requestedFolderName === '..' || - requestedFolderName.includes('/') || - requestedFolderName.includes('\\') || - requestedFolderName.includes('\0') || - !SAFE_FOLDER_NAME_RE.test(requestedFolderName) + !sanitizedFolderName || + sanitizedFolderName !== requestedFolderName || + sanitizedFolderName === '.' || + sanitizedFolderName === '..' || + sanitizedFolderName.includes('/') || + sanitizedFolderName.includes('\\') || + sanitizedFolderName.includes('\0') || + !SAFE_FOLDER_NAME_RE.test(sanitizedFolderName) ) { throw new Error('Invalid folder path'); } @@ -354,7 +358,7 @@ export function triggerIndexing(folderPath) { // Resolve from trusted roots only let canonicalRequestedPath = null; for (const root of canonicalAllowedRoots) { - const candidatePath = path.join(root, requestedFolderName); + const candidatePath = path.join(root, sanitizedFolderName); if (!fs.existsSync(candidatePath)) { continue; } diff --git a/package.json b/package.json index c1c4856..1824619 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "chokidar": "^3.5.3", "express": "^4.18.2", "fast-glob": "^3.3.2", - "express-rate-limit": "^8.3.2" + "express-rate-limit": "^8.3.2", + "sanitize-filename": "^1.6.4" }, "devDependencies": {} }