sec: FIX: rate-limit fix to work with the actual http proxy header
This commit is contained in:
parent
25b7b21412
commit
e9d195a2ec
2 changed files with 17 additions and 2 deletions
|
|
@ -99,6 +99,7 @@ podman run -d --name aukpad-app \
|
|||
-e MAX_TEXT_SIZE=5 \
|
||||
-e MAX_CONNECTIONS_PER_IP=20 \
|
||||
-e RETENTION_HOURS=72 \
|
||||
-e TRUST_PROXY=true \
|
||||
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 |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|
||||
---
|
||||
|
|
|
|||
17
app.py
17
app.py
|
|
@ -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"))
|
||||
RETENTION_HOURS = int(os.getenv("RETENTION_HOURS", "48")) # Default 48 hours
|
||||
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")
|
||||
|
||||
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:
|
||||
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)
|
||||
redis_client = None
|
||||
|
||||
|
|
@ -601,7 +614,7 @@ def root():
|
|||
@app.post("/", include_in_schema=False)
|
||||
async def create_pad_with_content(request: Request):
|
||||
# Get client IP
|
||||
client_ip = request.client.host if request.client else "unknown"
|
||||
client_ip = get_client_ip(request)
|
||||
|
||||
# Check rate limit
|
||||
if not check_rate_limit(client_ip):
|
||||
|
|
@ -703,7 +716,7 @@ async def ws(doc_id: str, ws: WebSocket):
|
|||
return
|
||||
|
||||
# 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
|
||||
if connections_per_ip[client_ip] >= MAX_CONNECTIONS_PER_IP:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue