sec: FIX: rate-limit fix to work with the actual http proxy header

This commit is contained in:
Caffeine Fueled 2026-05-25 01:57:01 +02:00
parent 25b7b21412
commit e9d195a2ec
Signed by: cf7
GPG key ID: CA295D643074C68C
2 changed files with 17 additions and 2 deletions

View file

@ -99,6 +99,7 @@ podman run -d --name aukpad-app \
-e MAX_TEXT_SIZE=5 \ -e MAX_TEXT_SIZE=5 \
-e MAX_CONNECTIONS_PER_IP=20 \ -e MAX_CONNECTIONS_PER_IP=20 \
-e RETENTION_HOURS=72 \ -e RETENTION_HOURS=72 \
-e TRUST_PROXY=true \
git.uphillsecurity.com/cf7/aukpad:latest git.uphillsecurity.com/cf7/aukpad:latest
``` ```
@ -118,6 +119,7 @@ The following environment variables can be configured:
| `MAX_CONNECTIONS_PER_IP` | `10` | Maximum concurrent connections per IP address | | `MAX_CONNECTIONS_PER_IP` | `10` | Maximum concurrent connections per IP address |
| `RETENTION_HOURS` | `48` | How long to retain pads in hours after last access | | `RETENTION_HOURS` | `48` | How long to retain pads in hours after last access |
| `MAX_ROOMS` | `10000` | Maximum number of pads kept in memory; new pads are refused (WS close 1008) when full until cleanup reclaims space | | `MAX_ROOMS` | `10000` | Maximum number of pads kept in memory; new pads are refused (WS close 1008) when full until cleanup reclaims space |
| `TRUST_PROXY` | `false` | If `true`, read the client IP from `X-Forwarded-For` (first entry) or `X-Real-IP` for per-IP rate/connection limits. Only enable when aukpad sits behind a reverse proxy that strips/sets these headers — otherwise they can be spoofed |
| `DESCRIPTION` | `powered by aukpad.com` | Instance description shown on info page | | `DESCRIPTION` | `powered by aukpad.com` | Instance description shown on info page |
--- ---

17
app.py
View file

@ -15,6 +15,7 @@ MAX_TEXT_SIZE = int(os.getenv("MAX_TEXT_SIZE", "5")) * 1024 * 1024 # 5MB defaul
MAX_CONNECTIONS_PER_IP = int(os.getenv("MAX_CONNECTIONS_PER_IP", "10")) MAX_CONNECTIONS_PER_IP = int(os.getenv("MAX_CONNECTIONS_PER_IP", "10"))
RETENTION_HOURS = int(os.getenv("RETENTION_HOURS", "48")) # Default 48 hours RETENTION_HOURS = int(os.getenv("RETENTION_HOURS", "48")) # Default 48 hours
MAX_ROOMS = int(os.getenv("MAX_ROOMS", "10000")) MAX_ROOMS = int(os.getenv("MAX_ROOMS", "10000"))
TRUST_PROXY = os.getenv("TRUST_PROXY", "false").lower() == "true"
DESCRIPTION = os.getenv("DESCRIPTION", "powered by aukpad.com") DESCRIPTION = os.getenv("DESCRIPTION", "powered by aukpad.com")
DOC_ID_RE = re.compile(r"^[a-zA-Z0-9_-]{1,64}$") DOC_ID_RE = re.compile(r"^[a-zA-Z0-9_-]{1,64}$")
@ -22,6 +23,18 @@ DOC_ID_RE = re.compile(r"^[a-zA-Z0-9_-]{1,64}$")
def is_valid_doc_id(doc_id: str) -> bool: def is_valid_doc_id(doc_id: str) -> bool:
return bool(DOC_ID_RE.match(doc_id)) return bool(DOC_ID_RE.match(doc_id))
def get_client_ip(conn) -> str:
# Only honor proxy headers when explicitly opted in — otherwise attackers
# can spoof them to bypass per-IP limits.
if TRUST_PROXY:
xff = conn.headers.get("x-forwarded-for")
if xff:
return xff.split(",")[0].strip()
xri = conn.headers.get("x-real-ip")
if xri:
return xri.strip()
return conn.client.host if conn.client else "unknown"
# Valkey/Redis client (initialized later if enabled) # Valkey/Redis client (initialized later if enabled)
redis_client = None redis_client = None
@ -601,7 +614,7 @@ def root():
@app.post("/", include_in_schema=False) @app.post("/", include_in_schema=False)
async def create_pad_with_content(request: Request): async def create_pad_with_content(request: Request):
# Get client IP # Get client IP
client_ip = request.client.host if request.client else "unknown" client_ip = get_client_ip(request)
# Check rate limit # Check rate limit
if not check_rate_limit(client_ip): if not check_rate_limit(client_ip):
@ -703,7 +716,7 @@ async def ws(doc_id: str, ws: WebSocket):
return return
# Get client IP for connection limiting # Get client IP for connection limiting
client_ip = ws.client.host if ws.client else "unknown" client_ip = get_client_ip(ws)
# Check connection limit per IP # Check connection limit per IP
if connections_per_ip[client_ip] >= MAX_CONNECTIONS_PER_IP: if connections_per_ip[client_ip] >= MAX_CONNECTIONS_PER_IP: