aio-libs/aiohttp · warning · HTTPNotFound
404: Not Found
Error message
404: Not Found
What it means
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.
Source
Thrown at aiohttp/web_urldispatcher.py:630
allowed_methods = self._allowed_methods
if method not in allowed_methods:
return None, allowed_methods
match_dict = {"filename": _unquote_path_safe(path[len(self._prefix) + 1 :])}
return (UrlMappingMatchInfo(match_dict, self._routes[method]), allowed_methods)
def __len__(self) -> int:
return len(self._routes)
def __iter__(self) -> Iterator[AbstractRoute]:
return iter(self._routes.values())
async def _handle(self, request: Request) -> StreamResponse:
filename = request.match_info["filename"]
if Path(filename).is_absolute():
# filename is an absolute path e.g. //network/share or D:\path
# which could be a UNC path leading to NTLM credential theft
raise HTTPNotFound()
unresolved_path = self._directory.joinpath(filename)
loop = asyncio.get_running_loop()
return await loop.run_in_executor(
None, self._resolve_path_to_response, unresolved_path
)
def _resolve_path_to_response(self, unresolved_path: Path) -> StreamResponse:
"""Take the unresolved path and query the file system to form a response."""
# Check for access outside the root directory. When the sandbox is
# broken, URI cannot traverse out, but symlinks can. Otherwise, no
# access outside root is permitted.
try:
if self._break_symlink_sandbox:
normalized_path = Path(os.path.normpath(unresolved_path))
normalized_path.relative_to(self._directory)
file_path = normalized_path.resolve()
else:
file_path = unresolved_path.resolve()View on GitHub (pinned to d041d4d0fd)
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.
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def is_safe_static_filename(filename: str) -> bool:
return not Path(filename).is_absolute() Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- 403: Forbidden
- 1009
- 1009
- Cannot combine AUTHORIZATION header with credentials encoded
- Invalid challenge response
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/d18c837e710361ca.
Report an issue: GitHub.