crewAIInc/crewAI · error · ValueError

Blocked unsafe {label}: {e}

Error message

Blocked unsafe {label}: {e}

What it means

Raised by RAGTool.add() when a URL argument fails CrewAI's SSRF guard (validate_url). Every URL-scheme argument is checked before being handed to the storage layer; if the URL targets a blocked host (loopback/private/reserved IPs, non-http(s) schemes, credentials in URL, etc.) the guard's reason is wrapped as 'Blocked unsafe URL: <reason>'. The {label} variant appears in the shared _check_url/_check_path helpers used for typed content items.

Source

Thrown at lib/crewai-tools/src/crewai_tools/tools/rag/rag_tool.py:288

            # Keyword argument (documented API)
            rag_tool.add(path="path/to/document.pdf", data_type="file")
            rag_tool.add(file_path="path/to/document.pdf", data_type="pdf_file")

            # Auto-detect type from extension
            rag_tool.add("path/to/document.pdf")  # auto-detects PDF
        """
        # Validate file paths and URLs before adding to prevent
        # unauthorized file reads and SSRF.
        from urllib.parse import urlparse

        from crewai_tools.security.safe_path import validate_file_path, validate_url

        def _check_url(value: str, label: str) -> None:
            try:
                validate_url(value)
            except ValueError as e:
                raise ValueError(f"Blocked unsafe {label}: {e}") from e

        def _check_path(value: str, label: str) -> str:
            try:
                return validate_file_path(value)
            except ValueError as e:
                raise ValueError(f"Blocked unsafe {label}: {e}") from e

        validated_args: list[ContentItem] = []
        for arg in args:
            source_ref = (
                str(arg.get("source", arg.get("content", "")))
                if isinstance(arg, dict)
                else str(arg)
            )

            # Check if it's a URL — only catch urlparse-specific errors here;
            # validate_url's ValueError must propagate so it is never silently bypassed.
            try:

View on GitHub (pinned to 754d7323be)

Solutions

  1. If the target is genuinely safe, ingest its content directly: fetch it yourself from the allowed network context and pass the text/blob via a content item instead of a URL
  2. Check the wrapped reason (e) — it states exactly why the URL was rejected
  3. For local testing, expose the content via a public tunnel or write it to a file and add the file path instead
  4. Never disable the guard in shared/production code — it exists to stop agent-driven SSRF

Example fix

# before
rag_tool.add('http://localhost:8000/docs/index.html')  # ValueError: Blocked unsafe URL

# after
import requests
text = requests.get('http://localhost:8000/docs/index.html', timeout=10).text
rag_tool.add({'content': text})  # plain content bypasses URL fetching entirely
Defensive patterns

Strategy: validation

Validate before calling

from crewai_tools.security.safe_path import validate_url

def url_safe(u: str) -> bool:
    try:
        validate_url(u)
        return True
    except ValueError:
        return False

assert url_safe(target_url), f"URL rejected by SSRF guard: {target_url}"

Try / catch

try:
    rag_tool.add(url)
except ValueError as e:
    if str(e).startswith("Blocked unsafe"):
        # fetch content from an allowed context and add as text instead
        text = fetch_via_proxy(url)
        rag_tool.add({"content": text})
    else:
        raise

Prevention

When it happens

Trigger: Calling rag_tool.add('http://169.254.169.254/latest/meta-data') or any URL resolving to loopback/private/reserved ranges, non-http(s) schemes, or otherwise disallowed targets — enforced by crewai_tools.security.safe_path.validate_url before ingestion.

Common situations: Agents instructed to fetch internal endpoints (localhost dev servers, 10.x/192.168.x hosts, cloud metadata IPs); legitimate intranet ingestion being blocked by the SSRF guard; passing URLs with embedded userinfo.

Related errors


AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15). Data as JSON: /api/errors/c87fec8808b506b8. Report an issue: GitHub.