{"record":{"id":"d18c837e710361ca","repo":"aio-libs/aiohttp","slug":"404-not-found","errorCode":null,"errorMessage":"404: Not Found","messagePattern":"404: Not Found","errorType":"http","errorClass":"HTTPNotFound","httpStatus":404,"severity":"warning","filePath":"aiohttp/web_urldispatcher.py","lineNumber":630,"sourceCode":"        allowed_methods = self._allowed_methods\n        if method not in allowed_methods:\n            return None, allowed_methods\n\n        match_dict = {\"filename\": _unquote_path_safe(path[len(self._prefix) + 1 :])}\n        return (UrlMappingMatchInfo(match_dict, self._routes[method]), allowed_methods)\n\n    def __len__(self) -> int:\n        return len(self._routes)\n\n    def __iter__(self) -> Iterator[AbstractRoute]:\n        return iter(self._routes.values())\n\n    async def _handle(self, request: Request) -> StreamResponse:\n        filename = request.match_info[\"filename\"]\n        if Path(filename).is_absolute():\n            # filename is an absolute path e.g. //network/share or D:\\path\n            # which could be a UNC path leading to NTLM credential theft\n            raise HTTPNotFound()\n        unresolved_path = self._directory.joinpath(filename)\n        loop = asyncio.get_running_loop()\n        return await loop.run_in_executor(\n            None, self._resolve_path_to_response, unresolved_path\n        )\n\n    def _resolve_path_to_response(self, unresolved_path: Path) -> StreamResponse:\n        \"\"\"Take the unresolved path and query the file system to form a response.\"\"\"\n        # Check for access outside the root directory. When the sandbox is\n        # broken, URI cannot traverse out, but symlinks can. Otherwise, no\n        # access outside root is permitted.\n        try:\n            if self._break_symlink_sandbox:\n                normalized_path = Path(os.path.normpath(unresolved_path))\n                normalized_path.relative_to(self._directory)\n                file_path = normalized_path.resolve()\n            else:\n                file_path = unresolved_path.resolve()","sourceCodeStart":612,"sourceCodeEnd":648,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_urldispatcher.py#L612-L648","documentation":"Inside StaticResource._handle, if the matched filename is an absolute path (e.g. '//network/share' on Linux or 'D:\\path' on Windows), aiohttp raises HTTPNotFound (404). Absolute filenames could escape the static root and, on Windows, reference a UNC path (//server/share) which is a known NTLM credential-theft vector. The 404 is a security guard masquerading as a normal not-found.","triggerScenarios":"A request whose resolved filename (after the static prefix is stripped) is absolute — for example GET /static//etc/passwd where the filename component becomes '/etc/passwd', or a Windows UNC-style path. Also triggered by URL-encoded separators that decode to an absolute path.","commonSituations":"Path-traversal attempts against a static file endpoint; misconfigured reverse proxy that forwards a leading slash; client libraries that normalize URLs in unexpected ways; security scanners probing the static route.","solutions":["This is the correct security behavior — no action needed on the server; the 404 protects you.","If legitimate clients hit this, fix the client URL construction to avoid leading slashes in the filename portion.","Ensure your reverse proxy strips or rejects paths that would produce absolute filenames."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef is_safe_static_filename(filename: str) -> bool:\n    return not Path(filename).is_absolute()","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat this 404 as correct security behavior; do not try to serve absolute paths.","Sanitize reverse-proxy input to avoid leading slashes in the filename segment.","Run security scanners against your static endpoint to confirm 404s for traversal attempts."],"tags":["static-files","security","path-traversal","http-404"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}