Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 60 additions & 10 deletions api/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ 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';

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
Expand Down Expand Up @@ -320,34 +322,82 @@ export function triggerIndexing(folderPath) {
try {
const config = getConfig();

// Validate folder path exists
if (!fs.existsSync(folderPath)) {
throw new Error(`Folder does not exist: ${folderPath}`);
if (typeof folderPath !== 'string' || !folderPath.trim()) {
throw new Error('Invalid folder path');
}

if (!fs.statSync(folderPath).isDirectory()) {
throw new Error(`Path is not a directory: ${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');
}

// Accept only a simple safe folder name (no separators, traversal, or special chars)
const requestedFolderName = folderPath.trim();
const sanitizedFolderName = sanitizeFilename(requestedFolderName);
if (
!sanitizedFolderName ||
sanitizedFolderName !== requestedFolderName ||
sanitizedFolderName === '.' ||
sanitizedFolderName === '..' ||
sanitizedFolderName.includes('/') ||
sanitizedFolderName.includes('\\') ||
sanitizedFolderName.includes('\0') ||
!SAFE_FOLDER_NAME_RE.test(sanitizedFolderName)
) {
throw new Error('Invalid folder path');
}

// Resolve from trusted roots only
let canonicalRequestedPath = null;
for (const root of canonicalAllowedRoots) {
const candidatePath = path.join(root, sanitizedFolderName);
if (!fs.existsSync(candidatePath)) {
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
continue;
}
const candidateCanonicalPath = fs.realpathSync(candidatePath);
const isWithinRoot =
candidateCanonicalPath === root || candidateCanonicalPath.startsWith(root + path.sep);
if (!isWithinRoot) {
continue;
}
if (!fs.statSync(candidateCanonicalPath).isDirectory()) {
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
continue;
}
canonicalRequestedPath = candidateCanonicalPath;
break;
}

if (!canonicalRequestedPath) {
throw new Error(`Folder does not exist in configured project folders: ${folderPath}`);
}

// Import the Indexer class
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) {
Expand Down
6 changes: 3 additions & 3 deletions mcp/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {}
}
Loading