VYPR
Moderate severityNVD Advisory· Published Oct 22, 2025· Updated Oct 22, 2025

XPath Injection in Hugging Face Smolagents search_item_ctrl_f Function

CVE-2025-11844

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.

PackageAffected versionsPatched versions
smolagentsPyPI
< 1.22.01.22.0

Affected products

2
  • Range: <1.22.0 (>=1.20.0, <=1.21.x)
  • huggingface/huggingface/smolagentsv5
    Range: unspecified

Patches

1
f570ed5e1799

Fix XPath injection in search_item_ctrl_f (#1768)

https://github.com/huggingface/smolagentsAlbert Villanova del MoralSep 25, 2025via ghsa
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

4

News mentions

0

No linked articles in our index yet.