Repository navigation
feat(sse): port Claude Code quota-probe bypass + command meta-request helpers #4083
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2c9efee
1fb1c99
793ec6e
0423b99
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| // Tools whose meaningful prefix is two words (verb + subcommand). | ||
| const TWO_WORD_TOOLS = new Set([ | ||
| "git", "npm", "docker", "kubectl", "cargo", "go", "pip", "yarn", "pnpm", "bun", | ||
| ]); | ||
|
|
||
| const ENV_ASSIGNMENT_RE = /^[A-Za-z_][A-Za-z0-9_]*=/; | ||
| // Command-injection signals: ; | & ` $( (newline handled separately) | ||
| const INJECTION_RE = /[;|&`]|\$\(/; | ||
|
|
||
| /** Tokenize a shell command on whitespace, respecting simple single/double quotes. */ | ||
| function tokenize(command: string): string[] { | ||
| const tokens: string[] = []; | ||
| let current = ""; | ||
| let quote: string | null = null; | ||
| for (const ch of command) { | ||
| if (quote) { | ||
| if (ch === quote) quote = null; | ||
| else current += ch; | ||
| } else if (ch === '"' || ch === "'") { | ||
| quote = ch; | ||
| } else if (/\s/.test(ch)) { | ||
| if (current) { tokens.push(current); current = ""; } | ||
| } else { | ||
| current += ch; | ||
| } | ||
| } | ||
| if (current) tokens.push(current); | ||
| return tokens; | ||
| } | ||
|
|
||
| export function extractCommandPrefix(command: string): string { | ||
| if (typeof command !== "string" || !command.trim()) return ""; | ||
| if (INJECTION_RE.test(command) || command.includes("\n")) { | ||
| return "command_injection_detected"; | ||
| } | ||
|
Comment on lines
+31
to
+35
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Falso positivo na detecção de injeção de comando: A expressão regular Embora o helper ainda não esteja integrado ao handler principal, vale a pena considerar essa limitação para evitar que comandos legítimos contendo esses caracteres em strings citadas sejam bloqueados ou falhem no bypass. |
||
| let tokens = tokenize(command); | ||
| // Strip leading FOO=bar env assignments. | ||
| while (tokens.length && ENV_ASSIGNMENT_RE.test(tokens[0])) tokens = tokens.slice(1); | ||
| if (!tokens.length) return ""; | ||
| const head = tokens[0]; | ||
| // Only form a two-word prefix when the second token is an actual subcommand, | ||
| // not a flag. We intentionally do not parse flag-arguments (e.g. `git -C <path>`); | ||
| // for those we fall back to the single-word head (conservative, never wrong). | ||
| if (TWO_WORD_TOOLS.has(head) && tokens.length > 1 && !tokens[1].startsWith("-")) { | ||
| return `${head} ${tokens[1]}`; | ||
| } | ||
| return head; | ||
| } | ||
|
|
||
| const READ_COMMANDS = new Set(["cat", "head", "tail", "less", "more", "bat", "type"]); | ||
| const LISTING_COMMANDS = new Set(["ls", "dir", "find", "tree"]); | ||
| // grep flags that consume the NEXT arg (so it is not a filepath). | ||
| const GREP_ARG_FLAGS = new Set(["-e", "-f", "-m", "-A", "-B", "-C"]); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Limitação no parsing de flags do |
||
|
|
||
| export function extractFilepathsFromCommand(command: string, _output = ""): string[] { | ||
| if (typeof command !== "string" || !command.trim()) return []; | ||
| const tokens = tokenize(command); | ||
| if (!tokens.length) return []; | ||
| const head = tokens[0]; | ||
|
|
||
| if (LISTING_COMMANDS.has(head)) return []; | ||
|
|
||
| if (head === "grep") { | ||
| const files: string[] = []; | ||
| let patternConsumed = false; | ||
| for (let i = 1; i < tokens.length; i++) { | ||
| const tok = tokens[i]; | ||
| if (tok.startsWith("-")) { | ||
| if (GREP_ARG_FLAGS.has(tok)) { | ||
| i++; // skip this flag's argument | ||
| // -e/-f supply the pattern itself, so the positional pattern is already consumed. | ||
| if (tok === "-e" || tok === "-f") patternConsumed = true; | ||
| } | ||
| continue; | ||
| } | ||
| if (!patternConsumed) { patternConsumed = true; continue; } // first non-flag = pattern | ||
| files.push(tok); | ||
| } | ||
| return files; | ||
| } | ||
|
|
||
| if (READ_COMMANDS.has(head)) { | ||
| return tokens.slice(1).filter((t) => !t.startsWith("-")); | ||
| } | ||
|
Comment on lines
+82
to
+84
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug de corretude: Ao processar comandos de leitura como Podemos corrigir isso iterando pelos tokens e pulando o argumento das flags conhecidas que consomem o próximo parâmetro (como if (READ_COMMANDS.has(head)) {
const files: string[] = [];
const READ_ARG_FLAGS = new Set(["-n", "-c", "--lines", "--bytes"]);
for (let i = 1; i < tokens.length; i++) {
const tok = tokens[i];
if (tok.startsWith("-")) {
if (READ_ARG_FLAGS.has(tok)) {
i++;
}
continue;
}
files.push(tok);
}
return files;
} |
||
|
|
||
| return []; | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,53 @@ | ||||||||||||||||||||
| import { test } from "node:test"; | ||||||||||||||||||||
| import assert from "node:assert/strict"; | ||||||||||||||||||||
| import { extractCommandPrefix } from "../../open-sse/utils/claudeCodeMetaRequests.ts"; | ||||||||||||||||||||
| import { extractFilepathsFromCommand } from "../../open-sse/utils/claudeCodeMetaRequests.ts"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| test("extractCommandPrefix returns two-word prefix for known multi-verb tools", () => { | ||||||||||||||||||||
| assert.equal(extractCommandPrefix("git commit -m 'x'"), "git commit"); | ||||||||||||||||||||
| assert.equal(extractCommandPrefix("npm install lodash"), "npm install"); | ||||||||||||||||||||
| assert.equal(extractCommandPrefix("docker build ."), "docker build"); | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| test("extractCommandPrefix returns single word for simple commands", () => { | ||||||||||||||||||||
| assert.equal(extractCommandPrefix("ls -la"), "ls"); | ||||||||||||||||||||
| assert.equal(extractCommandPrefix("cat file.txt"), "cat"); | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| test("extractCommandPrefix strips leading env assignments", () => { | ||||||||||||||||||||
| assert.equal(extractCommandPrefix("FOO=bar npm run build"), "npm run"); | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| test("extractCommandPrefix detects command injection", () => { | ||||||||||||||||||||
| assert.equal(extractCommandPrefix("ls; rm -rf /"), "command_injection_detected"); | ||||||||||||||||||||
| assert.equal(extractCommandPrefix("echo $(whoami)"), "command_injection_detected"); | ||||||||||||||||||||
| assert.equal(extractCommandPrefix("cat `id`"), "command_injection_detected"); | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| test("extractFilepathsFromCommand returns read files for read commands", () => { | ||||||||||||||||||||
| assert.deepEqual(extractFilepathsFromCommand("cat src/a.ts src/b.ts", ""), ["src/a.ts", "src/b.ts"]); | ||||||||||||||||||||
| assert.deepEqual(extractFilepathsFromCommand("head -n5 README.md", ""), ["README.md"]); | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
Comment on lines
+27
to
+30
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Melhoria nos testes: Recomenda-se adicionar um caso de teste para cobrir comandos de leitura com argumentos de flags separados por espaço (ex:
Suggested change
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| test("extractFilepathsFromCommand returns empty for listing commands", () => { | ||||||||||||||||||||
| assert.deepEqual(extractFilepathsFromCommand("ls -la src/", ""), []); | ||||||||||||||||||||
| assert.deepEqual(extractFilepathsFromCommand("find . -name '*.ts'", ""), []); | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| test("extractFilepathsFromCommand skips the grep pattern arg", () => { | ||||||||||||||||||||
| assert.deepEqual(extractFilepathsFromCommand("grep -n foo src/a.ts", ""), ["src/a.ts"]); | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| test("extractCommandPrefix stays conservative when a flag precedes the subcommand", () => { | ||||||||||||||||||||
| // -C takes a path arg; we don't parse flag-args, so fall back to the head | ||||||||||||||||||||
| // (never emit a wrong two-word prefix like "git -C" or "git /x"). | ||||||||||||||||||||
| assert.equal(extractCommandPrefix("git -C /x commit"), "git"); | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| test("extractFilepathsFromCommand handles grep -e pattern then file", () => { | ||||||||||||||||||||
| assert.deepEqual(extractFilepathsFromCommand("grep -e pattern a.ts", ""), ["a.ts"]); | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| test("extractFilepathsFromCommand handles grep -f patternfile then file", () => { | ||||||||||||||||||||
| assert.deepEqual(extractFilepathsFromCommand("grep -f pats.txt a.ts", ""), ["a.ts"]); | ||||||||||||||||||||
| }); | ||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Limitação na tokenização (escapes de aspas): A função
tokenizenão lida com caracteres de escape (como\"ou\'). Se um comando contiver aspas escapadas dentro de uma string citada (por exemplo,git commit -m "Fix \"bug\""), a tokenização será quebrada, pois o caractere"escapado fechará prematuramente a string.Podemos tornar a tokenização muito mais robusta adicionando um controle simples para caracteres escapados por barra invertida (
\\).