Skip to content

fix(#419): Security (low): 10,000 anonymous GETs to /auth/login block every sign-in for ten - #429

Merged
BeSovereign merged 1 commit into
mainfrom
fix/issue-419
Oct 20, 2026
Merged

BeSovereign merged 1 commit into
mainfrom
fix/issue-419

Conversation

@BeSovereign

Copy link
Copy Markdown
Contributor

Refs #419

Login starts are rate-limited per addressKey (30/min, a completed sign-in resets it), and live login attempts are capped at 50 per address, with the oldest evicted (migration 033 adds login_attempts.address); the global LOGIN_ATTEMPTS_MAX stays as the last resort, and the trade-off is recorded in DECISIONS/CONCEPTS. This round: eslint.config.js now ignores the generated playwright-report/, test-results/ and coverage/ output whose minified bundles failed lint; after the adversarial and test-adversary reviews, the completed-sign-in test uses its own address and the #255 test asserts no retry-after.

Driver gates (measured, not claimed)

  • Suites: typecheck: 1 passed, 0 failed | lint: 1 passed, 0 failed | unit: 1203 passed, 0 failed | e2e: 616 passed, 0 failed
  • Red/green check on declared tests: server/test/auth.test.ts, server/test/sessions.test.ts, server/test/db.test.ts
red/green output
RED without the change:
better-sqlite3@13.0.3/node_modules/better-sqlite3/lib/methods/wrappers.js:�[2m5:21�[22m�[39m
�[36m �[2m❯�[22m test/sessions.test.ts:�[2m62:23�[22m�[39m
    �[90m 60|�[39m     const own: string[] = [];
    �[90m 61|�[39m     for (let i = 0; i < LOGIN_ATTEMPTS_PER_ADDRESS; i++) { own.push(cr…
    �[90m 62|�[39m     const insert = db.prepare(`INSERT INTO login_attempts (id_hash, st…
    �[90m   |�[39m                       �[31m^�[39m
    �[90m 63|�[39m       VALUES (?,?,?,?,?,?,?)`);
    �[90m 64|�[39m     const expires = new Date(Date.now() + LOGIN_TTL_MS).toISOString();

�[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[7/7]⎯�[22m�[39m

Error: ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL

  × "pnpm recursive exec" failed in /home/agent-user/Coding/Freeshard/
  │ vacation_framework/worktrees/files/issue-419/server



GREEN with the change:
m server �[49m�[39m test/db.test.ts �[2m(�[22m�[2m7 tests�[22m�[2m)�[22m�[33m 1868�[2mms�[22m�[39m
   �[32m✓�[39m openDb �[2m(7)�[22m
     �[33m�[2m✓�[22m�[39m two processes starting at once apply a pending migration exactly once and both start�[33m 1362�[2mms�[22m�[39m
 �[32m✓�[39m �[30m�[42m server �[49m�[39m test/auth.test.ts �[2m(�[22m�[2m44 tests�[22m�[2m)�[22m�[33m 2545�[2mms�[22m�[39m
   �[32m✓�[39m OIDC login �[2m(33)�[22m
     �[33m�[2m✓�[22m�[39m a flood from one address does not stop other members from signing in (#419)�[33m 1793�[2mms�[22m�[39m

�[2m Test Files �[22m �[1m�[32m3 passed�[39m�[22m�[90m (3)�[39m
�[2m      Tests �[22m �[1m�[32m55 passed�[39m�[22m�[90m (55)�[39m
�[2m   Start at �[22m 23:03:48
�[2m   Duration �[22m 3.21s�[2m (tests 88%, import 9%, transform 3%)�[22m


🤖 Generated with Claude Code

Login starts are rate-limited per addressKey (30/min, a completed sign-in resets it), and live login attempts are capped at 50 per address, with the oldest evicted (migration 033 adds login_attempts.address); the global LOGIN_ATTEMPTS_MAX stays as the last resort, and the trade-off is recorded in DECISIONS/CONCEPTS. This round: eslint.config.js now ignores the generated playwright-report/, test-results/ and coverage/ output whose minified bundles failed lint; after the adversarial and test-adversary reviews, the completed-sign-in test uses its own address and the #255 test asserts no retry-after.

Refs #419

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@BeSovereign

Copy link
Copy Markdown
Contributor Author

Security review (claude-fable-5-1): no blocking finding. No blocking defects. Per-address limiter and ceiling are correctly scoped; forget() only after a successful code exchange keyed on the stored address; SQL parameterised; tests fail when either guard is removed. Address key trusts the one-hop proxy (Traefik appends XFF), a pre-existing invariant; a /56 holder can still fill the global table (documented); client IPs now logged at warn.

@BeSovereign
BeSovereign merged commit 2ba60d6 into main Oct 20, 2026
3 checks passed
@BeSovereign
BeSovereign deleted the fix/issue-419 branch October 20, 2026 21:52
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