{"record":{"id":"8e4b8181ed045e27","repo":"unclecode/crawl4ai","slug":"type-type-name-may-not-be-constructed-from-an","errorCode":null,"errorMessage":"type '{type_name}' may not be constructed from an untrusted request","messagePattern":"type '(.+?)' may not be constructed from an untrusted request","errorType":"validation","errorClass":"UntrustedConfigError","httpStatus":null,"severity":"warning","filePath":"crawl4ai/async_configs.py","lineNumber":444,"sourceCode":"        isinstance(data, dict)\n        and \"type\" in data\n        and (\"params\" in data or (data[\"type\"] == \"dict\" and \"value\" in data))\n    ):\n        # Handle plain dictionaries\n        if data[\"type\"] == \"dict\" and \"value\" in data:\n            return {k: from_serializable_dict(v, provenance) for k, v in data[\"value\"].items()}\n\n        # Security: only allow known-safe types to be deserialized.\n        # Unknown types (e.g. logging.Logger serialized by older clients) are\n        # silently dropped (returned as None) instead of crashing the request.\n        type_name = data[\"type\"]\n        if type_name not in ALLOWED_DESERIALIZE_TYPES:\n            return None\n\n        # Untrusted bodies may only construct the strict subset of types and\n        # may not set forbidden power-fields.\n        if provenance == Provenance.UNTRUSTED and type_name not in UNTRUSTED_ALLOWED_TYPES:\n            raise UntrustedConfigError(\n                f\"type '{type_name}' may not be constructed from an untrusted request\"\n            )\n\n        cls = None\n        module_paths = [\"crawl4ai\"]\n        for module_path in module_paths:\n            try:\n                mod = importlib.import_module(module_path)\n                if hasattr(mod, type_name):\n                    cls = getattr(mod, type_name)\n                    break\n            except (ImportError, AttributeError):\n                continue\n\n        if cls is not None:\n            # Handle Enum\n            if issubclass(cls, Enum):\n                return cls(data[\"params\"])","sourceCodeStart":426,"sourceCodeEnd":462,"githubUrl":"https://github.com/unclecode/crawl4ai/blob/7e801521428ee12509994d39151006f64055ebe3/crawl4ai/async_configs.py#L426-L462","documentation":"EgressBlocked ('URL blocked') from resolve_and_pin when urlparse finds no hostname in the URL — e.g. 'http:///path', relative URLs, or bare strings. Like the empty-host check in assert_host_allowed, a URL without a host cannot be policy-checked or pinned, so it is rejected.","triggerScenarios":"Calling resolve_and_pin('http:///foo'), resolve_and_pin('/relative/path'), or a URL built by string concatenation that lost its authority component.","commonSituations":"Joining scraped relative links without urljoin; URLs assembled from config parts where the host segment is empty; passing an ID or slug where a URL was expected.","solutions":["Fully resolve relative URLs against the page base with urllib.parse.urljoin before validation","Require parsed.hostname to be non-empty in your URL intake validation","Type-check inputs: only absolute http(s) URL strings should reach the crawl API"],"exampleFix":"# before\ntarget = link  # '/docs/next' -> no host -> blocked\n\n# after\nfrom urllib.parse import urljoin\ntarget = urljoin(page_url, link)  # 'https://site.com/docs/next'","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"from urllib.parse import urlparse\ndef is_absolute_http_url(u) -> bool:\n    try:\n        p = urlparse(str(u))\n    except ValueError:\n        return False\n    return p.scheme in (\"http\", \"https\") and bool(p.hostname)","tryCatchPattern":"from urllib.parse import urljoin\nu = urljoin(base_page_url, link)  # resolve relative first\nif not is_absolute_http_url(u):\n    discard(link)\nelse:\n    target = resolve_and_pin(u)","preventionTips":["Always urljoin relative links against the page base before crawling","Require absolute http(s) URLs at your intake boundary","Type-check that crawl targets are strings, not ids/slugs"],"tags":["ssrf-protection","egress","url-validation","relative-url"],"backgroundTag":null,"analyzedSha":"7e801521428ee12509994d39151006f64055ebe3","analyzedAt":"2026-08-14T20:46:20.673Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}