XPath Injection Vulnerabilities

High Risk Input Validation
xpathxmlinjectiondata-extractionauthentication-bypassxml-database

What it is

XPath injection occurs when untrusted user input is used to construct XPath queries without proper validation or escaping. Attackers can manipulate XPath expressions to access unauthorized XML data, bypass authentication mechanisms, or extract sensitive information from XML documents and databases that support XPath queries.

import javax.xml.xpath.*;
import org.w3c.dom.*;

public class VulnerableXPathAuth {
    private Document xmlDoc;
    private XPath xpath;
    
    public boolean authenticateUser(String username, String password) {
        try {
            // VULNERABLE: Direct string concatenation
            String xpathExpr = "//users/user[username='" + username + 
                               "' and password='" + password + "']";
            
            XPathExpression expr = xpath.compile(xpathExpr);
            NodeList result = (NodeList) expr.evaluate(xmlDoc, XPathConstants.NODESET);
            
            return result.getLength() > 0;
        } catch (Exception e) {
            return false;
        }
    }
    
    public NodeList searchUsers(String searchTerm) {
        try {
            // VULNERABLE: No input validation
            String xpathExpr = "//users/user[contains(username, '" + searchTerm + "')]";
            
            XPathExpression expr = xpath.compile(xpathExpr);
            return (NodeList) expr.evaluate(xmlDoc, XPathConstants.NODESET);
        } catch (Exception e) {
            return null;
        }
    }
}

// Malicious inputs:
// username: "admin') or ('1'='1"
// searchTerm: "') or contains(//admin/password, '')"
// Results in unauthorized data access
import javax.xml.xpath.*;
import org.w3c.dom.*;
import java.util.regex.Pattern;

public class SecureXPathAuth {
    private Document xmlDoc;
    private XPath xpath;
    private static final Pattern SAFE_INPUT = Pattern.compile("^[a-zA-Z0-9._-]+$");
    private static final int MAX_INPUT_LENGTH = 50;
    
    public boolean authenticateUser(String username, String password) {
        try {
            // Input validation
            if (!isValidInput(username) || !isValidInput(password)) {
                return false;
            }
            
            // SECURE: Proper XPath escaping
            String escapedUsername = escapeXPathString(username);
            String escapedPassword = escapeXPathString(password);
            
            String xpathExpr = "//users/user[username=" + escapedUsername + 
                               " and password=" + escapedPassword + "]";
            
            XPathExpression expr = xpath.compile(xpathExpr);
            NodeList result = (NodeList) expr.evaluate(xmlDoc, XPathConstants.NODESET);
            
            return result.getLength() > 0;
        } catch (Exception e) {
            System.err.println("XPath authentication error: " + e.getMessage());
            return false;
        }
    }
    
    public NodeList searchUsers(String searchTerm) {
        try {
            // Comprehensive input validation
            if (!isValidSearchTerm(searchTerm)) {
                throw new IllegalArgumentException("Invalid search term");
            }
            
            String escapedTerm = escapeXPathString(searchTerm);
            
            // SECURE: Restricted XPath with absolute path
            String xpathExpr = "/root/users/user[contains(username, " + escapedTerm + ")]";
            
            XPathExpression expr = xpath.compile(xpathExpr);
            
            // Restrict context to users node only
            Node usersNode = (Node) xpath.evaluate("/root/users", xmlDoc, XPathConstants.NODE);
            if (usersNode == null) {
                return null;
            }
            
            NodeList result = (NodeList) expr.evaluate(usersNode, XPathConstants.NODESET);
            
            // Limit results
            if (result.getLength() > 100) {
                throw new SecurityException("Too many results returned");
            }
            
            return result;
            
        } catch (Exception e) {
            System.err.println("XPath search error: " + e.getMessage());
            return null;
        }
    }
    
    private boolean isValidInput(String input) {
        if (input == null || input.isEmpty() || input.length() > MAX_INPUT_LENGTH) {
            return false;
        }
        return SAFE_INPUT.matcher(input).matches();
    }
    
    private boolean isValidSearchTerm(String searchTerm) {
        if (searchTerm == null || searchTerm.isEmpty() || searchTerm.length() > MAX_INPUT_LENGTH) {
            return false;
        }
        
        // Allow alphanumeric, spaces, dots, hyphens, underscores
        Pattern searchPattern = Pattern.compile("^[a-zA-Z0-9\\s._-]+$");
        return searchPattern.matcher(searchTerm).matches();
    }
    
    private String escapeXPathString(String input) {
        if (input == null) {
            return "''";
        }
        
        // If no single quotes, wrap in single quotes
        if (!input.contains("'")) {
            return "'" + input + "'";
        }
        // If no double quotes, wrap in double quotes
        else if (!input.contains("\"")) {
            return "\"" + input + "\"";
        }
        // Contains both, use concat function
        else {
            StringBuilder result = new StringBuilder("concat(");
            String[] parts = input.split("'", -1);
            for (int i = 0; i < parts.length; i++) {
                if (i > 0) {
                    result.append(", \"'\" , ");
                }
                result.append("'").append(parts[i]).append("'");
            }
            result.append(")");
            return result.toString();
        }
    }
}

💡 Why This Fix Works

The vulnerable code directly concatenates user input into XPath expressions. The secure version implements proper input validation, XPath escaping, and context restrictions.

from lxml import etree
import xml.etree.ElementTree as ET

class VulnerableXMLProcessor:
    def __init__(self, xml_file):
        self.tree = etree.parse(xml_file)
        
    def find_products(self, category, min_price):
        # VULNERABLE: Direct string formatting
        xpath_query = f"//product[category='{category}' and price >= {min_price}]"
        
        try:
            results = self.tree.xpath(xpath_query)
            return results
        except Exception as e:
            return []
    
    def get_user_data(self, user_id, field):
        # VULNERABLE: No input validation
        xpath_query = f"//users/user[@id='{user_id}']/{field}"
        
        result = self.tree.xpath(xpath_query)
        return result[0].text if result else None
    
    def search_by_attribute(self, attr_name, attr_value):
        # VULNERABLE: Dynamic attribute access
        xpath_query = f"//*[@{attr_name}='{attr_value}']"
        
        return self.tree.xpath(xpath_query)

# Malicious inputs:
# category: "electronics'] | //admin[@password"
# field: "../../../admin/secret"
# attr_name: "password'] | //*[@admin"
# Results in unauthorized data access and XML structure traversal
from lxml import etree
import xml.etree.ElementTree as ET
import re
from typing import List, Optional, Any

class SecureXMLProcessor:
    def __init__(self, xml_file):
        self.tree = etree.parse(xml_file)
        
        # Define allowed fields and attributes
        self.allowed_fields = ['name', 'description', 'category', 'price', 'stock']
        self.allowed_attributes = ['id', 'type', 'status', 'category']
        self.allowed_categories = ['electronics', 'books', 'clothing', 'home']
        
    def find_products(self, category: str, min_price: float) -> List[Any]:
        try:
            # Input validation
            if not self._validate_category(category):
                raise ValueError("Invalid category")
            
            if not isinstance(min_price, (int, float)) or min_price < 0:
                raise ValueError("Invalid price")
            
            # SECURE: Use parameterized approach with validation
            # Escape the category string properly
            escaped_category = self._escape_xpath_string(category)
            
            xpath_query = f"//product[category={escaped_category} and price >= {min_price}]"
            
            results = self.tree.xpath(xpath_query)
            
            # Limit results to prevent resource exhaustion
            return results[:100]
            
        except Exception as e:
            print(f"Product search error: {e}")
            return []
    
    def get_user_data(self, user_id: str, field: str) -> Optional[str]:
        try {
            # Strict input validation
            if not self._validate_user_id(user_id):
                raise ValueError("Invalid user ID")
            
            if field not in self.allowed_fields:
                raise ValueError(f"Field '{field}' not allowed")
            
            escaped_user_id = self._escape_xpath_string(user_id)
            
            # SECURE: Absolute path with restricted context
            xpath_query = f"/root/users/user[@id={escaped_user_id}]/{field}"
            
            # Restrict to users section only
            users_node = self.tree.xpath("/root/users")[0]
            result = users_node.xpath(f"user[@id={escaped_user_id}]/{field}")
            
            return result[0].text if result else None
            
        except Exception as e:
            print(f"User data access error: {e}")
            return None
    
    def search_by_attribute(self, attr_name: str, attr_value: str) -> List[Any]:
        try:
            # Validate attribute name against whitelist
            if attr_name not in self.allowed_attributes:
                raise ValueError(f"Attribute '{attr_name}' not allowed")
            
            if not self._validate_attribute_value(attr_value):
                raise ValueError("Invalid attribute value")
            
            escaped_value = self._escape_xpath_string(attr_value)
            
            # SECURE: Restricted search with absolute path
            xpath_query = f"/root/products/product[@{attr_name}={escaped_value}]"
            
            results = self.tree.xpath(xpath_query)
            
            # Limit and log results
            limited_results = results[:50]
            if len(results) > 50:
                print(f"Warning: Search returned {len(results)} results, limited to 50")
            
            return limited_results
            
        except Exception as e:
            print(f"Attribute search error: {e}")
            return []
    
    def _validate_category(self, category: str) -> bool:
        if not isinstance(category, str) or len(category) > 50:
            return False
        return category in self.allowed_categories
    
    def _validate_user_id(self, user_id: str) -> bool:
        if not isinstance(user_id, str) or len(user_id) > 20:
            return False
        # Only allow alphanumeric user IDs
        return re.match(r'^[a-zA-Z0-9]+$', user_id) is not None
    
    def _validate_attribute_value(self, value: str) -> bool:
        if not isinstance(value, str) or len(value) > 100:
            return False
        # Allow alphanumeric, spaces, hyphens, underscores
        return re.match(r'^[a-zA-Z0-9\s._-]+$', value) is not None
    
    def _escape_xpath_string(self, input_string: str) -> str:
        """Properly escape string for XPath queries"""
        if input_string is None:
            return "''"
        
        # If no single quotes, wrap in single quotes
        if "'" not in input_string:
            return f"'{input_string}'"
        # If no double quotes, wrap in double quotes
        elif '"' not in input_string:
            return f'"{input_string}"'
        else:
            # Contains both types of quotes - use concat()
            parts = input_string.split("'")
            escaped_parts = []
            for i, part in enumerate(parts):
                if i > 0:
                    escaped_parts.append("\"'\"")  # Add escaped single quote
                if part:
                    escaped_parts.append(f"'{part}'")
            return f"concat({', '.join(escaped_parts)})"
    
    def get_safe_product_count(self, category: str) -> int:
        """Example of safe counting without data exposure"""
        try:
            if not self._validate_category(category):
                return 0
            
            escaped_category = self._escape_xpath_string(category)
            xpath_query = f"count(//product[category={escaped_category}])"
            
            result = self.tree.xpath(xpath_query)
            return int(result) if isinstance(result, (int, float)) else 0
            
        except Exception as e:
            print(f"Count error: {e}")
            return 0

💡 Why This Fix Works

The vulnerable Python code has no input validation and uses direct string formatting in XPath queries. The secure version implements comprehensive validation, escaping, and context restrictions.

using System;
using System.Xml;
using System.Xml.XPath;

public class VulnerableConfigProcessor
{
    private XmlDocument xmlDoc;
    
    public VulnerableConfigProcessor(string configFile)
    {
        xmlDoc = new XmlDocument();
        xmlDoc.Load(configFile);
    }
    
    public string GetConfigValue(string section, string key)
    {
        // VULNERABLE: Direct string concatenation
        string xpath = $"//configuration/{section}/add[@key='{key}']/@value";
        
        XmlNode node = xmlDoc.SelectSingleNode(xpath);
        return node?.Value;
    }
    
    public XmlNodeList FindUsersByRole(string role)
    {
        // VULNERABLE: No input validation
        string xpath = $"//users/user[contains(@roles, '{role}')]";
        
        return xmlDoc.SelectNodes(xpath);
    }
    
    public bool ValidateUserCredentials(string username, string password)
    {
        // VULNERABLE: Authentication bypass possible
        string xpath = $"//users/user[@username='{username}' and @password='{password}']";
        
        XmlNode userNode = xmlDoc.SelectSingleNode(xpath);
        return userNode != null;
    }
}

// Malicious inputs:
// key: "dbPassword'] | //admin[@*"
// role: "user'] | //configuration//*[@password"
// username: "admin'] | //user[@username"
// Results in configuration data exposure and authentication bypass
using System;
using System.Xml;
using System.Xml.XPath;
using System.Text.RegularExpressions;
using System.Collections.Generic;

public class SecureConfigProcessor
{
    private XmlDocument xmlDoc;
    private static readonly Regex SafeKeyPattern = new Regex(@"^[a-zA-Z0-9._-]+$");
    private static readonly Regex SafeValuePattern = new Regex(@"^[a-zA-Z0-9\s._-]+$");
    
    // Whitelist of allowed configuration sections
    private static readonly HashSet<string> AllowedSections = new HashSet<string>
    {
        "appSettings", "database", "logging", "features"
    };
    
    // Whitelist of allowed user roles
    private static readonly HashSet<string> AllowedRoles = new HashSet<string>
    {
        "user", "admin", "moderator", "viewer"
    };
    
    public SecureConfigProcessor(string configFile)
    {
        xmlDoc = new XmlDocument();
        xmlDoc.Load(configFile);
    }
    
    public string GetConfigValue(string section, string key)
    {
        try
        {
            // Input validation
            if (!ValidateSection(section) || !ValidateKey(key))
            {
                throw new ArgumentException("Invalid section or key");
            }
            
            // SECURE: Proper XPath escaping
            string escapedSection = EscapeXPathString(section);
            string escapedKey = EscapeXPathString(key);
            
            string xpath = $"//configuration/{section}/add[@key={escapedKey}]/@value";
            
            XmlNode node = xmlDoc.SelectSingleNode(xpath);
            return node?.Value;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Config access error: {ex.Message}");
            return null;
        }
    }
    
    public XmlNodeList FindUsersByRole(string role)
    {
        try
        {
            // Validate role against whitelist
            if (!AllowedRoles.Contains(role))
            {
                throw new ArgumentException($"Role '{role}' not allowed");
            }
            
            string escapedRole = EscapeXPathString(role);
            
            // SECURE: Absolute path with restricted context
            string xpath = $"/configuration/users/user[contains(@roles, {escapedRole})]";
            
            XmlNodeList results = xmlDoc.SelectNodes(xpath);
            
            // Limit results
            if (results.Count > 100)
            {
                Console.WriteLine($"Warning: Query returned {results.Count} results, consider refining search");
            }
            
            return results;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"User search error: {ex.Message}");
            return null;
        }
    }
    
    public bool ValidateUserCredentials(string username, string password)
    {
        try
        {
            // Input validation
            if (!ValidateUsername(username) || string.IsNullOrEmpty(password))
            {
                return false;
            }
            
            // NOTE: In production, passwords should be hashed!
            // This is for demonstration of XPath security only
            
            string escapedUsername = EscapeXPathString(username);
            
            // SECURE: Use separate queries to prevent injection
            string userXPath = $"/configuration/users/user[@username={escapedUsername}]";
            
            XmlNode userNode = xmlDoc.SelectSingleNode(userXPath);
            if (userNode == null)
            {
                return false;
            }
            
            // Get the stored password and compare securely
            string storedPassword = userNode.Attributes["password"]?.Value;
            
            // In production: use bcrypt or similar for password comparison
            return storedPassword == password;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Authentication error: {ex.Message}");
            return false;
        }
    }
    
    private bool ValidateSection(string section)
    {
        if (string.IsNullOrEmpty(section) || section.Length > 50)
            return false;
            
        return AllowedSections.Contains(section);
    }
    
    private bool ValidateKey(string key)
    {
        if (string.IsNullOrEmpty(key) || key.Length > 100)
            return false;
            
        return SafeKeyPattern.IsMatch(key);
    }
    
    private bool ValidateUsername(string username)
    {
        if (string.IsNullOrEmpty(username) || username.Length > 50)
            return false;
            
        return SafeKeyPattern.IsMatch(username);
    }
    
    private string EscapeXPathString(string input)
    {
        if (string.IsNullOrEmpty(input))
            return "\"\"";
        
        // If no single quotes, wrap in single quotes
        if (!input.Contains("'"))
        {
            return $"'{input}'";
        }
        // If no double quotes, wrap in double quotes
        else if (!input.Contains("\""))
        {
            return $"\"{input}\"";
        }
        // Contains both, use concat function
        else
        {
            string[] parts = input.Split('\'');
            List<string> escapedParts = new List<string>();
            
            for (int i = 0; i < parts.Length; i++)
            {
                if (i > 0)
                {
                    escapedParts.Add("\"'\"\";
                }
                if (!string.IsNullOrEmpty(parts[i]))
                {
                    escapedParts.Add($"'{parts[i]}'\";
                }
            }
            
            return $"concat({string.Join(", ", escapedParts)})";
        }
    }
    
    public int GetUserCount()
    {
        try
        {
            // Safe counting without exposing data
            XmlNodeList users = xmlDoc.SelectNodes("/configuration/users/user");
            return users?.Count ?? 0;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Count error: {ex.Message}");
            return 0;
        }
    }
}

💡 Why This Fix Works

The vulnerable C# code directly concatenates user input into XPath queries. The secure version implements whitelisting, proper escaping, and separated authentication logic.

Why it happens

The most common cause of XPath injection is directly concatenating user input into XPath query strings. This allows attackers to inject malicious XPath expressions that can modify query logic, access unauthorized data, or bypass authentication checks.

Root causes

String Concatenation in XPath Expressions

The most common cause of XPath injection is directly concatenating user input into XPath query strings. This allows attackers to inject malicious XPath expressions that can modify query logic, access unauthorized data, or bypass authentication checks.

Preview example – JAVA
// Vulnerable XPath query construction
String xpath = "//user[username='" + userInput + "' and password='" + password + "']";
// Attacker input: "admin' or '1'='1"
// Results in: //user[username='admin' or '1'='1' and password='pass']

Unescaped User Input in XML Filtering

Applications that filter XML data based on user input without proper escaping are vulnerable to XPath injection. This commonly occurs in XML-based configuration systems, data export features, and XML search functionality.

Preview example – PYTHON
# Vulnerable XML filtering
def search_xml_data(search_term):
    xpath_query = f"//product[contains(name, '{search_term}')]"  
    # Attacker input: "') or contains(price, '0"
    # Results in: //product[contains(name, '') or contains(price, '0')]

Dynamic XPath Construction for Authentication

Using XPath for user authentication by dynamically constructing queries based on username and password input creates serious vulnerabilities. Attackers can use XPath operators to bypass authentication entirely.

Preview example – JAVA
// Vulnerable XPath authentication
public boolean authenticateUser(String username, String password) {
    String xpathExpr = "//users/user[username='" + username + 
                       "' and password='" + password + "']";
    return (Boolean) xpath.evaluate(xpathExpr, xmlDoc, XPathConstants.BOOLEAN);
}

Insufficient XPath Context Restrictions

Failing to restrict XPath query context allows attackers to traverse the entire XML document structure. Without proper node restrictions, attackers can access data outside the intended scope of the application.

Preview example – XML
<!-- Vulnerable: Unrestricted XPath context -->
<!-- XML structure with sensitive data -->
<root>
    <users>
        <user id="1">...</user>
    </users>
    <admin_config>
        <api_key>secret_key_123</api_key>
    </admin_config>
</root>

// Malicious XPath: "//admin_config/api_key" or "../../admin_config"

Fixes

1

Use Parameterized XPath Queries

When available, use XPath libraries that support parameterized queries or variable binding. This separates XPath logic from data and prevents injection attacks by treating user input as data rather than executable code.

View implementation – JAVA
// Java with parameterized XPath using XPathExpression
XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();

// Use XPath variables instead of string concatenation
String xpathExpr = "//user[username=$username and password=$password]";
XPathExpression expr = xpath.compile(xpathExpr);

// Set variables (if supported by implementation)
SimpleVariableResolver resolver = new SimpleVariableResolver();
resolver.addVariable("username", username);
resolver.addVariable("password", password);
xpath.setXPathVariableResolver(resolver);

boolean result = (Boolean) expr.evaluate(xmlDoc, XPathConstants.BOOLEAN);
2

Implement Proper XPath Escaping

Escape special XPath characters before including user input in XPath expressions. Focus on escaping single quotes, double quotes, and XPath operators. Create helper functions for consistent escaping across the application.

View implementation – PYTHON
def escape_xpath_string(input_string):
    """Properly escape string for XPath queries"""
    if input_string is None:
        return "''"
    
    # Handle strings that contain both single and double quotes
    if "'" not in input_string:
        return f"'{input_string}'"  # Wrap in single quotes
    elif '"' not in input_string:
        return f'"{input_string}"'  # Wrap in double quotes
    else:
        # Contains both types of quotes - use concat()
        parts = input_string.split("'")
        escaped_parts = [f"'{part}'" if part else "\"'\"" for part in parts]
        return f"concat({', '.join(escaped_parts)})"

# Usage
safe_username = escape_xpath_string(user_input)
xpath_query = f"//user[username={safe_username}]"
3

Validate and Sanitize XPath Input

Implement comprehensive input validation before using data in XPath queries. Use whitelist validation for expected characters, enforce length limits, and validate input format. Reject input containing XPath operators and functions.

View implementation – PYTHON
import re
from typing import Optional

def validate_xpath_input(user_input: str, input_type: str = 'search') -> Optional[str]:
    """Validate user input for XPath queries"""
    
    if not user_input or len(user_input) > 100:
        raise ValueError("Input length invalid")
    
    # Remove potentially dangerous XPath characters and functions
    dangerous_patterns = [
        r'\[',     # Square brackets for predicates
        r'\]',
        r'//',     # Descendant axis
        r'\|',     # Union operator
        r'and\s',  # Boolean operators
        r'or\s',
        r'not\s',
        r'text\(', # XPath functions
        r'node\(',
        r'position\(',
        r'last\(',
        r'count\(',
        r'string\(',
        r'contains\(',
        r'starts-with\(',
        r'normalize-space\(',
    ]
    
    for pattern in dangerous_patterns:
        if re.search(pattern, user_input, re.IGNORECASE):
            raise ValueError(f"Invalid characters detected: {pattern}")
    
    # Type-specific validation
    if input_type == 'username':
        if not re.match(r'^[a-zA-Z0-9._-]+$', user_input):
            raise ValueError("Username contains invalid characters")
    elif input_type == 'search':
        if not re.match(r'^[a-zA-Z0-9\s._-]+$', user_input):
            raise ValueError("Search term contains invalid characters")
    
    return user_input
4

Restrict XPath Context and Use Absolute Paths

Limit XPath queries to specific document sections and use absolute paths when possible. Avoid using descendant axes (//) that can traverse the entire document. Implement context restrictions to prevent access to sensitive document areas.

View implementation – JAVA
// Java: Restrict XPath context to specific nodes
public NodeList searchUsers(String searchTerm, Document xmlDoc) {
    try {
        // Validate input first
        String validatedTerm = validateSearchTerm(searchTerm);
        String escapedTerm = escapeXPathString(validatedTerm);
        
        // SECURE: Use absolute path with restricted context
        String xpathExpr = "/root/users/user[contains(username, " + escapedTerm + ")]";
        
        XPathFactory xpathFactory = XPathFactory.newInstance();
        XPath xpath = xpathFactory.newXPath();
        XPathExpression expr = xpath.compile(xpathExpr);
        
        // Limit to users node only
        Node usersNode = (Node) xpath.evaluate("/root/users", xmlDoc, XPathConstants.NODE);
        
        return (NodeList) expr.evaluate(usersNode, XPathConstants.NODESET);
        
    } catch (Exception e) {
        throw new SecurityException("XPath query failed", e);
    }
}
5

Implement XPath Query Monitoring and Logging

Monitor XPath queries for suspicious patterns, log all query attempts, and implement rate limiting. Use security tools to detect potential injection attempts and implement alerting for unusual query patterns.

View implementation – PYTHON
import logging
import time
from collections import defaultdict

class XPathSecurityMonitor:
    def __init__(self):
        self.query_log = []
        self.rate_limits = defaultdict(list)
        
    def validate_and_log_query(self, xpath_query, user_id, source_ip):
        """Validate XPath query and log for security monitoring"""
        
        # Log all queries
        log_entry = {
            'timestamp': time.time(),
            'user_id': user_id,
            'source_ip': source_ip,
            'xpath_query': xpath_query
        }
        self.query_log.append(log_entry)
        
        # Rate limiting
        current_time = time.time()
        user_queries = self.rate_limits[user_id]
        
        # Remove old entries (older than 1 minute)
        user_queries = [t for t in user_queries if current_time - t < 60]
        self.rate_limits[user_id] = user_queries
        
        if len(user_queries) > 10:  # Max 10 queries per minute
            logging.warning(f"Rate limit exceeded for user {user_id}")
            raise ValueError("Too many XPath queries")
        
        user_queries.append(current_time)
        
        # Detect suspicious patterns
        suspicious_indicators = [
            'or 1=1',
            'or true()',
            '//',
            'parent::',
            'ancestor::',
            'following::', 
            'preceding::',
            'document(',
            'unparsed-text('
        ]
        
        query_lower = xpath_query.lower()
        for indicator in suspicious_indicators:
            if indicator in query_lower:
                logging.critical(f"Suspicious XPath query from user {user_id}: {xpath_query}")
                raise SecurityError("Potentially malicious XPath query detected")
        
        logging.info(f"XPath query validated for user {user_id}")
        return True

Detect This Vulnerability in Your Code

Sourcery automatically identifies xpath injection vulnerabilities and many other security issues in your codebase.