CVE-2026-10692
Description
A weakness has been identified in johnhuang316 code-index-mcp up to 2.14.0. Affected is the function is_safe_regex_pattern of the component search_code_advanced. Executing a manipulation of the argument regex can lead to inefficient regular expression complexity. It is possible to launch the attack remotely. The exploit has been made available to the public and could be used for attacks. Upgrading to version 2.14.1 is able to address this issue. This patch is called 25bc02fac74051ddae15ce79e952f00211b1ea6b. Upgrading the affected component is recommended.
Affected products
1- Range: <=2.14.0
Patches
125bc02fac740fix(search): make regex mode explicit
17 files changed · +212 −142
.gitignore+9 −8 modified@@ -41,11 +41,12 @@ Thumbs.db # Claude Code generated files CLAUDE.local.md -.claude/ -.claude_chat/ -claude_* -COMMIT_MESSAGE.txt -RELEASE_NOTE.txt - -.llm-context/ -AGENTS.md +.claude/ +.claude_chat/ +claude_* +COMMIT_MESSAGE.txt +RELEASE_NOTE.txt +.worktrees/ + +.llm-context/ +AGENTS.md
README_ja.md+4 −4 modified@@ -289,7 +289,7 @@ pip install code-index-mcp ### 🔍 **検索・発見** | ツール | 説明 | |--------|------| -| **`search_code_advanced`** | 正規表現、ファジーマッチング、ファイルフィルタリング対応のスマート検索。デフォルトで 1 ページあたり 10 件を返し、`max_results` と `start_index` で調整可能 | +| **`search_code_advanced`** | スマート検索。デフォルトはリテラル一致で、正規表現は `regex=True` を指定したときのみ有効。ファジーマッチング、ファイルフィルタリング、ページング(デフォルト 10 件)に対応し、正規表現モードはネイティブ検索ツールが必要。基本フォールバックはリテラル検索のみ | | **`find_files`** | globパターンを使用したファイル検索(例:`**/*.py`) | | **`get_file_summary`** | ファイル構造、関数、インポート、複雑度の解析(深いインデックスが必要) | @@ -336,9 +336,9 @@ src/api/userService.ts の要約を教えてください <summary><strong>コードパターン検索</strong></summary> ``` -正規表現を使って "get.*Data" にマッチする全ての関数呼び出しを検索してください +`regex=True` を使って "get.*Data" にマッチする全ての関数呼び出しを検索してください ``` -*発見:`getData()`、`getUserData()`、`getFormData()` など* +*発見:`getData()`、`getUserData()`、`getFormData()` など。正規表現検索はオプトインです。ネイティブ検索ツールを導入して `regex=True` を使ってください。基本フォールバックはリテラル検索のままです。* </details> @@ -358,7 +358,7 @@ src/api/userService.ts の要約を教えてください ``` Pythonファイルのみで "API_ENDPOINT" を検索してください ``` -*使用ツール:`search_code_advanced`、`file_pattern: "*.py"`(デフォルトは 10 件。`max_results` で件数を増やし、`start_index` でページ送り)* +*使用ツール:`search_code_advanced` のリテラル検索と `file_pattern: "*.py"`(デフォルトは 10 件。`max_results` で件数を増やし、`start_index` でページ送り)* </details>
README_ko.md+4 −4 modified@@ -190,7 +190,7 @@ Linux와 macOS는 운영체제가 `HOME`과 XDG 경로를 기본으로 제공하 ### 🔍 **검색 & 탐색** | 도구 | 설명 | |------|------| -| **`search_code_advanced`** | 정규식, 퍼지 매칭, 파일 필터링을 지원하는 스마트 검색 (기본적으로 페이지당 10개 결과 반환, `max_results`·`start_index`로 조정 가능) | +| **`search_code_advanced`** | 스마트 검색. 기본은 리터럴 검색이며 정규식은 `regex=True`일 때만 활성화됩니다. 퍼지 매칭, 파일 필터링, 페이징(기본 10개 결과)을 지원하며, 정규식 모드는 네이티브 검색 도구가 필요합니다. 기본 폴백은 리터럴 검색만 지원합니다 | | **`find_files`** | 글롭 패턴으로 파일 찾기 (예: `**/*.py`) | | **`get_file_summary`** | 파일 구조, 함수, 임포트, 복잡도를 분석 (심층 인덱스 필요) | @@ -237,9 +237,9 @@ src/api/userService.ts 요약을 알려줘 <summary><strong>코드 패턴 검색</strong></summary> ``` -"get.*Data"에 해당하는 함수 호출을 정규식으로 찾아줘 +`regex=True`로 "get.*Data"에 해당하는 함수 호출을 찾아줘 ``` -*예: `getData()`, `getUserData()`, `getFormData()`* +*예: `getData()`, `getUserData()`, `getFormData()`. 정규식 검색은 옵트인입니다. 네이티브 검색 도구를 설치한 뒤 `regex=True`를 사용해야 하며, 기본 폴백은 리터럴 검색으로 유지됩니다.* </details> @@ -259,7 +259,7 @@ src/api/userService.ts 요약을 알려줘 ``` Python 파일에서만 "API_ENDPOINT" 를 찾아줘 ``` -*`search_code_advanced` + `file_pattern="*.py"` (기본 10개 결과, `max_results`로 확장하고 `start_index`로 페이지 이동)* +*`search_code_advanced`의 리터럴 검색 + `file_pattern="*.py"` (기본 10개 결과, `max_results`로 확장하고 `start_index`로 페이지 이동)* </details>
README.md+4 −4 modified@@ -282,7 +282,7 @@ Then configure: ### 🔍 **Search & Discovery** | Tool | Description | |------|-------------| -| **`search_code_advanced`** | Smart search with regex, fuzzy matching, file filtering, and paginated results (10 per page by default) | +| **`search_code_advanced`** | Smart search with literal-by-default matching, optional `regex=True`, fuzzy matching, file filtering, and paginated results (10 per page by default); regex mode requires a native search tool because the basic fallback is literal-only | | **`find_files`** | Locate files using glob patterns (e.g., `**/*.py`) | | **`get_file_summary`** | Analyze file structure, functions, imports, and complexity (requires deep index) | @@ -329,9 +329,9 @@ Give me a summary of src/api/userService.ts <summary><strong>Code Pattern Search</strong></summary> ``` -Search for all function calls matching "get.*Data" using regex +Search for all function calls matching "get.*Data" using `regex=True` ``` -*Finds: `getData()`, `getUserData()`, `getFormData()`, etc.* +*Finds: `getData()`, `getUserData()`, `getFormData()`, etc. Regex search is opt-in; install a native search tool and use `regex=True` because the basic fallback stays literal-only.* </details> @@ -351,7 +351,7 @@ Find authentication-related functions with fuzzy search for 'authUser' ``` Search for "API_ENDPOINT" only in Python files ``` -*Uses: `search_code_advanced` with `file_pattern: "*.py"` (defaults to 10 matches; use `max_results` to expand or `start_index` to page)* +*Uses: `search_code_advanced` with literal matching and `file_pattern: "*.py"` (defaults to 10 matches; use `max_results` to expand or `start_index` to page)* </details>
README_zh.md+4 −4 modified@@ -286,7 +286,7 @@ pip install code-index-mcp ### 🔍 **搜尋與探索** | 工具 | 描述 | |------|------| -| **`search_code_advanced`** | 智慧搜尋,支援正規表達式、模糊匹配和檔案篩選,預設每頁回傳 10 筆結果,可透過 `max_results` 與 `start_index` 調整 | +| **`search_code_advanced`** | 智慧搜尋,預設為字面匹配,可透過 `regex=True` 啟用正規表達式,並支援模糊匹配、檔案篩選與分頁結果(預設每頁 10 筆);正規表達式模式需要原生搜尋工具,因為基本後備模式僅支援字面搜尋 | | **`find_files`** | 使用萬用字元模式尋找檔案(例如 `**/*.py`) | | **`get_file_summary`** | 分析檔案結構、函式、匯入和複雜度(需要深度索引) | @@ -333,9 +333,9 @@ pip install code-index-mcp <summary><strong>程式碼模式搜尋</strong></summary> ``` -使用正規表達式搜尋所有符合 "get.*Data" 的函式呼叫 +使用 `regex=True` 搜尋所有符合 "get.*Data" 的函式呼叫 ``` -*找到:`getData()`、`getUserData()`、`getFormData()` 等* +*找到:`getData()`、`getUserData()`、`getFormData()` 等。正規表達式搜尋為選用功能;請安裝原生搜尋工具並搭配 `regex=True`,因為基本後備模式維持字面搜尋。* </details> @@ -355,7 +355,7 @@ pip install code-index-mcp ``` 只在 Python 檔案中搜尋 "API_ENDPOINT" ``` -*使用:`search_code_advanced`,`file_pattern: "*.py"`(預設回傳 10 筆;使用 `max_results` 擴充或 `start_index` 換頁)* +*使用:`search_code_advanced` 的字面搜尋搭配 `file_pattern: "*.py"`(預設回傳 10 筆;使用 `max_results` 擴充或 `start_index` 換頁)* </details>
src/code_index_mcp/search/ag.py+3 −8 modified@@ -5,7 +5,7 @@ import subprocess from typing import Dict, List, Optional, Tuple -from .base import SearchStrategy, parse_search_output, create_word_boundary_pattern, is_safe_regex_pattern +from .base import SearchStrategy, parse_search_output, create_word_boundary_pattern class AgStrategy(SearchStrategy): """Search strategy using 'The Silver Searcher' (ag) command-line tool.""" @@ -51,15 +51,10 @@ def search( # Prepare search pattern search_pattern = pattern - if regex: - # Use regex mode - check for safety first - if not is_safe_regex_pattern(pattern): - raise ValueError(f"Potentially unsafe regex pattern: {pattern}") - # Don't add --literal, use regex mode - elif fuzzy: + if fuzzy: # Use word boundary pattern for partial matching search_pattern = create_word_boundary_pattern(pattern) - else: + elif not regex: # Use literal string search cmd.append('--literal')
src/code_index_mcp/search/base.py+0 −53 modified@@ -119,59 +119,6 @@ def create_word_boundary_pattern(pattern: str) -> str: boundary_pattern = f"\\b{escaped}\\b" return boundary_pattern - - -def is_safe_regex_pattern(pattern: str) -> bool: - """ - Check if a pattern appears to be a safe regex pattern. - - Args: - pattern: The search pattern to check - - Returns: - True if the pattern looks like a safe regex, False otherwise - """ - # Strong indicators of regex intent - strong_regex_indicators = ['|', '(', ')', '[', ']', '^', '$'] - - # Weaker indicators that need context - weak_regex_indicators = ['.', '*', '+', '?'] - - # Check for strong regex indicators - has_strong_regex = any(char in pattern for char in strong_regex_indicators) - - # Check for weak indicators with context - has_weak_regex = any(char in pattern for char in weak_regex_indicators) - - # If has strong indicators, likely regex - if has_strong_regex: - # Still check for dangerous patterns - dangerous_patterns = [ - r'(.+)+', # Nested quantifiers - r'(.*)*', # Nested stars - r'(.{0,})+', # Potential ReDoS patterns - ] - - has_dangerous_patterns = any(dangerous in pattern for dangerous in dangerous_patterns) - return not has_dangerous_patterns - - # If only weak indicators, need more context - if has_weak_regex: - # Patterns like ".*", ".+", "file.*py" look like regex - # But "file.txt", "test.py" look like literal filenames - regex_like_patterns = [ - r'\.\*', # .* - r'\.\+', # .+ - r'\.\w*\*', # .something* - r'\*\.', # *. - r'\w+\.\*\w*', # word.*word - ] - - return any(re.search(regex_pattern, pattern) for regex_pattern in regex_like_patterns) - - return False - - class SearchStrategy(ABC): """ Abstract base class for a search strategy.
src/code_index_mcp/search/basic.py+15 −11 modified@@ -7,15 +7,16 @@ from pathlib import Path from typing import Dict, List, Optional, Tuple -from .base import SearchStrategy, create_word_boundary_pattern, is_safe_regex_pattern +from .base import SearchStrategy, create_word_boundary_pattern class BasicSearchStrategy(SearchStrategy): """ A basic, pure-Python search strategy. This strategy iterates through files and lines manually. It's a fallback for when no advanced command-line search tools are available. - It does not support context lines. + It supports literal and fuzzy matching only. + It does not support context lines or regex mode. """ @property @@ -52,35 +53,38 @@ def search( """ Execute a basic, line-by-line search. - Note: This implementation does not support context_lines. + Note: This implementation supports literal and fuzzy matching only. + It does not support context_lines or regex mode. + Args: pattern: The search pattern base_path: Directory to search in case_sensitive: Whether search is case sensitive context_lines: Number of context lines (not supported) file_pattern: File pattern to filter fuzzy: Enable word boundary matching - regex: Enable regex pattern matching + regex: Request regex matching (rejected for the basic strategy) """ results: Dict[str, List[Tuple[int, str]]] = {} + + if regex: + raise ValueError( + "Regex mode requires an external search tool; " + "basic search only supports literal and fuzzy matching" + ) flags = 0 if case_sensitive else re.IGNORECASE try: - if regex: - # Use regex mode - check for safety first - if not is_safe_regex_pattern(pattern): - raise ValueError(f"Potentially unsafe regex pattern: {pattern}") - search_regex = re.compile(pattern, flags) - elif fuzzy: + if fuzzy: # Use word boundary pattern for partial matching search_pattern = create_word_boundary_pattern(pattern) search_regex = re.compile(search_pattern, flags) else: # Use literal string search search_regex = re.compile(re.escape(pattern), flags) except re.error as e: - raise ValueError(f"Invalid regex pattern: {pattern}, error: {e}") + raise ValueError(f"Invalid search pattern: {pattern}, error: {e}") file_filter = getattr(self, 'file_filter', None) base = Path(base_path)
src/code_index_mcp/search/grep.py+3 −11 modified@@ -5,7 +5,7 @@ import subprocess from typing import Dict, List, Optional, Tuple -from .base import SearchStrategy, parse_search_output, create_word_boundary_pattern, is_safe_regex_pattern +from .base import SearchStrategy, parse_search_output, create_word_boundary_pattern class GrepStrategy(SearchStrategy): """ @@ -53,22 +53,14 @@ def search( search_pattern = pattern if regex: - # Use regex mode - check for safety first - if not is_safe_regex_pattern(pattern): - raise ValueError(f"Potentially unsafe regex pattern: {pattern}") cmd.append('-E') # Extended Regular Expressions elif fuzzy: # Use word boundary pattern for partial matching search_pattern = create_word_boundary_pattern(pattern) cmd.append('-E') # Extended Regular Expressions else: - # Auto-detect if pattern looks like a safe regex - if is_safe_regex_pattern(pattern): - # Pattern contains regex chars, use extended regex mode - cmd.append('-E') - else: - # Use literal string search - cmd.append('-F') + # Regex is explicit; default searches are literal. + cmd.append('-F') if not case_sensitive: cmd.append('-i')
src/code_index_mcp/search/ripgrep.py+3 −8 modified@@ -5,7 +5,7 @@ import subprocess from typing import Dict, List, Optional, Tuple -from .base import SearchStrategy, parse_search_output, create_word_boundary_pattern, is_safe_regex_pattern +from .base import SearchStrategy, parse_search_output, create_word_boundary_pattern class RipgrepStrategy(SearchStrategy): """Search strategy using the 'ripgrep' (rg) command-line tool.""" @@ -49,15 +49,10 @@ def search( # Prepare search pattern search_pattern = pattern - if regex: - # Use regex mode - check for safety first - if not is_safe_regex_pattern(pattern): - raise ValueError(f"Potentially unsafe regex pattern: {pattern}") - # Don't add --fixed-strings, use regex mode - elif fuzzy: + if fuzzy: # Use word boundary pattern for partial matching search_pattern = create_word_boundary_pattern(pattern) - else: + elif not regex: # Use literal string search cmd.append('--fixed-strings')
src/code_index_mcp/search/ugrep.py+2 −7 modified@@ -5,7 +5,7 @@ import subprocess from typing import Dict, List, Optional, Tuple -from .base import SearchStrategy, parse_search_output, create_word_boundary_pattern, is_safe_regex_pattern +from .base import SearchStrategy, parse_search_output, create_word_boundary_pattern class UgrepStrategy(SearchStrategy): """Search strategy using the 'ugrep' (ug) command-line tool.""" @@ -49,12 +49,7 @@ def search( if fuzzy: # ugrep has native fuzzy search support cmd.append('--fuzzy') - elif regex: - # Use regex mode - check for safety first - if not is_safe_regex_pattern(pattern): - raise ValueError(f"Potentially unsafe regex pattern: {pattern}") - # Don't add --fixed-strings, use regex mode - else: + elif not regex: # Use literal string search cmd.append('--fixed-strings')
src/code_index_mcp/server.py+4 −3 modified@@ -285,15 +285,16 @@ def search_code_advanced( ctx: Context, case_sensitive: bool = True, context_lines: int = 0, - file_pattern: str = None, + file_pattern: str | None = None, fuzzy: bool = False, - regex: bool = None, + regex: bool | None = None, start_index: int = 0, max_results: int | None = 10, ) -> dict[str, Any]: """ Search for code pattern with pagination. Auto-selects best search tool (ugrep/ripgrep/ag/grep). -Supports glob file_pattern (e.g., "*.py"), regex patterns, and fuzzy matching (ugrep only). +Supports glob file_pattern (e.g., "*.py"), explicit regex mode, and fuzzy matching (ugrep only). +Regex matching requires passing regex=True and may require an external search tool. """ return SearchService(ctx).search_code( pattern=pattern,
src/code_index_mcp/services/search_service.py+11 −4 modified@@ -10,7 +10,6 @@ from .base_service import BaseService from ..utils import FileFilter, ResponseFormatter, ValidationHelper -from ..search.base import is_safe_regex_pattern class SearchService(BaseService): @@ -35,7 +34,7 @@ def search_code( # pylint: disable=too-many-arguments, too-many-locals self._require_project_setup() if regex is None: - regex = is_safe_regex_pattern(pattern) + regex = False error = ValidationHelper.validate_search_pattern(pattern, regex) if error: @@ -57,6 +56,12 @@ def search_code( # pylint: disable=too-many-arguments, too-many-locals if not strategy: raise ValueError("No search strategies available") + if regex and getattr(strategy, 'name', '').lower() == 'basic': + raise ValueError( + "Regex mode requires an external search tool; " + "basic search only supports literal and fuzzy matching" + ) + self._configure_strategy(strategy) try: @@ -100,11 +105,13 @@ def get_search_capabilities(self) -> Dict[str, Any]: return {"error": "Settings not available"} config = self.settings.get_search_tools_config() + available_tools = config.get('available_tools', []) + supports_regex = any(tool.lower() != 'basic' for tool in available_tools) capabilities = { - "available_tools": config.get('available_tools', []), + "available_tools": available_tools, "preferred_tool": config.get('preferred_tool', 'basic'), - "supports_regex": True, + "supports_regex": supports_regex, "supports_fuzzy": True, "supports_case_sensitivity": True, "supports_context_lines": True,
src/code_index_mcp/utils/validation.py+0 −13 modified@@ -138,7 +138,6 @@ def validate_search_pattern(pattern: str, regex: bool = False) -> Optional[str]: return "Search pattern cannot be empty" if regex: - # Basic regex validation - check for potentially dangerous patterns try: re.compile(pattern) except re.error as e: @@ -147,18 +146,6 @@ def validate_search_pattern(pattern: str, regex: bool = False) -> Optional[str]: "If you intended a literal search, pass regex=False." ) - # Check for potentially expensive regex patterns (basic ReDoS protection) - dangerous_patterns = [ - r'\(\?\=.*\)\+', # Positive lookahead with quantifier - r'\(\?\!.*\)\+', # Negative lookahead with quantifier - r'\(\?\<\=.*\)\+', # Positive lookbehind with quantifier - r'\(\?\<\!.*\)\+', # Negative lookbehind with quantifier - ] - - for dangerous in dangerous_patterns: - if re.search(dangerous, pattern): - return "Potentially dangerous regex pattern detected" - return None @staticmethod
tests/search/test_search_filters.py+26 −0 modified@@ -5,12 +5,15 @@ from pathlib import Path as _TestPath import sys +import pytest + ROOT = _TestPath(__file__).resolve().parents[2] SRC_PATH = ROOT / 'src' if str(SRC_PATH) not in sys.path: sys.path.insert(0, str(SRC_PATH)) from code_index_mcp.search.basic import BasicSearchStrategy +from code_index_mcp.search.grep import GrepStrategy from code_index_mcp.search.ripgrep import RipgrepStrategy from code_index_mcp.utils.file_filter import FileFilter @@ -37,6 +40,16 @@ def test_basic_strategy_skips_excluded_directories(tmp_path): assert excluded_path not in results +def test_basic_strategy_rejects_regex_mode_without_external_tool(tmp_path): + test_file = tmp_path / "app.py" + test_file.write_text("hello world\n") + + strategy = BasicSearchStrategy() + + with pytest.raises(ValueError, match="external search tool"): + strategy.search("hello.*world", str(tmp_path), regex=True) + + @patch("code_index_mcp.search.ripgrep.subprocess.run") def test_ripgrep_strategy_adds_exclude_globs(mock_run, tmp_path): mock_run.return_value = SimpleNamespace(returncode=0, stdout="", stderr="") @@ -50,3 +63,16 @@ def test_ripgrep_strategy_adds_exclude_globs(mock_run, tmp_path): glob_args = [cmd[i + 1] for i, arg in enumerate(cmd) if arg == '--glob' and i + 1 < len(cmd)] assert any(value.startswith('!**/node_modules/') for value in glob_args) + + +@patch("code_index_mcp.search.grep.subprocess.run") +def test_grep_strategy_treats_regex_chars_as_literal_without_regex_mode(mock_run, tmp_path): + mock_run.return_value = SimpleNamespace(returncode=0, stdout="", stderr="") + + strategy = GrepStrategy() + strategy.search("hello.*world", str(tmp_path), regex=False) + + cmd = mock_run.call_args[0][0] + + assert '-F' in cmd + assert '-E' not in cmd
tests/search/test_search_service_regex_mode.py+102 −0 added@@ -0,0 +1,102 @@ +"""Service-level tests for regex mode handling in search service.""" +from pathlib import Path as _TestPath +from types import SimpleNamespace +import sys + +import pytest + +ROOT = _TestPath(__file__).resolve().parents[2] +SRC_PATH = ROOT / 'src' +if str(SRC_PATH) not in sys.path: + sys.path.insert(0, str(SRC_PATH)) + +from code_index_mcp.services.search_service import SearchService + + +class _FakeStrategy: + def __init__(self, name: str): + self.name = name + self.calls = [] + + def search(self, **kwargs): + self.calls.append(kwargs) + return { + 'src/example.py': [(7, 'matched line')], + } + + +class _FakeSettings: + def __init__(self, strategy): + self.strategy = strategy + + def get_preferred_search_tool(self): + return self.strategy + + def get_file_watcher_config(self): + return {} + + def get_search_tools_config(self): + return { + 'available_tools': [self.strategy.name], + 'preferred_tool': self.strategy.name, + } + + +def _create_service(tmp_path, strategy: _FakeStrategy) -> SearchService: + ctx = SimpleNamespace( + request_context=SimpleNamespace( + lifespan_context=SimpleNamespace( + base_path=str(tmp_path), + settings=_FakeSettings(strategy), + ) + ) + ) + return SearchService(ctx) + + +def test_search_code_defaults_regex_none_to_false_for_basic_strategy(tmp_path): + strategy = _FakeStrategy('basic') + service = _create_service(tmp_path, strategy) + + response = service.search_code(pattern='hello.*world', regex=None) + + assert strategy.calls[0]['regex'] is False + assert response['results'] == [ + {'file': 'src/example.py', 'line': 7, 'text': 'matched line'}, + ] + + +def test_search_code_rejects_regex_mode_for_basic_strategy(tmp_path): + strategy = _FakeStrategy('basic') + service = _create_service(tmp_path, strategy) + + with pytest.raises(ValueError, match='external search tool'): + service.search_code(pattern='hello.*world', regex=True) + + +def test_search_code_allows_regex_mode_for_external_strategy(tmp_path): + strategy = _FakeStrategy('ripgrep') + service = _create_service(tmp_path, strategy) + + service.search_code(pattern='hello.*world', regex=True) + + assert strategy.calls[0]['regex'] is True + + +def test_search_code_rejects_malformed_explicit_regex(tmp_path): + strategy = _FakeStrategy('ripgrep') + service = _create_service(tmp_path, strategy) + + with pytest.raises(ValueError, match='Invalid regex pattern'): + service.search_code(pattern='hello(', regex=True) + + +def test_get_search_capabilities_reports_no_regex_for_basic_only_setup(tmp_path): + strategy = _FakeStrategy('basic') + service = _create_service(tmp_path, strategy) + + capabilities = service.get_search_capabilities() + + assert capabilities['available_tools'] == ['basic'] + assert capabilities['preferred_tool'] == 'basic' + assert capabilities['supports_regex'] is False
tests/test_server_search_code_advanced_signature.py+18 −0 added@@ -0,0 +1,18 @@ +import inspect +import os +import sys +from typing import get_type_hints + + +sys.path.insert(0, os.path.join(os.getcwd(), "src")) + +from code_index_mcp.server import search_code_advanced + + +def test_search_code_advanced_regex_parameter_allows_none(): + signature = inspect.signature(search_code_advanced) + regex_param = signature.parameters['regex'] + type_hints = get_type_hints(search_code_advanced) + + assert regex_param.default is None + assert type_hints['regex'] == bool | None
Vulnerability mechanics
Root cause
"The 'basic' search strategy incorrectly allowed regex patterns when it should have only supported literal and fuzzy matching."
Attack vector
An attacker can remotely trigger this vulnerability by sending a crafted regex pattern to the `search_code_advanced` function. This is possible because the `basic` search strategy, which is used as a fallback when no advanced search tools are available, did not properly validate or reject regex patterns. The vulnerability is exploitable by an authenticated user with low privileges.
Affected code
The vulnerability lies within the `BasicSearchStrategy` class in `src/code_index_mcp/search/basic.py` and the `search_code` function in `src/code_index_mcp/services/search_service.py`. The `is_safe_regex_pattern` function was removed, and the `BasicSearchStrategy` now explicitly raises an error if `regex` is true.
What the fix does
The patch modifies the `BasicSearchStrategy` to explicitly reject regex patterns, raising a `ValueError` if `regex=True` is passed. It also updates the `SearchService` to prevent the `basic` strategy from being used with regex enabled. This ensures that the basic search functionality only handles literal and fuzzy matching, preventing inefficient regex complexity attacks.
Preconditions
- authThe attacker must have low privileges.
Generated on Jun 3, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/johnhuang316/code-index-mcp/commit/25bc02fac74051ddae15ce79e952f00211b1ea6bnvd
- github.com/johnhuang316/code-index-mcp/issues/84nvd
- github.com/johnhuang316/code-index-mcp/releases/tag/v2.14.1nvd
- vuldb.com/cve/CVE-2026-10692nvd
- vuldb.com/submit/830786nvd
- vuldb.com/vuln/367961nvd
- vuldb.com/vuln/367961/ctinvd
News mentions
0No linked articles in our index yet.