The app sits behind nginx, which caches 200s for 30s — so repeated lookups of the same user are cheap. What bypasses the cache is enumeration of distinct usernames: each is a unique cache key -> miss -> a fresh finger call to the mammut daemon, all attributed to admin's single IP (so the daemon cannot ban the real source). The app only ever receives cache misses, so a per-IP limit here throttles exactly that uncached path without touching the cached hot path. - Flask-Limiter keyed per client IP: 30/min on the finger lookup endpoints, 10/min on /api/upload (auth brute-force), 120/min global default. Index and the container healthcheck are exempt. All limits env-tunable (RATELIMIT_*). - ProxyFix(x_for=1): trust nginx's X-Forwarded-For so the real client IP is used for keying and logging. Without it the app only saw the Docker bridge gateway (172.20.0.1) and every client shared one bucket. - 429 handler (JSON for /api, HTML 429.html otherwise) and WARNING logging of failed/invalid lookups and limit hits, so enumeration is observable.
10 lines
152 B
Plaintext
10 lines
152 B
Plaintext
Flask==2.3.3
|
|
Werkzeug==2.3.7
|
|
Jinja2==3.1.2
|
|
MarkupSafe==2.1.3
|
|
itsdangerous==2.1.2
|
|
click==8.1.7
|
|
blinker==1.6.3
|
|
Flask-HTTPAuth==4.8.0
|
|
Flask-Limiter==3.5.1
|