perf: Parallelize payment API checks in GetImportConfig usecase#1173
perf: Parallelize payment API checks in GetImportConfig usecase#1173chavda-bhavik merged 3 commits intonextfrom
Conversation
Replace sequential for-of loop with Promise.allSettled to fire all 18 paymentAPIService.checkEvent calls concurrently. Also run the user email lookup and template fetch in parallel via Promise.all. This reduces widget open latency from ~18x(API_latency) to ~1x(API_latency). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
View your CI Pipeline Execution ↗ for commit 5a73028
☁️ Nx Cloud last updated this comment at |
| const response = await axios.get(url, { | ||
| timeout: 15000, | ||
| maxRedirects: 3, | ||
| headers, | ||
| responseType: 'stream', | ||
| }); |
Check failure
Code scanning / CodeQL
Server-side request forgery Critical
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 2 months ago
To fix the problem, the URL used in the outgoing axios requests must be constrained so that untrusted clients cannot force connections to internal or otherwise sensitive endpoints. In general, this is achieved by validating the destination host/IP against a strict allow-list and/or doing robust SSRF-safe checks on the resolved IP addresses (not just the string hostname), including IPv4 and IPv6, and rejecting loopback, link-local, and private ranges. Where arbitrary hosts must be allowed, the validation must at least resolve DNS and exclude internal networks.
The best way to fix this code with minimal behavioral change is to strengthen isUrlSafe and ensure getMimeType uses it consistently. We can: (1) add a small helper that resolves the hostname (via dns.promises.lookup) to an IP, (2) add functions that detect private/loopback/link-local ranges using ipaddr.js, a well-known library designed for this purpose, and (3) update isUrlSafe to use these helpers so that even if an attacker supplies a hostname that resolves to an internal IP (e.g., via DNS rebinding or internal DNS names), the request is rejected. We will continue to allow http and https schemes and public IPs/hostnames, preserving intended functionality as much as possible.
Concretely, in apps/api/src/app/shared/helpers/common.helper.ts we will:
- Add an import for
ipaddr.jsand Node’sdnspromises. - Introduce an internal async helper
resolveAndCheckHost(parsedUrl: URL): Promise<boolean>that DNS-resolves the host, parses the IP withipaddr.js, and returnsfalseif it is loopback, private, link-local, or otherwise non-globally-routable. - Change
isUrlSafefrom synchronous toasyncand implement it using this helper: keep the existing scheme checks and localhost literal checks, and add IP-classification usingipaddr.js. BecauseisUrlSafebecomes async, we will updategetMimeTypetoawait isUrlSafe(url)before proceeding. - Keep the rest of
getMimeTypeunchanged.
No changes are required in the controller or use case snippets for this specific SSRF fix.
| @@ -74,7 +74,8 @@ | ||
| "uuid": "^9.0.0", | ||
| "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.1/xlsx-0.20.1.tgz", | ||
| "xlsx-populate": "^1.21.0", | ||
| "xml2js": "^0.6.2" | ||
| "xml2js": "^0.6.2", | ||
| "ipaddr.js": "^2.3.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@nestjs/cli": "^10.4.5", |
| Package | Version | Security advisories |
| ipaddr.js (npm) | 2.3.0 | None |
- base-repository: Remove JSON round-trip (JSON.parse/stringify) in mapEntity/mapEntities; lean() already returns plain objects so the extra serialization was pure CPU waste on every query result - base-repository: Run paginate() data fetch and count in parallel via Promise.all instead of sequentially (saves one full round-trip per page) - do-review, start-process: Replace forEach(async) fire-and-forget with Promise.allSettled so limit-exceeded emails are actually awaited and sent concurrently instead of being silently dropped - upload.repository: Deduplicate getUserEmailFromUploadId / getUserIdFromUploadId into a single private helper to avoid executing identical DB queries twice when both are needed - auto-import-jobs-schedular: Process all scheduled jobs in parallel with Promise.allSettled instead of a sequential for-of loop - invite.usecase: Batch-check existing invitations with a single $in query; create all invitations in parallel; send emails + payment events in parallel — reduces N*4 sequential DB calls to O(1) queries - bubble-io.service: Convert bubbleIoDefaultColumns from Array to Set so column-key exclusion checks are O(1) instead of O(n) - project-invitation.schema: Add indexes on invitationToEmail, invitedBy, and _projectId (previously unindexed, scanned on every invite check) - user-job.schema: Add indexes on _templateId, nextRun, and status to speed up scheduler lookups and $in joins Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Performance impact: Widget open latency reduced from ~18× API_latency to ~1× API_latency (the slowest single call)
Test plan