fix: codeql user value provided path validation

This commit is contained in:
simosmik
2026-03-10 21:16:24 +00:00
parent 8ddeeb0ce8
commit aaa14b9fc0

View File

@@ -61,10 +61,19 @@ function validateBranchName(branch) {
return branch;
}
function validateFilePath(file) {
function validateFilePath(file, projectPath) {
if (!file || file.includes('\0')) {
throw new Error('Invalid file path');
}
// Prevent path traversal: resolve the file relative to the project root
// and ensure the result stays within the project directory
if (projectPath) {
const resolved = path.resolve(projectPath, file);
const normalizedRoot = path.resolve(projectPath) + path.sep;
if (!resolved.startsWith(normalizedRoot) && resolved !== path.resolve(projectPath)) {
throw new Error('Invalid file path: path traversal detected');
}
}
return file;
}
@@ -75,15 +84,33 @@ function validateRemoteName(remote) {
return remote;
}
function validateProjectPath(projectPath) {
if (!projectPath || projectPath.includes('\0')) {
throw new Error('Invalid project path');
}
const resolved = path.resolve(projectPath);
// Must be an absolute path after resolution
if (!path.isAbsolute(resolved)) {
throw new Error('Invalid project path: must be absolute');
}
// Block obviously dangerous paths
if (resolved === '/' || resolved === path.sep) {
throw new Error('Invalid project path: root directory not allowed');
}
return resolved;
}
// Helper function to get the actual project path from the encoded project name
async function getActualProjectPath(projectName) {
let projectPath;
try {
return await extractProjectDirectory(projectName);
projectPath = await extractProjectDirectory(projectName);
} catch (error) {
console.error(`Error extracting project directory for ${projectName}:`, error);
// Fallback to the old method
return projectName.replace(/-/g, '/');
projectPath = projectName.replace(/-/g, '/');
}
return validateProjectPath(projectPath);
}
// Helper function to strip git diff headers
@@ -230,7 +257,7 @@ router.get('/diff', async (req, res) => {
await validateGitRepository(projectPath);
// Validate file path
validateFilePath(file);
validateFilePath(file, projectPath);
// Check if file is untracked or deleted
const { stdout: statusOutput } = await spawnAsync('git', ['status', '--porcelain', file], { cwd: projectPath });
@@ -295,7 +322,7 @@ router.get('/file-with-diff', async (req, res) => {
await validateGitRepository(projectPath);
// Validate file path
validateFilePath(file);
validateFilePath(file, projectPath);
// Check file status
const { stdout: statusOutput } = await spawnAsync('git', ['status', '--porcelain', file], { cwd: projectPath });
@@ -406,7 +433,7 @@ router.post('/commit', async (req, res) => {
// Stage selected files
for (const file of files) {
validateFilePath(file);
validateFilePath(file, projectPath);
await spawnAsync('git', ['add', file], { cwd: projectPath });
}
@@ -610,7 +637,7 @@ router.post('/generate-commit-message', async (req, res) => {
let diffContext = '';
for (const file of files) {
try {
validateFilePath(file);
validateFilePath(file, projectPath);
const { stdout } = await spawnAsync(
'git', ['diff', 'HEAD', '--', file],
{ cwd: projectPath }
@@ -1139,7 +1166,7 @@ router.post('/discard', async (req, res) => {
await validateGitRepository(projectPath);
// Validate file path
validateFilePath(file);
validateFilePath(file, projectPath);
// Check file status to determine correct discard command
const { stdout: statusOutput } = await spawnAsync('git', ['status', '--porcelain', file], { cwd: projectPath });
@@ -1188,7 +1215,7 @@ router.post('/delete-untracked', async (req, res) => {
await validateGitRepository(projectPath);
// Validate file path
validateFilePath(file);
validateFilePath(file, projectPath);
// Check if file is actually untracked
const { stdout: statusOutput } = await spawnAsync('git', ['status', '--porcelain', file], { cwd: projectPath });