Constrain signature uploads to PNG data (#2844)

This commit is contained in:
Vykos
2026-06-05 13:17:43 +02:00
committed by GitHub
parent 688194113b
commit 0b0d747f1c
4 changed files with 150 additions and 18 deletions
+44 -16
View File
@@ -21,10 +21,44 @@ from src.auth_helpers import get_current_user
logger = logging.getLogger(__name__)
_DATA_URL_RE = re.compile(
r'^data:image/(?P<fmt>png|jpeg|jpg);base64,(?P<data>.+)$',
re.IGNORECASE | re.DOTALL,
)
_DATA_URL_RE = re.compile(r"^data:image/png;base64,(?P<data>.+)$", re.IGNORECASE | re.DOTALL)
_ANY_IMAGE_DATA_URL_RE = re.compile(r"^data:image/[^;]+;base64,", re.IGNORECASE)
_PNG_MAGIC = b"\x89PNG\r\n\x1a\n"
_MAX_SIGNATURE_BYTES = 2 * 1024 * 1024
_MAX_SIGNATURE_B64 = ((_MAX_SIGNATURE_BYTES + 2) // 3) * 4
_MAX_SIGNATURE_DIMENSION = 4096
def _normalize_signature_png(raw: str) -> str:
raw = (raw or "").strip()
m = _DATA_URL_RE.match(raw)
if m:
b64 = m.group("data")
elif _ANY_IMAGE_DATA_URL_RE.match(raw):
raise HTTPException(400, "Signature data must be a PNG image")
else:
b64 = raw
if len(b64) > _MAX_SIGNATURE_B64:
raise HTTPException(400, "Signature PNG is too large")
try:
payload = base64.b64decode(b64, validate=True)
except Exception:
raise HTTPException(400, "Signature data must be base64-encoded PNG bytes")
if not payload:
raise HTTPException(400, "Signature PNG is empty")
if len(payload) > _MAX_SIGNATURE_BYTES:
raise HTTPException(400, "Signature PNG is too large")
if not payload.startswith(_PNG_MAGIC):
raise HTTPException(400, "Signature data must be a PNG image")
return base64.b64encode(payload).decode("ascii")
def _signature_dimension(value: Optional[int]) -> Optional[int]:
if value is None:
return None
if not isinstance(value, int) or value < 1 or value > _MAX_SIGNATURE_DIMENSION:
raise HTTPException(400, "Signature dimensions are invalid")
return value
class SignatureCreate(BaseModel):
@@ -67,24 +101,18 @@ def setup_signature_routes() -> APIRouter:
@router.post("/api/signatures")
async def create_signature(request: Request, req: SignatureCreate) -> Dict[str, Any]:
user = get_current_user(request)
raw = (req.data or "").strip()
m = _DATA_URL_RE.match(raw)
b64 = m.group("data") if m else raw
try:
payload = base64.b64decode(b64, validate=True)
if not payload:
raise ValueError("empty payload")
except Exception:
raise HTTPException(400, "Signature data must be base64-encoded PNG bytes")
b64 = _normalize_signature_png(req.data)
width = _signature_dimension(req.width)
height = _signature_dimension(req.height)
sig = Signature(
id=str(uuid.uuid4()),
owner=user,
name=(req.name or "Signature").strip() or "Signature",
data_png=b64,
width=req.width,
height=req.height,
svg=req.svg,
width=width,
height=height,
svg=None,
)
db = SessionLocal()
try: