{"record":{"id":"c53d248558bdf645","repo":"Mintplex-Labs/anything-llm","slug":"search-pattern-must-not-start-with","errorCode":null,"errorMessage":"search pattern must not start with '-'","messagePattern":"search pattern must not start with '-'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/utils/agents/aibitat/plugins/filesystem/search-files.js","lineNumber":347,"sourceCode":"  // Build ripgrep arguments\n  const args = [\n    \"--json\", // JSON output for structured parsing\n    \"--line-number\", // Include line numbers\n    \"--no-ignore\", // Search all files, even those in .gitignore\n    \"--max-count\",\n    String(maxResults),\n  ];\n\n  if (!caseSensitive) args.push(\"--ignore-case\");\n  if (filePattern) args.push(\"--glob\", filePattern);\n  for (const exclude of excludePatterns) args.push(\"--glob\", `!${exclude}`);\n\n  // Security: prevent argument injection attacks where a malicious pattern like\n  // \"--pre=/bin/sh\" could cause ripgrep to execute arbitrary commands.\n  // The \"--\" separator tells ripgrep to treat everything after it as positional\n  // arguments, not options. The startsWith(\"-\") check is defense-in-depth.\n  if (typeof pattern === \"string\" && pattern.startsWith(\"-\")) {\n    throw new Error(\"search pattern must not start with '-'\");\n  }\n  args.push(\"--\", pattern, searchPath);\n  const result = spawnSync(rgPath, args, {\n    encoding: \"utf-8\",\n    maxBuffer: 10 * 1024 * 1024, // 10MB\n  });\n\n  // Exit code 1 means no matches (not an error)\n  if (result.status > 1) {\n    throw new Error(\n      result.stderr || `ripgrep exited with code ${result.status}`\n    );\n  }\n\n  const results = [];\n  if (!result.stdout) return results;\n  const matches = safeJsonParse(result.stdout, []).filter(\n    (m) => m.type === \"match\" && m.data","sourceCodeStart":329,"sourceCodeEnd":365,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/3aec848f2885144aa8f1e53b9731a04310d5d558/server/utils/agents/aibitat/plugins/filesystem/search-files.js#L329-L365","documentation":"Thrown by searchWithRipgrep when the regex pattern string starts with '-'. Because the pattern is passed as a positional argument to the rg binary, a leading dash could make ripgrep parse it as an option (e.g. --pre=/bin/sh executes commands), so the library rejects it outright even though '--' is also pushed as a separator (defense in depth). This is an intentional argument-injection guard, not a ripgrep limitation.","triggerScenarios":"Calling search_files with a pattern that begins with a dash: \"-flag\", \"--verbose\", \"-\\d+\"; an LLM echoes a CLI-style flag from user text into the query field; searching for literal text that happens to start with '-' (e.g. \"--- separator comments\").","commonSituations":"User asks the agent to 'search for --help' in a codebase; prompt-injection attempts smuggling rg options like --pre through the search tool; regexes written to match dash-prefixed flags or bullet lists.","solutions":["Escape the leading dash so the regex still matches a literal '-': prefix with a backslash, e.g. pattern \"\\\\-flag\" or use a character class \"[-]flag\".","Strip leading dashes/flags from user-supplied query text before passing it to the tool.","Treat hits of this error from untrusted input as a prompt-injection signal and sanitize at the application boundary."],"exampleFix":"// before\nsearch_files({ path: \".\", query: \"--verbose\" })  // throws: must not start with '-'\n\n// after\nsearch_files({ path: \".\", query: \"\\\\-\\\\-verbose\" })  // escaped literal dashes","handlingStrategy":"validation","validationCode":"function sanitizeSearchPattern(pattern) {\n  if (typeof pattern !== \"string\" || pattern.length === 0) {\n    throw new Error(\"search pattern must be a non-empty string\");\n  }\n  // literal leading dash -> escaped regex form the guard accepts and rg still matches\n  return pattern.startsWith(\"-\") ? \"\\\\\" + pattern : pattern;\n}\nconst safe = sanitizeSearchPattern(userQuery);","typeGuard":"/** @returns {boolean} pattern cannot be parsed by rg as an option */\nfunction isSafePattern(p) {\n  return typeof p === \"string\" && p.length > 0 && !p.startsWith(\"-\");\n}","tryCatchPattern":"try {\n  const hits = searchWithRipgrep({ searchPath, pattern });\n} catch (e) {\n  if (e.message === \"search pattern must not start with '-'\") {\n    pattern = \"\\\\\" + pattern; // escape literal dash, retry once\n  } else throw e;\n}","preventionTips":["Strip or escape leading dashes in user/LLM-supplied queries before they reach the search tool.","Treat this guard firing on untrusted input as a prompt-injection signal worth alerting on.","Prefer character classes ([-]foo) when searching literal dash-prefixed text."],"tags":["filesystem","ripgrep","argument-injection","security","input-validation"],"backgroundTag":"argument-injection-guard","analyzedSha":"3aec848f2885144aa8f1e53b9731a04310d5d558","analyzedAt":"2026-08-18T10:02:21.017Z","contentChangedAt":"2026-08-18T10:02:21.017Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}