{"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":"Error","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/526360e320da9d1b36074be5ed64fe76e5bbfbbd/server/utils/agents/aibitat/plugins/filesystem/search-files.js#L329-L365","documentation":"Defense-in-depth security guard inside searchWithRipgrep. Ripgrep patterns that start with '-' could be misinterpreted as flags (e.g. '--pre=/bin/sh' would execute a command). Even though a '--' separator is pushed before the pattern, this check rejects any leading-dash pattern before the process is spawned.","triggerScenarios":"An agent or caller passes a content-search pattern that begins with '-', such as '-foo', '--bar', or a regex like '-[a-z]+'. Most commonly happens when the LLM generates a malformed query or when a user's literal search term starts with a hyphen.","commonSituations":"LLM agent hallucinates a flag-like search term; a frontend input is passed straight to the tool without sanitization; testing with a pattern meant for grep -v syntax; the agent tries to search for a negative lookahead regex.","solutions":["Strip or escape a leading dash before calling the tool: if the pattern starts with '-', prefix it with a backslash or remove the dash.","If the user genuinely wants to search for a literal leading dash, pass an escaped regex like '\\\\-' or use a pattern that does not start with the dash.","Validate user/agent input upstream and reject or rewrite flag-like patterns before they reach the tool."],"exampleFix":"// before\nif (typeof pattern === \"string\" && pattern.startsWith(\"-\")) {\n  throw new Error(\"search pattern must not start with '-' \");\n}\n\n// caller-side fix — sanitize before calling the agent tool\nconst safePattern = pattern.startsWith(\"-\") ? `\\\\${pattern}` : pattern;\n// then pass safePattern instead of pattern","handlingStrategy":"validation","validationCode":"/** Strip or reject leading dashes before passing to the search tool. */\nfunction sanitizeSearchPattern(pattern) {\n  if (typeof pattern !== \"string\") return pattern;\n  // Remove leading dashes or escape them\n  return pattern.replace(/^-+/, (m) => \"\\\\\".repeat(m.length) + m);\n}\nconst safePattern = sanitizeSearchPattern(userPattern);","typeGuard":"/** @param {string} p */\nfunction isSafePattern(p) {\n  return typeof p === \"string\" && p.length > 0 && !p.startsWith(\"-\");\n}","tryCatchPattern":"try {\n  const results = searchWithRipgrep({ searchPath, pattern: safePattern, ... });\n} catch (e) {\n  if (e.message === \"search pattern must not start with '-'\") {\n    // Re-sanitize and retry, or inform the caller\n    const escaped = pattern.replace(/^-/, \"\\\\-\");\n    return searchWithRipgrep({ searchPath, pattern: escaped, ... });\n  }\n  throw e;\n}","preventionTips":["Sanitize all search patterns before passing them to the tool.","Treat agent/LLM-generated patterns as untrusted input.","Document the leading-dash restriction in the tool's description so the LLM avoids it.","Use the '--' separator (already in place) but do not rely on it alone."],"tags":["ripgrep","security","argument-injection","validation","agent-tool"],"backgroundTag":null,"analyzedSha":"526360e320da9d1b36074be5ed64fe76e5bbfbbd","analyzedAt":"2026-08-13T01:45:47.170Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}