Root cause
SSRF & network boundaries introduced
feat: Add SearxNG search tool for privacy-focused web searches
How AI contributed: Direct introductionPublished Jun 18, 2026 in
mervinpraison/praisonaiAll 19 findings in this repository
PraisonAI SearxNG SSRF via unvalidated searxng_url: searxng_search() in searxng_tools.py and _search_searxng() in web_search.py accepted an attacker-controlled searxng_url parameter and issued requests.get(url, ...) without validating scheme, host, or port, allowing agents processing untrusted inputs to reach internal endpoints (including cloud metadata services) via prompt injection.
Root cause
feat: Add SearxNG search tool for privacy-focused web searches
Fix status
No minimum fix commit has been established for this finding.
Fix status unresolved
Case facts
@@ -0,0 +1,94 @@+"""SearxNG search functionality.++Usage:+from praisonaiagents.tools import searxng_search+results = searxng_search("AI news")++or +from praisonaiagents.tools import searxng+results = searxng("AI news")+"""++from typing import List, Dict, Optional+import logging+from importlib import util++def searxng_search(+ query: str, + max_results: int = 5,+ searxng_url: Optional[str] = None+) -> List[Dict]:+ """Perform an internet search using SearxNG instance.+ + Args:+ query: Search query string+ max_results: Maximum number of results to return+ searxng_url: SearxNG instance URL (defaults to localhost:32768)+ + Returns:+ List[Dict]: Search results with title, url, and snippet keys+ Returns error dict on failure+ """+ # Check if requests is available+ if util.find_spec("requests") is None:+ error_msg = "SearxNG search requires requests package. Install with: pip install requests"+ logging.error(error_msg)+ return [{"error": error_msg}]+ + try:+ import requests+ + # Default URL for local SearxNG instance+ url = searxng_url or "http://localhost:32768/search"+ + params = {+ 'q': query, + 'format': 'json',+ 'engines': 'google,bing,duckduckgo', # Multiple engines+ 'safesearch': '1' # Safe search enabled+ }+ + response = requests.get(url, params=params, timeout=10)+ response.raise_for_status()+ + raw_results = response.json().get('results', [])+ + # Standardize to PraisonAI format+ results = []+ for i, result in enumerate(raw_results[:max_results]):+ results.append({+ "title": result.get("title", ""),+ "url": result.get("url", ""),+ "snippet": result.get("content", "")+ })+ + return results+ + except requests.exceptions.ConnectionError:+ error_msg = f"Could not connect to SearxNG at {url}. Ensure SearxNG is running."+ logging.error(error_msg)+ return [{"error": error_msg}]+ except requests.exceptions.Timeout:+ error_msg = "SearxNG search request timed out"+ logging.error(error_msg)+ return [{"error": error_msg}]+ except requests.exceptions.RequestException as e:+ error_msg = f"SearxNG search request failed: {e}"+ logging.error(error_msg)+ return [{"error": error_msg}]+ except (ValueError, KeyError) as e:+ error_msg = f"Error parsing SearxNG response: {e}"+ logging.error(error_msg)+ return [{"error": error_msg}]++def searxng(query: str, max_results: int = 5, searxng_url: Optional[str] = None) -> List[Dict]:+ """Alias for searxng_search function."""+ return searxng_search(query, max_results, searxng_url)++if __name__ == "__main__":+ # Example usage@@ -7,6 +7,8 @@ # Direct functions 'internet_search': ('.duckduckgo_tools', None), 'duckduckgo': ('.duckduckgo_tools', None),+ 'searxng_search': ('.searxng_tools', None),+ 'searxng': ('.searxng_tools', None), # arXiv Tools 'search_arxiv': ('.arxiv_tools', None),@@ -171,7 +173,7 @@ def __getattr__(name: str) -> Any: # Direct function import module = import_module(module_path, __package__) if name in [- 'duckduckgo', 'internet_search',+ 'duckduckgo', 'internet_search', 'searxng_search', 'searxng', 'search_arxiv', 'get_arxiv_paper', 'get_papers_by_author', 'get_papers_by_category', 'wiki_search', 'wiki_summary', 'wiki_page', 'wiki_random', 'wiki_language', 'get_article', 'get_news_sources', 'get_articles_from_source', 'get_trending_topics',@@ -0,0 +1,199 @@+#!/usr/bin/env python3+"""+SearxNG Search Tool Example++This example demonstrates how to use the SearxNG search tool for privacy-focused web searches.+Make sure you have a SearxNG instance running locally before running this example.++Prerequisites:+1. Install dependencies: pip install requests+2. Run SearxNG: docker run -d --name searxng -p 32768:8080 searxng/searxng+3. Wait a moment for SearxNG to start up+"""++from praisonaiagents import Agent, Task, Process+from praisonaiagents.tools import searxng_search++def basic_search_example():+ """Basic SearxNG search example"""+ print("=== Basic SearxNG Search ===")+ + # Simple search+ results = searxng_search("Python programming tutorials", max_results=3)+ + for i, result in enumerate(results, 1):+ if "error" in result:+ print(f"Error: {result['error']}")+ break+ else:+ print(f"\n{i}. {result['title']}")+ print(f" URL: {result['url']}")+ print(f" Snippet: {result['snippet'][:100]}...")++def agent_search_example():+ """Example using SearxNG with an agent"""+ print("\n=== Agent-Based SearxNG Search ===")+ + # Create a research agent+ researcher = Agent(+ name="Research Agent",+ instructions="""You are a research assistant that uses SearxNG to find information.+ When searching, provide a comprehensive summary of the findings including:+ 1. Key points from the search results+ 2. Relevant URLs for further reading+ 3. Your analysis of the information quality""",+ tools=[searxng_search]+ )+ + # Create a task+ task = Task(+ description="Search for information about sustainable energy technologies and provide a summary",+ agent=researcher+ )+ + # Execute the task+ result = task.execute()+ print(f"Research Result:\n{result}")++def multi_search_example():+ """Example with multiple searches using different parameters"""+ print("\n=== Multi-Search Example ===")+ + queries = [+ ("artificial intelligence news", 3),+ ("climate change solutions", 3),+ ("quantum computing basics", 2)+ ]+ + for query, max_results in queries:+ print(f"\nSearching for: '{query}'")+ results = searxng_search(query, max_results=max_results)+ + if results and "error" not in results[0]:+ print(f"Found {len(results)} results:")+ for result in results:+ print(f" • {result['title']}")+ else:+ print(f" Error or no results: {results}")++def custom_searxng_url_example():+ """Example using custom SearxNG URL"""+ print("\n=== Custom SearxNG URL Example ===")+ + # Example with different SearxNG instances+ searxng_instances = [+ "http://localhost:32768/search", # Default local instance+ "http://localhost:8080/search", # Alternative port+ ]+ + query = "machine learning"