Skip to content

Add CSRF, API-key auth, and rate-limiting hardening - #59

Open
robomello wants to merge 1 commit into
averygan:mainfrom
robomello:fix/csrf-auth-ratelimit
Open

Add CSRF, API-key auth, and rate-limiting hardening#59
robomello wants to merge 1 commit into
averygan:mainfrom
robomello:fix/csrf-auth-ratelimit

Conversation

@robomello

Copy link
Copy Markdown

Summary

Follow-up to #58 (yt-dlp argument injection / RCE fix). reclip has no login system by default, so the main residual risk is a confused-deputy / CSRF-triggered SSRF: a malicious web page a user has open in their browser could silently POST to their locally-bound reclip instance (e.g. http://localhost:8899/api/download) and trigger downloads on their behalf, or hammer the yt-dlp-invoking endpoints. This PR adds defense-in-depth for that, entirely off-by-default so the zero-config ./reclip.sh quick start is unaffected.

Changes

  • CSRF / cross-site request blocking (always on): state-changing /api/* requests are rejected with 403 unless their Origin/Referer matches the server's own Host. Requests with no Origin/Referer (curl, scripts, non-browser clients) are unaffected — protection is specifically against silent browser-driven cross-site requests.
  • Optional API key auth: set RECLIP_API_KEY and every /api/* request must send a matching X-API-Key header (constant-time comparison via hmac.compare_digest). Useful when binding beyond 127.0.0.1.
  • Rate limiting: dependency-free, in-memory, per-client-IP sliding-window limiter on the yt-dlp-invoking endpoints — /api/info and /api/playlist (30 req/60s), /api/download (10 req/60s, stricter since it spawns a longer-running subprocess and writes files). Tunable via RECLIP_RATE_LIMIT / RECLIP_RATE_WINDOW. Returns 429 with Retry-After when exceeded.
  • New README Security section documenting all of the above and the env vars, plus an example of running with an API key.

Design notes

  • Kept it dependency-free (no Flask-Limiter, no session/cookie machinery) to preserve the project's "2 dependencies" footprint and keep the change small and auditable.
  • Rate limiter is intentionally simple (single-process, in-memory, not persisted across restarts) — appropriate for reclip's self-hosted, single-process deployment model. Documented as such in the code.
  • RECLIP_TRUST_PROXY guards against IP spoofing via X-Forwarded-For unless explicitly opted into.

Testing

Manually smoke-tested locally:

  • Cross-origin POST /api/download (mismatched Origin) → 403 {"error":"Cross-site request blocked"}
  • Same-origin POST /api/download → passes CSRF check, proceeds to normal validation
  • RECLIP_API_KEY set, no/incorrect X-API-Key401; correct key → passes auth
  • 12 rapid POST /api/download requests against the default 10/60s limit → first 10 succeed past rate limiting, 11th/12th → 429
  • python3 -m py_compile app.py clean

Backward compatibility

No breaking changes. All new behavior is either always-on-but-permissive (CSRF check only blocks genuinely cross-site browser requests) or opt-in via env vars. Existing bundled web UI (same-origin fetch calls) works unmodified.

Follow-up to the yt-dlp argument injection fix. reclip has no login
system, so a malicious web page could silently POST to a user's
locally-bound instance to trigger downloads/SSRF-style requests on
their behalf. This adds defense-in-depth, all off-by-default so the
zero-config quick start keeps working:

- CSRF: reject state-changing /api/* requests whose Origin/Referer
  doesn't match the server's own host (browsers always send Origin on
  cross-origin POST/fetch). Non-browser clients with no Origin/Referer
  are unaffected.
- Optional API key: set RECLIP_API_KEY to require a matching
  X-API-Key header on all /api/* routes (constant-time compare).
- Rate limiting: dependency-free, in-memory per-IP sliding-window
  limiter on the yt-dlp-invoking endpoints (/api/info, /api/playlist,
  /api/download), tunable via RECLIP_RATE_LIMIT / RECLIP_RATE_WINDOW.

Documents all of this in a new README "Security" section.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant