Skip to content

refactor: move signup limit ownership to Bouncer - #7267

Merged
iscekic merged 4 commits into
feat/bouncer-signup-admissionfrom
feat/bouncer-signup-cutover
Oct 8, 2026
Merged

iscekic merged 4 commits into
feat/bouncer-signup-admissionfrom
feat/bouncer-signup-cutover

Conversation

@iscekic

@iscekic iscekic commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Stack

Depends on #7265. The base branch is feat/bouncer-signup-admission.
Merge and deploy #7265 fully before this ownership cutover.
Bouncer #1 adds the independent JA4 quota and is merged.

Change

  • Remove the Cloud 100/24h and 150/30d signup database limiter and its obsolete tests.
  • Keep Bouncer IP/JA4 admission before Stripe and user insertion.
  • Keep signup IP evidence, authentication, identity uniqueness, and deletion fences.
  • Merge the JA4 update from feat: await Bouncer signup admission before account creation #7265 without rebasing.
  • Keep db import-signup-history through the existing script runner.
  • Import completed accounts through a fixed time bound with 100-row keyset pages.
  • Require complete acknowledgements and retain explicit failure handling.
  • Use the same normalized-email identity as live signup. Full retries and catch-up imports remain idempotent.
  • Update ENVIRONMENT.md with independent enforcement, IP-only history, cutover, failure behavior, and rollback steps.

Limits and history

  • IP defaults: 3 admissions per rolling 30 days and 3 per 24 hours.
  • JA4 default: 10 admissions per rolling 720 hours across IPs.
  • Limits and windows change in the Bouncer admin panel without deployment.
  • enforcement.signup governs IPs. enforcement.signupJa4 governs JA4. The global switch gates both.
  • An exempt IP bypasses both signup counters.
  • IP runs first. A rejected IP does not touch JA4.
  • An IP admission remains counted after a JA4 or later creation failure. No refund protocol is added.
  • Historical import is IP-only. Never reconstruct signup JA4 from later usage or payment fingerprints.
  • JA4 coverage starts with live signup reports. Shared fingerprints require separate shadow review before enforcement.

Verification

  • Fresh Cloud VM selection: 33 user tests passed; 144 unrelated tests were skipped.
  • Actual Cloud signup reached the Worker and PostgreSQL with IP/JA4 rejection, exemptions, malformed JA4, shadow behavior, and sign-in exclusion.
  • Same-IP creation succeeded with 100 existing same-IP accounts after removing the Cloud limiter.
  • The actual history importer acknowledged 110 accounts in two pages. Repeating the import preserved the exact three-account history quota.
  • Imported IP history rejected new creation before insertion and did not create a JA4 history counter.
  • Cloud web typecheck and changed-file lint/format checks passed.
  • Earlier VM verification covers tied timestamps, idempotent catch-up, saturation, invalid acknowledgements, and importer failure exits.

Stripe calls use the existing development skip switch. No real Stripe operation or production history import ran.

Required rollout

  1. Deploy feat: await Bouncer signup admission before account creation #7265 fully while its old Cloud limiter remains active.
  2. Run pnpm --filter web script:run db import-signup-history from this branch with the production script environment.
  3. Complete the import and catch-up. The optional start argument accepts the previous run's through timestamp.
  4. Only Igor enables IP signup enforcement and the global switch in Bouncer.
  5. Review JA4 shadow data separately. Enable JA4 enforcement independently only after that review.
  6. Verify live allowed and rejected signup cases, then merge and deploy this PR.

After this cutover, Bouncer failure deliberately fails open. Revert this PR to restore the old Cloud limiter.

@kilo-code-bot

kilo-code-bot Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (5 files)
  • ENVIRONMENT.md
  • apps/web/src/lib/user/index.ts
  • apps/web/src/lib/user/index.test.ts
  • packages/web-shared/src/lib/bouncer/client.ts
  • packages/web-shared/src/lib/bouncer/client.test.ts
Previous Review Summary (commit 8680509)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 8680509)

Status: No Issues Found | Recommendation: Merge

The cutover removes the Cloud per-IP signup limiter, keeps Bouncer admission and IP evidence intact, and adds a careful, byte-bounded keyset history importer; no correctness, security, or runtime defects were found in the changed lines.

Files Reviewed (4 files)
  • ENVIRONMENT.md
  • apps/web/src/lib/user/index.test.ts
  • apps/web/src/lib/user/index.ts
  • apps/web/src/scripts/db/import-signup-history.ts
Verification Notes
  • Importer operation identity (signupOperationId(normalizeEmail(...))) matches the live pre-insert admission path in apps/web/src/lib/user/index.ts:591, and normalizeEmail is idempotent, so repeated/catch-up imports dedupe consistently.
  • Keyset pagination uses the full-precision created_at string (mode: 'string') plus the id tie-breaker, so tied timestamps across page boundaries do not skip or reorder rows; the per-page strict acknowledgement (imported === entries.length) is enforced.
  • IP handling mirrors live admission via bareIpLiteral; missing/empty IPs are excluded, invalid nonempty IPs abort — matching the documented contract.
  • URL target validates HTTPS (HTTP only on loopback), rejects embedded credentials, and uses redirect: 'error' with a 30s AbortSignal.timeout, so response-body consumption is also bounded.
  • The script runner (apps/web/src/scripts/index.ts) closes DB connections and exits 1 on thrown errors; server-only and @kilocode/web-shared resolve via apps/web/tsconfig.scripts.json. Removed limiter symbols (count, gte, checkSignupIpRateLimit) have no remaining references, and SIGNUP-RATE-LIMITED is still produced by the Bouncer admission path.
  • No memory-leak pattern: the importer adds no persistent listeners and retains only counters across pages.

Reviewed by deepseek-v4.1-flash · Input: 54.2K · Output: 12.4K · Cached: 791.4K

Review guidance: REVIEW.md from base branch feat/bouncer-signup-admission

@iscekic
iscekic added this pull request to stack #7271 October 7, 2026 22:07
@iscekic
iscekic merged commit c87edd3 into main Oct 8, 2026
28 checks passed
@iscekic
iscekic deleted the feat/bouncer-signup-cutover branch October 8, 2026 10:05
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.

2 participants