XPath Injection in Hugging Face Smolagents search_item_ctrl_f Function
Description
Hugging Face Smolagents version 1.20.0 contains an XPath injection vulnerability in the search_item_ctrl_f function located in src/smolagents/vision_web_browser.py. The function constructs an XPath query by directly concatenating user-supplied input into the XPath expression without proper sanitization or escaping. This allows an attacker to inject malicious XPath syntax that can alter the intended query logic. The vulnerability enables attackers to bypass search filters, access unintended DOM elements, and disrupt web automation workflows. This can lead to information disclosure, manipulation of AI agent interactions, and compromise the reliability of automated web tasks. The issue is fixed in version 1.22.0.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Hugging Face Smolagents 1.20.0 has an XPath injection in search_item_ctrl_f, allowing attackers to bypass filters and access unintended DOM elements, fixed in 1.22.0.
Vulnerability
Overview CVE-2025-11844 describes an XPath injection vulnerability in Hugging Face Smolagents version 1.20.0. The flaw resides in the search_item_ctrl_f function within src/smolagents/vision_web_browser.py. This function constructs an XPath query by directly concatenating user-supplied input into the XPath expression without proper sanitization or escaping [2].
Exploitation
Method An attacker can exploit this vulnerability by providing malicious input to the search functionality. Since the input is concatenated directly into the XPath expression, the attacker can inject XPath syntax that modifies the query logic. This can be achieved without authentication if the input is sourced from an untrusted user or external data [2].
Impact
The successful exploitation allows attackers to bypass intended search filters, access unintended DOM elements, and disrupt web automation workflows. This can lead to information disclosure, manipulation of AI agent interactions, and compromise the reliability of automated web tasks performed by the agent [2].
Mitigation
The issue has been addressed in Smolagents version 1.22.0. The fix includes the addition of an _escape_xpath_string function that properly escapes user input before inclusion in XPath queries, as demonstrated in the commit f570ed5e17999d4cf7d5e79c2830fbaefab8a794 [3]. Users are advised to upgrade to the patched version.
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
smolagentsPyPI | < 1.22.0 | 1.22.0 |
Affected products
2- Range: <1.22.0 (>=1.20.0, <=1.21.x)
- huggingface/huggingface/smolagentsv5Range: unspecified
Patches
1f570ed5e1799Fix XPath injection in search_item_ctrl_f (#1768)
2 files changed · +152 −1
src/smolagents/vision_web_browser.py+20 −1 modified@@ -84,6 +84,24 @@ def save_screenshot(memory_step: ActionStep, agent: CodeAgent) -> None: return +def _escape_xpath_string(s: str) -> str: + """ + Escapes a string for safe use in an XPath expression. + + Args: + s (`str`): Arbitrary input string to escape. + + Returns: + `str`: Valid XPath expression representing the literal value of `s`. + """ + if "'" not in s: + return f"'{s}'" + if '"' not in s: + return f'"{s}"' + parts = s.split("'") + return "concat(" + ', "\'", '.join(f"'{p}'" for p in parts) + ")" + + @tool def search_item_ctrl_f(text: str, nth_result: int = 1) -> str: """ @@ -92,7 +110,8 @@ def search_item_ctrl_f(text: str, nth_result: int = 1) -> str: text: The text to search for nth_result: Which occurrence to jump to (default: 1) """ - elements = driver.find_elements(By.XPATH, f"//*[contains(text(), '{text}')]") + escaped_text = _escape_xpath_string(text) + elements = driver.find_elements(By.XPATH, f"//*[contains(text(), {escaped_text})]") if nth_result > len(elements): raise Exception(f"Match n°{nth_result} not found (only {len(elements)} matches found)") result = f"Found {len(elements)} matches for '{text}'."
tests/test_vision_web_browser.py+132 −0 added@@ -0,0 +1,132 @@ +"""Test XPath injection vulnerability fix in vision_web_browser.py""" + +from unittest.mock import Mock, patch + +import pytest + +from smolagents.vision_web_browser import _escape_xpath_string, search_item_ctrl_f + + +@pytest.fixture +def mock_driver(): + """Mock Selenium WebDriver""" + driver = Mock() + driver.find_elements.return_value = [Mock()] # Mock found elements + driver.execute_script.return_value = None + return driver + + +class TestXPathEscaping: + """Test XPath string escaping functionality""" + + @pytest.mark.parametrize( + "input_text,expected_pattern", + [ + ("normal text", "'normal text'"), + ("text with 'quote'", "\"text with 'quote'\""), + ('text with "quote"', "'text with \"quote\"'"), + ("text with one single'quote", '"text with one single\'quote"'), + ('text with one double"quote', "'text with one double\"quote'"), + ( + "text with both 'single' and \"double\" quotes", + "concat('text with both ', \"'\", 'single', \"'\", ' and \"double\" quotes')", + ), + ("", "''"), + ("'", '"\'"'), + ('"', "'\"'"), + ], + ) + def test_escape_xpath_string_basic(self, input_text, expected_pattern): + """Test basic XPath escaping cases""" + result = _escape_xpath_string(input_text) + assert result == expected_pattern + + @pytest.mark.parametrize( + "input_text", + [ + "text with both 'single' and \"double\" quotes", + 'it\'s a "test" case', + "'mixed\" quotes'", + ], + ) + def test_escape_xpath_string_mixed_quotes(self, input_text): + """Test XPath escaping with mixed quotes uses concat()""" + result = _escape_xpath_string(input_text) + assert result.startswith("concat(") + assert result.endswith(")") + + @pytest.mark.parametrize( + "malicious_input", + [ + "')] | //script[@src='evil.js'] | foo[contains(text(), '", + "') or 1=1 or ('", + "')] | //user[contains(@role,'admin')] | foo[contains(text(), '", + "') and substring(//user[1]/password,1,1)='a", + ], + ) + def test_escape_prevents_injection(self, malicious_input): + """Test that malicious XPath injection attempts are safely escaped""" + result = _escape_xpath_string(malicious_input) + # Should either be wrapped in quotes or use concat() + assert ( + (result.startswith("'") and result.endswith("'")) + or (result.startswith('"') and result.endswith('"')) + or result.startswith("concat(") + ) + + +class TestSearchItemCtrlF: + """Test the search_item_ctrl_f function with XPath injection protection""" + + @pytest.mark.parametrize( + "search_text", + [ + "normal search", + "search with 'quotes'", + 'search with "quotes"', + "')] | //script[@src='evil.js'] | foo[contains(text(), '", + "') or 1=1 or ('", + ], + ) + def test_search_item_prevents_injection(self, search_text, mock_driver): + """Test that search_item_ctrl_f prevents XPath injection""" + with patch("smolagents.vision_web_browser.driver", mock_driver, create=True): + # Call the function + result = search_item_ctrl_f(search_text) + + # Verify driver.find_elements was called + mock_driver.find_elements.assert_called_once() + + # Get the actual XPath query that was generated + call_args = mock_driver.find_elements.call_args + xpath_query = call_args[0][1] # Second positional argument + + # Verify the query doesn't contain unescaped injection + if "')] | //" in search_text: + # For injection attempts, verify they're properly escaped + # The query should either use concat() or be properly quoted + is_concat = "concat(" in xpath_query + is_properly_quoted = xpath_query.count('"') >= 2 or xpath_query.count("'") >= 2 + assert is_concat or is_properly_quoted, f"XPath injection not prevented: {xpath_query}" + + # Verify we got a result + assert "Found" in result + + def test_search_item_nth_result(self, mock_driver): + """Test nth_result parameter works correctly""" + mock_driver.find_elements.return_value = [Mock(), Mock(), Mock()] # 3 elements + + with patch("smolagents.vision_web_browser.driver", mock_driver, create=True): + result = search_item_ctrl_f("test", nth_result=2) + + # Should find 3 matches and focus on element 2 + assert "Found 3 matches" in result + assert "Focused on element 2 of 3" in result + + def test_search_item_not_found(self, mock_driver): + """Test exception when nth_result exceeds available matches""" + mock_driver.find_elements.return_value = [Mock()] # Only 1 element + + with patch("smolagents.vision_web_browser.driver", mock_driver, create=True): + with pytest.raises(Exception, match="Match n°3 not found"): + search_item_ctrl_f("test", nth_result=3)
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.