Skip to content

Commit 6aeefb1

Browse files
ktnytclaude
andcommitted
🔒 Always quote config paths for safety
- Changed to always quote paths regardless of spaces - Handles special characters and edge cases better - Updated all tests to expect quoted paths - Improved cross-platform compatibility 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent fa2d65f commit 6aeefb1

File tree

2 files changed

+120
-56
lines changed

2 files changed

+120
-56
lines changed

src/setup.test.ts

Lines changed: 116 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,89 @@ describe('setup CLI integration', () => {
242242
});
243243
});
244244

245+
describe('Path execution tests', () => {
246+
test('should handle actual file system paths', () => {
247+
const testPaths = [
248+
'/tmp/test project/config.json',
249+
'C:\\Program Files\\My App\\config.json',
250+
'/home/user/.config/claude/cclsp.json',
251+
'C:\\Users\\test\\AppData\\cclsp.json',
252+
];
253+
254+
for (const path of testPaths) {
255+
const command = generateMCPCommand(path, false);
256+
const args = buildMCPArgs(path, false);
257+
258+
const commandQuoted = command.includes(`"${path}`);
259+
const envArg = args.find((arg) => arg.startsWith('CCLSP_CONFIG_PATH='));
260+
const argsQuoted = envArg ? envArg.includes('"') : false;
261+
262+
// All paths should now be quoted
263+
expect(commandQuoted).toBe(true);
264+
expect(argsQuoted).toBe(true);
265+
266+
// Verify the structure is valid
267+
expect(command).toContain('claude mcp add cclsp');
268+
expect(command).toContain('npx cclsp@latest');
269+
expect(command).toContain('CCLSP_CONFIG_PATH=');
270+
expect(args).toContain('mcp');
271+
expect(args).toContain('add');
272+
expect(args).toContain('cclsp');
273+
}
274+
});
275+
276+
test('should generate valid executable commands', () => {
277+
// Test that generated commands have correct structure
278+
const testPath = '/path with spaces/config.json';
279+
const command = generateMCPCommand(testPath, false);
280+
const args = buildMCPArgs(testPath, false);
281+
282+
// Command should be properly formatted
283+
expect(command).toMatch(/^claude mcp add cclsp .* --env CCLSP_CONFIG_PATH=.*/);
284+
285+
// Args should be properly structured for spawn/exec
286+
expect(args[0]).toBe('mcp');
287+
expect(args[1]).toBe('add');
288+
expect(args[2]).toBe('cclsp');
289+
290+
// Environment variable should be properly formatted
291+
const envArg = args.find((arg) => arg.startsWith('CCLSP_CONFIG_PATH='));
292+
expect(envArg).toBeDefined();
293+
expect(envArg).toContain('=');
294+
295+
// If path has spaces, it should be quoted in the env value
296+
if (testPath.includes(' ')) {
297+
expect(envArg).toMatch(/CCLSP_CONFIG_PATH=".*"/);
298+
}
299+
});
300+
301+
test('should handle paths with spaces in command generation', () => {
302+
// Test with a hypothetical path with spaces (doesn't need to exist for command generation)
303+
const pathWithSpaces = '/Users/test user/My Documents/project/.claude/cclsp.json';
304+
305+
// Generate command and args
306+
const command = generateMCPCommand(pathWithSpaces, false);
307+
const args = buildMCPArgs(pathWithSpaces, false);
308+
309+
// The path should contain spaces
310+
expect(pathWithSpaces).toContain(' ');
311+
312+
// Verify quoting for path with spaces in command
313+
expect(command).toContain(`"${pathWithSpaces}"`);
314+
expect(command).toContain('CCLSP_CONFIG_PATH=');
315+
316+
// Check if the env arg contains quotes
317+
const envArg = args.find((arg) => arg.startsWith('CCLSP_CONFIG_PATH='));
318+
expect(envArg).toBeDefined();
319+
expect(envArg).toBe(`CCLSP_CONFIG_PATH="${pathWithSpaces}"`);
320+
321+
// Verify command structure
322+
expect(command).toContain('claude mcp add cclsp');
323+
expect(command).toContain('npx cclsp@latest');
324+
expect(args).toEqual(expect.arrayContaining(['mcp', 'add', 'cclsp']));
325+
});
326+
});
327+
245328
describe('Windows platform support', () => {
246329
test('should generate MCP command with cmd /c prefix on Windows', () => {
247330
const configPath = '/path/to/config.json';
@@ -299,7 +382,7 @@ describe('Windows platform support', () => {
299382
'npx',
300383
'cclsp@latest',
301384
'--env',
302-
`CCLSP_CONFIG_PATH=${absoluteConfigPath}`,
385+
`CCLSP_CONFIG_PATH="${absoluteConfigPath}"`,
303386
]);
304387
});
305388

@@ -316,7 +399,7 @@ describe('Windows platform support', () => {
316399
'npx',
317400
'cclsp@latest',
318401
'--env',
319-
`CCLSP_CONFIG_PATH=${absoluteConfigPath}`,
402+
`CCLSP_CONFIG_PATH="${absoluteConfigPath}"`,
320403
]);
321404

322405
// Test Linux platform
@@ -328,7 +411,7 @@ describe('Windows platform support', () => {
328411
'npx',
329412
'cclsp@latest',
330413
'--env',
331-
`CCLSP_CONFIG_PATH=${absoluteConfigPath}`,
414+
`CCLSP_CONFIG_PATH="${absoluteConfigPath}"`,
332415
]);
333416
});
334417

@@ -348,7 +431,7 @@ describe('Windows platform support', () => {
348431
'--scope',
349432
'user',
350433
'--env',
351-
`CCLSP_CONFIG_PATH=${absoluteConfigPath}`,
434+
`CCLSP_CONFIG_PATH="${absoluteConfigPath}"`,
352435
]);
353436

354437
// Test macOS with user scope
@@ -362,7 +445,7 @@ describe('Windows platform support', () => {
362445
'--scope',
363446
'user',
364447
'--env',
365-
`CCLSP_CONFIG_PATH=${absoluteConfigPath}`,
448+
`CCLSP_CONFIG_PATH="${absoluteConfigPath}"`,
366449
]);
367450
});
368451

@@ -397,56 +480,41 @@ describe('Windows platform support', () => {
397480
}
398481
});
399482

400-
test('should quote config path with spaces in command', () => {
483+
test('should always quote config path in command', () => {
401484
const configPathWithSpaces = '/path with spaces/config.json';
485+
const configPathWithoutSpaces = '/path/to/config.json';
402486
const isUser = false;
403487

404-
// Test Windows platform with spaces
405-
const windowsCommand = generateMCPCommand(configPathWithSpaces, isUser, 'win32');
406-
expect(windowsCommand).toContain('CCLSP_CONFIG_PATH="/');
407-
expect(windowsCommand).toContain('with spaces');
408-
expect(windowsCommand).toContain('"');
409-
410-
// Test macOS platform with spaces
411-
const macCommand = generateMCPCommand(configPathWithSpaces, isUser, 'darwin');
412-
expect(macCommand).toContain('CCLSP_CONFIG_PATH="/');
413-
expect(macCommand).toContain('with spaces');
414-
expect(macCommand).toContain('"');
415-
});
488+
// Test with spaces
489+
const commandWithSpaces = generateMCPCommand(configPathWithSpaces, isUser, 'darwin');
490+
expect(commandWithSpaces).toContain('CCLSP_CONFIG_PATH="');
491+
expect(commandWithSpaces).toContain('"');
416492

417-
test('should quote config path with spaces in args', () => {
418-
const absolutePathWithSpaces = '/absolute/path with spaces/config.json';
419-
const isUser = false;
420-
421-
// Test Windows platform with spaces
422-
const windowsArgs = buildMCPArgs(absolutePathWithSpaces, isUser, 'win32');
423-
const envArg = windowsArgs.find((arg) => arg.startsWith('CCLSP_CONFIG_PATH='));
424-
expect(envArg).toBeDefined();
425-
expect(envArg).toContain('"');
426-
expect(envArg).toContain('with spaces');
427-
428-
// Test macOS platform with spaces
429-
const macArgs = buildMCPArgs(absolutePathWithSpaces, isUser, 'darwin');
430-
const macEnvArg = macArgs.find((arg) => arg.startsWith('CCLSP_CONFIG_PATH='));
431-
expect(macEnvArg).toBeDefined();
432-
expect(macEnvArg).toContain('"');
433-
expect(macEnvArg).toContain('with spaces');
493+
// Test without spaces - should still be quoted
494+
const commandWithoutSpaces = generateMCPCommand(configPathWithoutSpaces, isUser, 'darwin');
495+
expect(commandWithoutSpaces).toContain('CCLSP_CONFIG_PATH="');
496+
expect(commandWithoutSpaces).toContain('"');
434497
});
435498

436-
test('should not quote config path without spaces', () => {
437-
const configPath = '/path/to/config.json';
438-
const absolutePath = '/absolute/path/to/config.json';
499+
test('should always quote config path in args', () => {
500+
const absolutePathWithSpaces = '/absolute/path with spaces/config.json';
501+
const absolutePathWithoutSpaces = '/absolute/path/to/config.json';
439502
const isUser = false;
440503

441-
// Test command generation
442-
const command = generateMCPCommand(configPath, isUser, 'darwin');
443-
expect(command).not.toContain('CCLSP_CONFIG_PATH="');
444-
expect(command).toContain('CCLSP_CONFIG_PATH=/');
445-
446-
// Test args generation
447-
const args = buildMCPArgs(absolutePath, isUser, 'darwin');
448-
const envArg = args.find((arg) => arg.startsWith('CCLSP_CONFIG_PATH='));
449-
expect(envArg).toBeDefined();
450-
expect(envArg).not.toContain('"');
504+
// Test with spaces
505+
const argsWithSpaces = buildMCPArgs(absolutePathWithSpaces, isUser, 'darwin');
506+
const envArgWithSpaces = argsWithSpaces.find((arg) => arg.startsWith('CCLSP_CONFIG_PATH='));
507+
expect(envArgWithSpaces).toBeDefined();
508+
expect(envArgWithSpaces).toContain('"');
509+
expect(envArgWithSpaces).toBe(`CCLSP_CONFIG_PATH="${absolutePathWithSpaces}"`);
510+
511+
// Test without spaces - should still be quoted
512+
const argsWithoutSpaces = buildMCPArgs(absolutePathWithoutSpaces, isUser, 'darwin');
513+
const envArgWithoutSpaces = argsWithoutSpaces.find((arg) =>
514+
arg.startsWith('CCLSP_CONFIG_PATH=')
515+
);
516+
expect(envArgWithoutSpaces).toBeDefined();
517+
expect(envArgWithoutSpaces).toContain('"');
518+
expect(envArgWithoutSpaces).toBe(`CCLSP_CONFIG_PATH="${absolutePathWithoutSpaces}"`);
451519
});
452520
});

src/setup.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,8 @@ export function generateMCPCommand(
261261
const isWindows = platform === 'win32';
262262
const commandPrefix = isWindows ? 'cmd /c ' : '';
263263

264-
// Quote the path if it contains spaces
265-
const quotedPath = absoluteConfigPath.includes(' ')
266-
? `"${absoluteConfigPath}"`
267-
: absoluteConfigPath;
264+
// Always quote the path for safety (handles spaces and special characters)
265+
const quotedPath = `"${absoluteConfigPath}"`;
268266

269267
return `claude mcp add cclsp ${commandPrefix}npx cclsp@latest${scopeFlag} --env CCLSP_CONFIG_PATH=${quotedPath}`;
270268
}
@@ -289,10 +287,8 @@ export function buildMCPArgs(
289287
mcpArgs.push('--scope', 'user');
290288
}
291289
292-
// Add environment variable with proper quoting if path contains spaces
293-
const quotedPath = absoluteConfigPath.includes(' ')
294-
? `"${absoluteConfigPath}"`
295-
: absoluteConfigPath;
290+
// Always quote the path for safety (handles spaces and special characters)
291+
const quotedPath = `"${absoluteConfigPath}"`;
296292
mcpArgs.push('--env', `CCLSP_CONFIG_PATH=${quotedPath}`);
297293
298294
return mcpArgs;

0 commit comments

Comments
 (0)