fix: refuse a path that could escape the command

device_setup_command built a shell command from the agent's values: a
typographic quote ends a PowerShell string, and a path starting with -
reads as an option. Such values are refused, the options are written
attached to their values, and platform must be a setup name.
This commit is contained in:
Abdessamad Derraz committed 2026-11-06 20:01:03 +01:00
1 parent 24d7db0ab3
commit d10c037a86
2 files changed
+27 -5

No files matched your search

+18 -5
View File
@@ -141,12 +141,25 @@ const setupCommand: Tool = {
},
annotations: { readOnlyHint: true },
async execute({ shell, everything, bios_folder, games_folder, platform }) {
const quote = (value: unknown) => `'${String(value).replace(/'/g, shell === 'powershell' ? "''" : "'\\''")}'`;
const run = shell === 'powershell' ? PS : SH;
const powershell = shell === 'powershell';
// The command is pasted into a shell: a value that could close its
// quotes (PowerShell also ends a string on the typographic ones) or
// start another option is refused, never escaped.
const unsafe = /[\u0000-\u001f\u007f'"`‘’‚‛“”„]/;
for (const [field, value] of [['bios_folder', bios_folder], ['games_folder', games_folder]] as const) {
if (value !== undefined && (typeof value !== 'string' || !value || unsafe.test(value) || value.startsWith('-'))) {
return `${field} must be a folder path without quotes or control characters.`;
}
}
if (platform !== undefined && (typeof platform !== 'string' || !/^[a-z0-9_-]+$/.test(platform))) {
return 'platform must be a setup name such as retroarch or batocera.';
}
const run = powershell ? PS : SH;
const quote = (value: string) => `'${value}'`;
const place: string[] = [];
if (platform) place.push('--platform', quote(platform));
if (bios_folder) place.push('--dest', quote(bios_folder));
const games = everything ? [] : [...(games_folder ? ['--for-games', quote(games_folder)] : ['--for-games']), '--required-only'];
if (platform) place.push(`--platform=${platform}`);
if (bios_folder) place.push(`--dest=${quote(bios_folder as string)}`);
const games = everything ? [] : [games_folder ? `--for-games=${quote(games_folder as string)}` : '--for-games', '--required-only'];
const command = (...more: string[]) => [run, ...place, ...more].join(' ');
return JSON.stringify({
preview: command(...games, '--dry-run'),