Repository navigation
fix: limit anonymous auth and waitlist by email and source IP (ORB-108) - #749
Conversation
There was a problem hiding this comment.
Important
The new auth IP quota groups unrelated web users behind the existing BFF, allowing one caller to exhaust other users' email-login allowance on the same egress.
Reviewed changes: Reviewed the composite anonymous rate limits, forwarded-header trust configuration, existing authentication paths, and web consumer routing.
- Composite enforcement: Anonymous email requests consume both email and source-IP buckets, with separate auth and waitlist IP ceilings and the existing rejection response.
- Proxy trust: Configures the Render private network as trusted while retaining one-hop forwarded-header processing.
- Preserved paths: Keeps authenticated user partitions, verified refresh-token partitions, and support's store-failure exemption.
- Regression coverage: Covers email rotation, shared sources, rejection windows, store failures, waitlist email sends, and bot-filter ordering; the focused test run passed all 50 tests.
gpt-6.1-sol | 𝕏
There was a problem hiding this comment.
No new blocking issues found.
Reviewed changes: Reviewed the two follow-up commits since ed3575c1, covering authenticated BFF source forwarding and shared-secret provisioning, with the full PR diff for context.
- Isolated BFF clients: Accepted canonical end-client IPs only with a matching forwarding secret, retained connection-IP enforcement for untrusted requests, and bounded invalid authenticated forwarding through the unknown bucket.
- Provisioned shared secrets: Generated and persisted separate production and staging secrets, wiring each value into its API and web environment groups.
- Verified regressions: Passed all 3,390 Infrastructure tests and all four forwarding-secret configuration tests, including independent BFF clients, email rotation, forged headers and IP normalization.
The resolved finding remains settled under the recorded web-before-API release dependency on thomasluizon/orbit-tickets#1527; this review does not claim that the web counterpart is already deployed. Terraform CLI validation was unavailable because terraform is not installed.
gpt-6.1-sol | 𝕏
|
* fix: pin analyzer test Pkcs dependency to patched version (#728) (cherry picked from commit 61b0090) * fix: shard pull request domain mutation testing (#729) (cherry picked from commit bcfd81e) * Promote the API redesign with production compatibility checks (#723) * Bring the API redesign tree onto main for #1413 * Preserve calendar connection detection for shipped clients * Prove API and migrated schema compatibility with shipped clients * Trim whitespace in the archived main schema proof * Require the production health response to report the released commit * Bind initial approval previews to stored pending operations * fix: return the bulk skip refresh outcome and split the undo skip query SonarCloud failed the quality gate on two reliability findings in code this pull request brings to main: the bulk skip conflict check read a flag only a lambda wrote (S2583), and the undo skip check loaded Tags and Goals in one query (S8733). The transaction now returns whether every habit refreshed, and the undo skip check splits its query. A new case covers the concurrent change conflict; it failed with the refresh outcome forced to true and passes with the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 5526c9b) * Add per-account feature flag targeting and admin controls (#724) * feat: resolve feature flags per account * feat: administer account feature flags through MCP and REST * test: exclude feature flag targets from rate limit fixture * fix: reconcile account flag tests and migration metadata with redesign (cherry picked from commit 1b886bd) * Add header API versioning for v1 and v2 (#733) * Add header API versioning with v1 compatibility * Verify versioned OpenAPI and browser header compatibility * Preserve skip undo path contract with optional version header (cherry picked from commit ac8b34a) * fix: expose the config response in OpenAPI (#734) (cherry picked from commit a9d8dd5) * Route production redesign accounts through Cloudflare (#735) * Add cookie-based routing for the production redesign web service * Document web routing rollout, permissions, and rollback checks * Fix web routing coverage and disable infra install scripts * chore: regenerate the v2 OpenAPI document on main orbit-api#733 added openapi_v2.json and orbit-api#734 typed the /api/config response in parallel, so main carries a v2 document without the typed config schema and the Build check's stale-document step fails on every merge with main. dotnet build src/Orbit.Api regenerates it; openapi.json is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 9c64770) * Fix habit edit gating, end dates, and reminder updates (#736) * fix: validate habit updates with the resulting time range * fix: preserve end dates when creating root habits * fix: gate slip alert activation and preserve unchanged reminders * fix: compare effective reminder rules across stored formats (cherry picked from commit 7472203) * Reduce mutation and guard CI jobs (#737) * Select mutation shards from changed projects * Consolidate guards and filter artifact generation * Skip project documentation in mutation selection * Validate mutation base refs and restrict Git PATH * Read mutation shard changes from standard input (cherry picked from commit b802f9e) * test: strengthen habit and goal ownership coverage (#739) (cherry picked from commit 0e32628) * test: verify checkout session user metadata (#740) (cherry picked from commit da2f53a) * fix: protect OAuth code delivery with Turnstile (#741) (cherry picked from commit 1149606) * fix: enforce chat interaction scope before loading context (#743) (cherry picked from commit 98faf4e) * fix: erase accountability pairs during account deletion and reset (#745) * fix: erase accountability pairs during account deletion and reset * test: preserve migration compatibility checks after accountability cleanup (cherry picked from commit a9e5fb9) * Fix goal sync dates and deadline reminder counts (#747) * fix: compare streak sync dates in the user timezone * fix: show actual days remaining in goal deadline reminders * test: configure timezone for streak scheduler fixtures (cherry picked from commit 0e63842) * Manage staging landing routing in Terraform (#742) (cherry picked from commit abdd738) * Restrict Web Push endpoints to supported push services (#1503) (#751) Web Push subscriptions accept only HTTPS endpoints on the FCM, Mozilla, Apple and WNS push hosts, and the push HTTP client follows no redirects. Replaces orbit-api#748 as one commit so no commit carries the high-entropy sample fixture. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit d9f847a) * Erase account uploads before permanent deletion (#1507) (#750) * fix: erase uploaded objects before completing account deletion * fix: refuse upload reads after permanent account deletion * fix: grant uploads bucket deletion permissions and verify provider wiring * test: align storage startup checks with erasure requirements * fix: retire deleted Supabase upload storage * test: configure upload erasure providers in deletion fixtures Share the existing object-store fake across account deletion and scheduler fixtures after merging main. Preserve accountability assertions and production provider requirements for thomasluizon/orbit-tickets#1507. (cherry picked from commit fa33a94) * fix: limit anonymous auth and waitlist by email and source IP (ORB-108) (#749) * fix: enforce source IP ceilings alongside anonymous email limits * test: cover both rate limit dimensions before bot verification * fix: isolate BFF email rate limits by verified client IP * fix: generate and persist BFF forwarding secrets (cherry picked from commit de0f42f) * Limit access tokens to fifteen minutes (#738) * fix(auth): limit access tokens to fifteen minutes * fix(auth): align managed JWT lifetime and prove logout rejection (cherry picked from commit ade7016) * fix(auth): equalize account provisioning work (ORB-96) (#744) * test(auth): expose registration dependent sign-in work * fix(auth): perform uniform registration work before sign-in responses * test(auth): cover expired codes and tracked upsert conflicts * fix(auth): isolate signup retries and restore provisioning guards * fix(auth): recover signup work after dispatch failures * fix(auth): roll back failed signup referrals before retry (cherry picked from commit 1bd0d26) * fix: expose redesign controllers in both API versions Apply the carried version declarations to the Notion and proactive controllers and regenerate the v2 contract. Refs thomasluizon/orbit-tickets#746. * fix: honor account flags in proactive engine --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>




Anonymous auth and waitlist requests previously acquired only an attacker-chosen email bucket. Rotating the email could therefore keep sending mail or probing codes from one source without exhausting a source quota.
DistributedRateLimitFilternow acquires both the original email bucket and a separate IP bucket for anonymous email requests. It evaluates both dimensions and rejects when either refuses a permit. The rejected dimension supplies the existing retry header and structured rejection log. Authenticated user partitions, session-verified refresh partitions, the support fail-open exemption, and the 429 response schema are preserved.The implementation lands in
src/Orbit.Api/RateLimiting/DistributedRateLimitAttribute.csandsrc/Orbit.Infrastructure/Services/DistributedRateLimitService.cs. Explicitauth-ipandwaitlist-ippolicies keep source ceilings independent of the existing account ceilings.src/Orbit.Api/appsettings.jsonpinsForwardedHeaders:KnownNetworksto10.0.0.0/8for both Render environments, andBuildForwardedHeadersOptionscarries the evidence-linked reason while retainingForwardLimit = 1.Regression coverage extends the partition, filter and waitlist controller suites. The waitlist test invokes the real controller and command handler with a mocked email port, so only 15 of 20 distinct-address attempts send confirmation mail. Auth send-code and OAuth verify-code stop after 30 of 40 attempts. Other cases cover original account limits across sources, shared NAT traffic, nine ordinary requests, unknown IPs, authenticated callers, refresh tokens, both rejection windows, and store failure. A structural guard enumerates every email-partitioned policy and requires its matching IP policy. Additional middleware tests use committed configuration and the actual forwarded-header middleware; the existing bot-protection ordering test now covers rejection by either dimension.
Render evidence
The work order supplies this staging log observation from the same Render platform, region and DNS-only routing as production, observed at 2026-10-18T09:37:47Z:
Sixteen forged refresh requests from public client address
177.189.50.154reached the refresh ceiling under the private peer address. This established that the proxy peer was10.24.103.103and that forwarded client addresses were previously discarded. The final work-order decision authorizes the committed network and enables the IP dimension immediately. Production was not probed.The actual middleware invocation confirms one trusted hop: peer
10.24.103.103plusX-Forwarded-For: 203.0.113.10resolves to203.0.113.10; the two-address header203.0.113.10, 10.0.0.1resolves to10.0.0.1. Both headers are ignored from untrusted peer198.51.100.7. These tests exercise the framework interface directly without an invented external response fixture.Assumptions
auth-ip30 per 60 seconds andwaitlist-ip15 per 600 seconds, rather than applying the tighter per-email ceilings to all callers behind a shared NAT. Account ceilings remain auth 10 per 60 seconds and waitlist 5 per 600 seconds.RequireBotProtectionFilterTests.csand addExtensions/ForwardedHeadersTests.csbecause the composite filter changes the ordering fixture and the trust configuration needs real middleware coverage, rather than limiting coverage to the originally listed files.Test evidence
The unchanged tests passed with the vulnerability present, including
TryResolveEmailPartitionKey_Auth_DifferentEmailsMapToDifferentKeys:Output excerpt:
Com falha: 0, Aprovado: 31, Ignorado: 0, Total: 31. Exit 0.Before changing production code, the extended suites exercised the missing IP ceiling:
AuthRoutes_RotatingEmails_ExhaustIpCeilingfailed for both auth send-code and OAuth verify-code at request 31.Join_RotatingEmails_BoundsConfirmationSendsByIpCeilingfailed at request 16. All three reportedExpected context.Result to be Microsoft.AspNetCore.Mvc.ObjectResult, but found <null>.Output excerpt:Com falha: 3, Aprovado: 0, Ignorado: 0, Total: 3. Exit 1.With the implementation in place, the same rotation cases pass within the final focused run. The former different-email test was deliberately rewritten as
Auth_DifferentEmailsKeepSeparateEmailKeysAndShareIpKey, retaining distinct email keys and asserting their shared IP bucket.Output excerpt:
Com falha: 0, Aprovado: 50, Ignorado: 0, Total: 50. Exit 0.Output excerpt:
13 Aviso(s)and0 Erro(s). Exit 0. OpenAPI generation produced no contract diff.Output excerpts: Domain
Com falha: 0, Aprovado: 671, Ignorado: 0, Total: 671; ApplicationCom falha: 0, Aprovado: 5485, Ignorado: 0, Total: 5485. Both exit 0.The first
dotnet test tests/Orbit.Infrastructure.Tests --no-buildrun found one old mock that returned a decision only for the email dimension:RateLimitRejectsBeforeBotVerification_WithRetryHeader. It reported 3373 passes and one failure. The follow-up commit configures both decisions and tests both rejection paths, retaining the assertion that bot verification is never called.The final full solution run includes the corrected Infrastructure suite and the analyzer suite:
dotnet testOutput excerpts:
Exit 0. All four projects passed: 9563 tests, zero failures and zero skipped. Both commits passed the installed pre-commit hooks, including formatting, dash, timeless text and suppression checks.
Closes thomasluizon/orbit-tickets#114. Implements ORB-108.