cdnV2

git clone https://github.com/notamitgamer/cdnV2.git
· rebuilt hourlyOpen on GitHub
commit c8ce8fc0851ed441edbcabb8a4674bd44e869142
parent 9afeb3f01d277d39ec93ff167f99150eaedb5e1b
Author: Amit Dutta <mail@amit.is-a.dev>
Date:   Sun,  4 Oct 2026 10:44:26 +0530

Merge pull request #28 from notamitgamer/Fix

Fix
Diffstat:
Mapp/main.py | 21++++++++++++++++++---
Aapp/upload_guard.py | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 157 insertions(+), 3 deletions(-)

diff --git a/app/main.py b/app/main.py @@ -35,8 +35,10 @@ from .storage import ( ) from .gh_oidc import verify_actions_token from .shortener import shorten_url, get_destination_url +from .upload_guard import router as upload_guard_router, get_client_ip, inspect_upload, record_upload app = FastAPI() +app.include_router(upload_guard_router) templates = Jinja2Templates(directory="app/templates") STATIC_DIR = Path(__file__).parent / "static" @@ -272,7 +274,7 @@ async def handle_upload( folder: str = Form("uploads"), ): folder = _validate_folder(folder) - client_ip = request.client.host if request.client else "unknown" + client_ip = get_client_ip(request) results = [] for file in files: @@ -284,6 +286,7 @@ async def handle_upload( size = os.path.getsize(temp_path) try: + guard_ctx = await inspect_upload(request, file.filename, temp_path, size, folder, "file") _upload_limiter.check_and_record(client_ip, size) except HTTPException: os.remove(temp_path) @@ -295,6 +298,8 @@ async def handle_upload( if os.path.exists(temp_path): os.remove(temp_path) + await record_upload(guard_ctx, hf_path) + results.append({ "filename": file.filename, "hf_path": hf_path, @@ -365,7 +370,7 @@ async def gh_sync( ): claims = verify_actions_token(token) owner, repo = claims["owner"], claims["repo"] - client_ip = request.client.host if request.client else "unknown" + client_ip = get_client_ip(request) contents = await archive.read() _gh_sync_limiter.check_and_record( @@ -504,7 +509,7 @@ async def handle_upload_from_url( body: UploadUrlRequest, ): folder = _validate_folder(body.folder) - client_ip = request.client.host if request.client else "unknown" + client_ip = get_client_ip(request) url = body.url.strip() _assert_public_url(url) @@ -571,6 +576,14 @@ async def handle_upload_from_url( raise try: + guard_ctx = await inspect_upload( + request, + filename, + temp_path, + downloaded, + folder, + "url", + ) _upload_limiter.check_and_record( client_ip, downloaded, @@ -589,6 +602,8 @@ async def handle_upload_from_url( if os.path.exists(temp_path): os.remove(temp_path) + await record_upload(guard_ctx, hf_path) + return { "files": [{ "filename": filename, diff --git a/app/upload_guard.py b/app/upload_guard.py @@ -0,0 +1,139 @@ +"""Upload safety checks. + +Every upload endpoint calls inspect_upload() before the file is stored and +record_upload() after it succeeds. Keeping the logic in this module means the +rules can grow without touching the upload handlers in main.py. +""" +from __future__ import annotations + +import ipaddress +import os +from dataclasses import dataclass + +from fastapi import APIRouter, HTTPException, Request + +router = APIRouter() + +# Video containers that are refused outright, by extension. +BLOCKED_VIDEO_EXTENSIONS = { + ".mp4", ".m4v", ".mov", ".webm", ".mkv", ".avi", ".wmv", + ".flv", ".mpg", ".mpeg", ".3gp", ".3g2", ".ogv", +} + +# ISO base media files ("ftyp" box) are used for video AND for images/audio. +# A file is only waved through when its major brand is one of these. +_NON_VIDEO_FTYP_BRANDS = { + b"avif", b"avis", b"heic", b"heix", b"heim", b"heis", + b"mif1", b"msf1", b"M4A ", b"M4B ", b"M4P ", b"crx ", + b"jp2 ", b"jpx ", b"jpm ", +} + +TRUST_CF_CONNECTING_IP = os.getenv("TRUST_CF_CONNECTING_IP", "1") == "1" +TRUSTED_PROXY_HOPS = max(1, int(os.getenv("TRUSTED_PROXY_HOPS", "1"))) + + +def _valid_ip(value: str | None) -> str | None: + if not value: + return None + try: + return str(ipaddress.ip_address(value.strip())) + except ValueError: + return None + + +def get_client_ip(request: Request) -> str: + """Best-effort real client IP behind Cloudflare / Render's proxy. + + Order: CF-Connecting-IP, then X-Forwarded-For (counted from the right by + TRUSTED_PROXY_HOPS so a client cannot simply prepend a fake address), then + the socket peer. + """ + if TRUST_CF_CONNECTING_IP: + ip = _valid_ip(request.headers.get("cf-connecting-ip")) + if ip: + return ip + + xff = request.headers.get("x-forwarded-for") + if xff: + parts = [p.strip() for p in xff.split(",") if p.strip()] + if parts: + idx = len(parts) - TRUSTED_PROXY_HOPS + ip = _valid_ip(parts[idx] if idx >= 0 else parts[0]) + if ip: + return ip + + return _valid_ip(request.client.host if request.client else None) or "unknown" + + +def _sniff_video(head: bytes) -> bool: + """True when the first bytes of a file look like a video container.""" + if len(head) >= 12 and head[4:8] == b"ftyp": + return head[8:12] not in _NON_VIDEO_FTYP_BRANDS + if head.startswith(b"\x1a\x45\xdf\xa3"): # Matroska / WebM + return True + if head[:4] == b"RIFF" and head[8:12] == b"AVI ": + return True + if head.startswith(b"\x30\x26\xb2\x75\x8e\x66\xcf\x11"): # ASF / WMV + return True + if head.startswith(b"FLV\x01"): + return True + if head[:4] in (b"\x00\x00\x01\xba", b"\x00\x00\x01\xb3"): # MPEG + return True + if head.startswith(b"OggS") and b"theora" in head[:64]: + return True + return False + + +def is_video_upload(filename: str, temp_path: str) -> bool: + ext = os.path.splitext((filename or "").lower())[1] + if ext in BLOCKED_VIDEO_EXTENSIONS: + return True + try: + with open(temp_path, "rb") as f: + head = f.read(64) + except OSError: + return False + return _sniff_video(head) + + +@dataclass +class UploadContext: + ip: str + filename: str + size: int + folder: str + method: str # "file" or "url" + user_agent: str = "" + + +async def inspect_upload( + request: Request, + filename: str, + temp_path: str, + size: int, + folder: str, + method: str = "file", +) -> UploadContext: + """Run all upload checks. Raises HTTPException when the upload is refused.""" + ctx = UploadContext( + ip=get_client_ip(request), + filename=filename or "", + size=size, + folder=folder, + method=method, + user_agent=request.headers.get("user-agent", "")[:300], + ) + + if is_video_upload(ctx.filename, temp_path): + raise HTTPException( + status_code=415, + detail="Video uploads (including .mp4) are not allowed on this CDN.", + ) + + return ctx + + +async def record_upload(ctx: UploadContext, hf_path: str) -> None: + """Called after a successful upload. Logging is added in a later commit.""" + return None +