Security: a made-up x-client-ip can no longer get round the sign-in and sign-up limits #76

Merged
anas merged 1 commits from fix/trusted-client-ip into main 2026-10-09 15:58:42 +00:00
Owner

Part of #44, and needed before #22 (going online).

Problem

  • Sign-in and sign-up are rate-limited per visitor address, against password guessing and mass sign-ups.
  • The site passes the visitor's address to the API in x-client-ip, and the API believed that header from anyone (auth.ts).
  • On a server where the API can be reached, an attacker could send a different fake address with each request and never hit the limits.

Fix

  • The API believes x-client-ip only from the site.
  • The site proves itself with DIVAN_SITE_KEY in x-site-key, compared timing-safe. Set the same key for the API and the site in production.
  • With no key set (local development), only requests from the same machine count as the site.
  • Everyone else is limited by their real address.
  • The site sends the key when it is set (web/src/lib/auth.ts).
  • README: setting the key; keeping the API off the internet; Caddy as the only way in. Caddy replaces any X-Forwarded-For a visitor sends with the real address, and Astro's clientAddress reads the first value of that header.

Tested: npm test passes. A new test covers:

  • local development without a key: this machine is trusted, a stranger is not;
  • with a key: the site is trusted; a wrong key, or no key even from this machine, is not.

Made in a separate worktree from the latest main, so the desktop session working in ~/divan was not touched.

🤖 Generated with Claude Code

Part of #44, and needed before #22 (going online). **Problem** - Sign-in and sign-up are rate-limited per visitor address, against password guessing and mass sign-ups. - The site passes the visitor's address to the API in `x-client-ip`, and the API believed that header from **anyone** (`auth.ts`). - On a server where the API can be reached, an attacker could send a different fake address with each request and never hit the limits. **Fix** - The API believes `x-client-ip` only from the site. - The site proves itself with `DIVAN_SITE_KEY` in `x-site-key`, compared timing-safe. Set the same key for the API and the site in production. - With no key set (local development), only requests from the same machine count as the site. - Everyone else is limited by their real address. - The site sends the key when it is set (`web/src/lib/auth.ts`). - README: setting the key; keeping the API off the internet; Caddy as the only way in. Caddy replaces any `X-Forwarded-For` a visitor sends with the real address, and Astro's `clientAddress` reads the first value of that header. **Tested:** `npm test` passes. A new test covers: - local development without a key: this machine is trusted, a stranger is not; - with a key: the site is trusted; a wrong key, or no key even from this machine, is not. Made in a separate worktree from the latest main, so the desktop session working in `~/divan` was not touched. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
anas added 1 commit 2026-10-09 15:38:58 +00:00
anas merged commit 30d66517bd into main 2026-10-09 15:58:42 +00:00
Sign in to join this conversation.
No reviewers
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: anas/divan#76
No description provided.