fix: harden profile image URL validation per review feedback

- Restrict data URIs to safe raster formats (png/jpeg/gif/webp);
  SVG is excluded because it can carry embedded scripts.
- Block scheme-relative URLs (//host/path) which browsers resolve
  against the current protocol, bypassing the relative-path check.
This commit is contained in:
DrMelone 2026-04-03 22:19:11 +02:00
parent 9af9635751
commit a3a385c4d9

View file

@ -1,5 +1,14 @@
"""Validation utilities for user-supplied input."""
# Raster image formats safe to accept as base64 data URIs.
# SVG is intentionally excluded: it can carry embedded scripts.
_SAFE_DATA_IMAGE_PREFIXES = (
'data:image/png',
'data:image/jpeg',
'data:image/gif',
'data:image/webp',
)
def validate_profile_image_url(url: str) -> str:
"""
@ -9,27 +18,31 @@ def validate_profile_image_url(url: str) -> str:
- Empty string (falls back to default avatar)
- Relative paths starting with ``/`` (internal assets and API routes)
- ``http://`` and ``https://`` URLs (external avatars, OAuth pictures)
- ``data:image/*`` URIs (base64-encoded uploads from the frontend)
- ``data:image/{png,jpeg,gif,webp}`` URIs (base64-encoded uploads)
All other schemes (javascript:, file:, ftp:, etc.) are rejected.
SVG data URIs are rejected because SVG can contain embedded scripts.
"""
if not url:
return url
# Relative paths: covers /user.png, /static/favicon.png,
# /api/v1/users/{id}/profile/image, and any future internal routes.
if url.startswith('/'):
# Exclude scheme-relative URLs (//host/path) which browsers resolve
# against the current protocol.
if url.startswith('/') and not url.startswith('//'):
return url
# External images served over HTTP(S), e.g. OAuth provider avatars.
if url.startswith('https://') or url.startswith('http://'):
return url
# Base64-encoded images uploaded via the frontend.
if url.startswith('data:image/'):
# Base64-encoded raster images uploaded via the frontend.
if any(url.startswith(prefix) for prefix in _SAFE_DATA_IMAGE_PREFIXES):
return url
raise ValueError(
'Invalid profile image URL: must be a relative path, an HTTP(S) URL, '
'or a data:image URI.'
'or a data:image URI (png/jpeg/gif/webp).'
)