aio-libs/aiohttp · warning · HTTPForbidden
403: Forbidden
Error message
403: Forbidden
What it means
When the resolved path inside StaticResource is a directory and show_index is False (the default), aiohttp raises HTTPForbidden (403) rather than listing the directory contents. This prevents unintentional directory listing, which can leak sensitive filenames. The same 403 is also raised if a PermissionError occurs while checking is_dir() (e.g. the segment is not readable).
Source
Thrown at aiohttp/web_urldispatcher.py:665
else:
file_path = unresolved_path.resolve()
file_path.relative_to(self._directory)
except (ValueError, *CIRCULAR_SYMLINK_ERROR) as error:
# ValueError is raised for the relative check. Circular symlinks
# raise here on resolving for python < 3.13.
raise HTTPNotFound() from error
# if path is a directory, return the contents if permitted. Note the
# directory check will raise if a segment is not readable.
try:
if file_path.is_dir():
if self._show_index:
return Response(
text=self._directory_as_html(file_path),
content_type="text/html",
)
else:
raise HTTPForbidden()
except PermissionError as error:
raise HTTPForbidden() from error
# Return the file response, which handles all other checks.
return FileResponse(file_path, chunk_size=self._chunk_size)
def _directory_as_html(self, dir_path: Path) -> str:
"""returns directory's index as html."""
assert dir_path.is_dir()
relative_path_to_dir = dir_path.relative_to(self._directory).as_posix()
index_of = f"Index of /{html_escape(relative_path_to_dir)}"
h1 = f"<h1>{index_of}</h1>"
index_list = []
dir_index = dir_path.iterdir()
for _file in sorted(dir_index):
# show file url as relative to static pathView on GitHub (pinned to d041d4d0fd)
Solutions
- If you want directory listings, pass show_index=True to add_static.
- Otherwise, this 403 is expected — ensure clients request specific files, not directories.
- Fix filesystem permissions if PermissionError is the cause.
Example fix
// before
app.router.add_static('/static', '/app/static') # show_index defaults to False
// after (enable listings)
app.router.add_static('/static', '/app/static', show_index=True) Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def require_file_or_listing(path: Path, show_index: bool):
if path.is_dir() and not show_index:
raise PermissionError('Directory listing disabled')
Prevention
- Decide upfront whether directory listings are acceptable; default to show_index=False for security.
- Add an index.html in directories you want to appear navigable.
- Return a redirect to a default file for directory requests if listings are off.
When it happens
Trigger: A GET request to a static URL that maps to a directory (e.g. GET /static/subdir/ where subdir is a folder) when add_static was configured with show_index=False (default). Also when the OS denies read permission on a path segment, causing PermissionError during the is_dir() check.
Common situations: Default static configuration that does not enable directory listings; a request for a folder URL without a trailing index file; permission misconfiguration on the served directory; security probing of folder URLs.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- 404: Not Found
- 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/9b70ad79a4cd9292.
Report an issue: GitHub.