mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-21 17:23:37 -07:00
fix: handle ENAMETOOLONG gracefully during path resolution
This fix was recovered from a timed-out bot run. It addresses issue #26979 where the CLI would crash if a user provided an extremely long path string in an @ command (e.g. @/aaa...a). Changes: - Updated 'robustRealpath' in 'packages/core/src/utils/paths.ts' to catch and gracefully handle 'ENAMETOOLONG' and 'EINVAL' errors from fs.realpathSync and fs.lstatSync. - Added a defensive try-catch block to 'checkPermissions' in 'packages/cli/src/ui/hooks/atCommandProcessor.ts' to prevent long path strings from crashing the CLI during @ command parsing. - Added regression unit tests to verify the fix.
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
handleAtCommand,
|
||||
escapeAtSymbols,
|
||||
unescapeLiteralAt,
|
||||
checkPermissions,
|
||||
} from './atCommandProcessor.js';
|
||||
import {
|
||||
FileDiscoveryService,
|
||||
@@ -1539,4 +1540,23 @@ describe('unescapeLiteralAt', () => {
|
||||
const input = 'user@example.com and @scope/pkg';
|
||||
expect(unescapeLiteralAt(escapeAtSymbols(input))).toBe(input);
|
||||
});
|
||||
|
||||
describe('checkPermissions', () => {
|
||||
it('should handle ENAMETOOLONG gracefully in checkPermissions', async () => {
|
||||
const longPath = 'a'.repeat(5000);
|
||||
const query = `@${longPath}`;
|
||||
|
||||
const localMockConfig = {
|
||||
getTargetDir: () => '.',
|
||||
validatePathAccess: () => true,
|
||||
getResourceRegistry: () => ({
|
||||
findResourceByUri: () => undefined,
|
||||
}),
|
||||
} as unknown as Config;
|
||||
|
||||
// checkPermissions should not throw ENAMETOOLONG
|
||||
const permissions = await checkPermissions(query, localMockConfig);
|
||||
expect(permissions).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -188,9 +188,15 @@ export async function checkPermissions(
|
||||
const pathName = part.content.substring(1);
|
||||
if (!pathName) continue;
|
||||
|
||||
const resolvedPathName = resolveToRealPath(
|
||||
path.resolve(config.getTargetDir(), pathName),
|
||||
);
|
||||
let resolvedPathName: string;
|
||||
try {
|
||||
resolvedPathName = resolveToRealPath(
|
||||
path.resolve(config.getTargetDir(), pathName),
|
||||
);
|
||||
} catch {
|
||||
// If path resolution fails (e.g. ENAMETOOLONG), skip this path
|
||||
continue;
|
||||
}
|
||||
|
||||
if (config.validatePathAccess(resolvedPathName, 'read')) {
|
||||
if (await fileExists(resolvedPathName)) {
|
||||
|
||||
@@ -602,6 +602,19 @@ describe('resolveToRealPath', () => {
|
||||
/Infinite recursion detected/,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle ENAMETOOLONG gracefully', () => {
|
||||
const longPath = path.resolve('/' + 'a'.repeat(5000));
|
||||
|
||||
vi.spyOn(fs, 'realpathSync').mockImplementation(() => {
|
||||
const err = new Error('ENAMETOOLONG') as NodeJS.ErrnoException;
|
||||
err.code = 'ENAMETOOLONG';
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Should return the path itself if realpathSync fails with ENAMETOOLONG
|
||||
expect(resolveToRealPath(longPath)).toBe(longPath);
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeRelative', () => {
|
||||
|
||||
@@ -440,7 +440,10 @@ function robustRealpath(p: string, visited = new Set<string>()): string {
|
||||
e &&
|
||||
typeof e === 'object' &&
|
||||
'code' in e &&
|
||||
(e.code === 'ENOENT' || e.code === 'EISDIR')
|
||||
(e.code === 'ENOENT' ||
|
||||
e.code === 'EISDIR' ||
|
||||
e.code === 'ENAMETOOLONG' ||
|
||||
e.code === 'EINVAL')
|
||||
) {
|
||||
try {
|
||||
const stat = fs.lstatSync(p);
|
||||
@@ -457,7 +460,10 @@ function robustRealpath(p: string, visited = new Set<string>()): string {
|
||||
lstatError &&
|
||||
typeof lstatError === 'object' &&
|
||||
'code' in lstatError &&
|
||||
(lstatError.code === 'ENOENT' || lstatError.code === 'EISDIR')
|
||||
(lstatError.code === 'ENOENT' ||
|
||||
lstatError.code === 'EISDIR' ||
|
||||
lstatError.code === 'ENAMETOOLONG' ||
|
||||
lstatError.code === 'EINVAL')
|
||||
)
|
||||
) {
|
||||
throw lstatError;
|
||||
|
||||
Reference in New Issue
Block a user