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.
Unify the interactive /finger page on the same results-first layout
introduced for direct /finger/<user> links: drop the two-column view
(About Finger panel, success banner, timestamp, back-to-home button)
and the now-unused direct flag.
Direct links now render a results-first single-column view: drop the
About Finger panel, success banner, timestamp and back-to-home button,
and replace the lookup card with a small inline form next to the heading.
The interactive /finger page keeps its original two-column layout.
- Add /api/finger and /api/finger/<username> JSON endpoints
- Remove hardcoded default credentials from config.py; require BASIC_AUTH_USERS env var
- Add cli/finger.py: query and plan-upload CLI using the JSON API
- Add mcp/server.py: FastMCP server exposing finger_user and upload_plan tools
- All credentials and base URL are read from environment variables