{"record":{"id":"523de8ef089fa47f","repo":"assafelovic/gpt-researcher","slug":"unsafe-blob-name-blob-name","errorCode":null,"errorMessage":"Unsafe blob name: {blob_name}","messagePattern":"Unsafe blob name: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"critical","filePath":"gpt_researcher/document/azure_document_loader.py","lineNumber":31,"sourceCode":"        temp_dir = Path(tempfile.mkdtemp()).resolve()\n        blobs = self.container.list_blobs()\n        file_paths = []\n        for blob in blobs:\n            blob_client = self.container.get_blob_client(blob.name)\n            local_path = self._get_blob_path(temp_dir, blob.name)\n            local_path.parent.mkdir(parents=True, exist_ok=True)\n            with open(local_path, \"wb\") as f:\n                blob_data = blob_client.download_blob()\n                f.write(blob_data.readall())\n            file_paths.append(str(local_path))\n        return file_paths  # Pass to existing DocumentLoader\n\n    @staticmethod\n    def _get_blob_path(temp_dir: Path, blob_name: str) -> Path:\n        \"\"\"Return a safe local path for an Azure blob name.\"\"\"\n        blob_path = PurePosixPath(blob_name.replace(\"\\\\\", \"/\"))\n        if blob_path.is_absolute() or \"..\" in blob_path.parts:\n            raise ValueError(f\"Unsafe blob name: {blob_name}\")\n\n        local_path = (temp_dir / Path(*blob_path.parts)).resolve()\n        if temp_dir != local_path and temp_dir not in local_path.parents:\n            raise ValueError(f\"Unsafe blob name: {blob_name}\")\n\n        return local_path\n","sourceCodeStart":13,"sourceCodeEnd":38,"githubUrl":"https://github.com/assafelovic/gpt-researcher/blob/6f998577d547b1e54ec662dac63583aa11e3b84b/gpt_researcher/document/azure_document_loader.py#L13-L38","documentation":"AzureDocumentLoader._get_blob_path rejects blob names that are absolute paths or contain '..' segments, preventing path traversal outside the temp directory. This is the first guard, on the un-resolved PurePosixPath parts.","triggerScenarios":"A blob named '../secrets.txt', '/etc/passwd', or 'a/../../b' supplied to load(); also Windows-style backslash variants like '..\\\\..\\\\x' after backslash-to-slash normalization.","commonSituations":"Blob names coming from untrusted user input or a compromised container listing; malformed metadata with leading slashes.","solutions":["Sanitize blob names: strip leading '/', reject '..' segments before calling load","If legitimate, store blobs under flat names or subpaths without traversal","Keep this exception—do not suppress; it is a security control"],"exampleFix":"# before\nloader.load(['../etc/passwd'])\n# after\nsafe = [n.lstrip('/') for n in names if '..' not in PurePosixPath(n.replace('\\\\','/')).parts]\nloader.load(safe)","handlingStrategy":"validation","validationCode":"from pathlib import PurePosixPath\ndef safe_blob(name: str) -> bool:\n    p = PurePosixPath(name.replace(\"\\\\\", \"/\"))\n    return not p.is_absolute() and \"..\" not in p.parts\nnames = [n for n in blob_names if safe_blob(n)]","typeGuard":"def is_safe_blob_name(name: str) -> bool:\n    p = PurePosixPath(str(name).replace(\"\\\\\", \"/\"))\n    return bool(name) and not p.is_absolute() and \"..\" not in p.parts","tryCatchPattern":"try:\n    loader.load(blobs)\nexcept ValueError as e:\n    if \"Unsafe blob name\" in str(e):\n        log_security_event(str(e)); skip_and_continue()\n    else: raise","preventionTips":["Never pass user-supplied blob names unfiltered","Strip leading slashes and reject '..' before calling load"],"tags":["python","azure","path-traversal","security","blob-storage"],"backgroundTag":"path-traversal-blocked","analyzedSha":"6f998577d547b1e54ec662dac63583aa11e3b84b","analyzedAt":"2026-08-28T17:50:07.383Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}