fix: reject curl destination override flags

* fix: reject curl destination override flags

* fix: block additional curl target bypass forms
This commit is contained in:
Eddy authored and GitHub committed 2026-07-15 18:12:35 -04:00
1 parent 84fcef5428
commit 25f86b0c66
2 files changed
+113 -2

No files matched your search

+82
View File
@@ -344,6 +344,79 @@ interface ParsedCommand {
const SHELL_META = /[|&;$<>`\\]/;
const COMMAND_CONTROL = /[\x00-\x1F\x7F-\x9F\u2028\u2029]/;
const CURL_TRANSPORT_OVERRIDE_FLAGS = new Set([
'--resolve',
'--connect-to',
'--proxy',
'--preproxy',
'--socks4',
'--socks4a',
'--socks5',
'--socks5-hostname',
'--unix-socket',
'--abstract-unix-socket',
'--interface',
'--url',
'--config',
'--next',
'-x',
'-K',
]);
const CURL_VALUE_FLAGS = new Set([
'-A', '--user-agent',
'-b', '--cookie',
'-c', '--cookie-jar',
'-d', '--data', '--data-ascii', '--data-binary', '--data-raw', '--data-urlencode',
'-F', '--form',
'-H', '--header',
'-m', '--max-time',
'-o', '--output',
'-T', '--upload-file',
'-u', '--user',
'-X', '--request',
'--cacert', '--cert', '--connect-timeout', '--key', '--request-target', '--retry',
]);
function findCurlTransportOverrideFlag(args: string[]): string | undefined {
for (const arg of args) {
if (!arg) continue;
const flag = arg.includes('=') ? arg.slice(0, arg.indexOf('=')) : arg;
if (CURL_TRANSPORT_OVERRIDE_FLAGS.has(flag)) return flag;
if (arg.startsWith('-x') && arg !== '-X' && arg.length > 2) return '-x';
if (arg.startsWith('-K') && arg.length > 2) return '-K';
}
return undefined;
}
function looksLikeCurlUrlOperand(arg: string): boolean {
return /^[a-z][a-z0-9+.-]*:\/\/\S+/i.test(arg)
|| /^(?:[a-z0-9-]+\.)+[a-z]{2,}(?::\d+)?(?:[/?#].*)?$/i.test(arg)
|| /^(?:localhost|(?:\d{1,3}\.){3}\d{1,3})(?::\d+)?(?:[/?#].*)?$/i.test(arg)
|| /^\[[0-9a-f:]+\](?::\d+)?(?:[/?#].*)?$/i.test(arg);
}
function countCurlUrlOperands(args: string[]): number {
let count = 0;
let skipNext = false;
let endOfOptions = false;
for (const arg of args) {
if (!arg) continue;
if (skipNext) { skipNext = false; continue; }
if (!endOfOptions && arg === '--') { endOfOptions = true; continue; }
if (!endOfOptions && arg.startsWith('--')) {
const hasInlineValue = arg.includes('=');
const flag = hasInlineValue ? arg.slice(0, arg.indexOf('=')) : arg;
if (!hasInlineValue && CURL_VALUE_FLAGS.has(flag)) skipNext = true;
continue;
}
if (!endOfOptions && arg.startsWith('-')) {
if (arg.length === 2 && CURL_VALUE_FLAGS.has(arg)) skipNext = true;
continue;
}
if (looksLikeCurlUrlOperand(arg)) count += 1;
}
return count;
}
function parseCommand(command: string): ParsedCommand | { error: string } {
if (SHELL_META.test(command) || COMMAND_CONTROL.test(command)) return { error: 'Shell control characters are not allowed; use direct argv-style commands only.' };
@@ -355,6 +428,15 @@ function parseCommand(command: string): ParsedCommand | { error: string } {
if (adapter?.execution === 'catalog_only' || adapter?.execution === 'import_only') {
return { error: `Tool is catalog-only and cannot be executed directly: ${bin}` };
}
if (bin === 'curl') {
const overrideFlag = findCurlTransportOverrideFlag(args);
if (overrideFlag) {
return { error: `curl flag ${overrideFlag} changes the effective network destination and is not allowed through /api/tools/execute.` };
}
if (countCurlUrlOperands(args) > 1) {
return { error: 'curl commands with multiple URL operands are not allowed through /api/tools/execute; submit one transfer per approved target.' };
}
}
return { bin, args };
}