fix: add system PATH fallback for ripgrep resolution (#26777) (#26868)

This commit is contained in:
Coco Sheng
2026-05-13 17:05:37 -04:00
committed by GitHub
parent 41599ce29f
commit 0750b01fe4
11 changed files with 573 additions and 366 deletions
+58
View File
@@ -19,6 +19,7 @@ import {
deduplicateAbsolutePaths,
toAbsolutePath,
toPathKey,
isTrustedSystemPath,
} from './paths.js';
vi.mock('node:fs', async (importOriginal) => {
@@ -797,4 +798,61 @@ describe('normalizePath', () => {
expect(toPathKey('/Tmp/Foo')).toBe(path.normalize('/Tmp/Foo'));
});
});
describe('isTrustedSystemPath', () => {
afterEach(() => {
vi.unstubAllGlobals();
vi.unstubAllEnvs();
});
it('should reject paths in the current working directory', () => {
const cwd = process.cwd();
expect(isTrustedSystemPath(path.join(cwd, 'bin/rg'))).toBe(false);
expect(isTrustedSystemPath(cwd)).toBe(false);
});
it('should allow trusted paths on Windows', () => {
mockPlatform('win32');
vi.stubEnv('SystemRoot', 'C:\\Windows');
vi.stubEnv('ProgramFiles', 'C:\\Program Files');
vi.stubEnv('ProgramFiles(x86)', 'C:\\Program Files (x86)');
expect(isTrustedSystemPath('C:\\Windows\\System32\\rg.exe')).toBe(true);
expect(isTrustedSystemPath('C:\\Program Files\\ripgrep\\rg.exe')).toBe(
true,
);
expect(
isTrustedSystemPath('C:\\Program Files (x86)\\ripgrep\\rg.exe'),
).toBe(true);
// Case insensitive
expect(isTrustedSystemPath('c:\\windows\\system32\\rg.exe')).toBe(true);
// Untrusted paths
expect(isTrustedSystemPath('D:\\Downloads\\rg.exe')).toBe(false);
expect(isTrustedSystemPath('C:\\Users\\User\\rg.exe')).toBe(false);
});
it('should allow trusted paths on macOS and Linux', () => {
mockPlatform('darwin');
expect(isTrustedSystemPath('/usr/bin/rg')).toBe(true);
expect(isTrustedSystemPath('/bin/rg')).toBe(true);
expect(isTrustedSystemPath('/usr/local/bin/rg')).toBe(true);
expect(isTrustedSystemPath('/opt/homebrew/bin/rg')).toBe(true);
expect(
isTrustedSystemPath('/opt/homebrew/Cellar/ripgrep/13.0.0/bin/rg'),
).toBe(true);
expect(
isTrustedSystemPath('/usr/local/Cellar/ripgrep/13.0.0/bin/rg'),
).toBe(true);
expect(isTrustedSystemPath('/usr/sbin/rg')).toBe(true);
expect(isTrustedSystemPath('/sbin/rg')).toBe(true);
// Untrusted paths
expect(isTrustedSystemPath('/home/user/bin/rg')).toBe(false);
expect(isTrustedSystemPath('/tmp/rg')).toBe(false);
expect(isTrustedSystemPath('/Library/rg')).toBe(false);
});
});
});
+44
View File
@@ -512,3 +512,47 @@ export function toPathKey(p: string): string {
const isCaseInsensitive = platform === 'win32' || platform === 'darwin';
return isCaseInsensitive ? norm.toLowerCase() : norm;
}
/**
* Verifies if a path is a trusted system directory.
*/
export function isTrustedSystemPath(filePath: string): boolean {
const normPath = normalizePath(filePath);
// 1. Explicitly reject paths in current working directory to prevent RCE
// Exclude root directories to avoid inadvertently rejecting all system paths.
const normCwd = normalizePath(process.cwd());
const isRoot = normCwd === '/' || /^[a-zA-Z]:[\\/]?$/.test(normCwd);
if (!isRoot && isSubpath(normCwd, normPath)) {
return false;
}
// 2. Allow standard system directories
const platform = process.platform;
if (platform === 'win32') {
const trustedPrefixes = [
process.env['SystemRoot'] || 'C:\\Windows',
process.env['ProgramFiles'] || 'C:\\Program Files',
process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)',
].map((p) => normalizePath(p));
return trustedPrefixes.some(
(prefix) => normPath === prefix || normPath.startsWith(prefix + '/'),
);
} else {
const trustedPrefixes = [
'/usr/bin',
'/bin',
'/usr/local/bin',
'/opt/homebrew/bin',
'/opt/homebrew/Cellar',
'/usr/local/Cellar',
'/usr/sbin',
'/sbin',
].map((p) => normalizePath(p));
return trustedPrefixes.some(
(prefix) => normPath === prefix || normPath.startsWith(prefix + '/'),
);
}
}