-
-
Notifications
You must be signed in to change notification settings - Fork 22.9k
Fix: CORS-related issues #5310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Fix: CORS-related issues #5310
Conversation
|
here's PR opened by vasu: #5297 |
|
Overall logic looks good. Added few minor comments. |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
WalkthroughThese changes enhance CORS security by implementing domain validation for chatflow prediction requests. The system now validates that prediction endpoint requests originate from allowed domains specified in chatbot configuration, while sensitive CORS settings are excluded from public responses. Changes
Sequence DiagramsequenceDiagram
participant Client
participant CORS Handler
participant Request Classifier
participant Domain Validator
participant Chatflow DB
Client->>CORS Handler: HTTP Request with Origin header
CORS Handler->>Request Classifier: isPredictionRequest(url)?
alt Prediction Request
Request Classifier-->>CORS Handler: true
CORS Handler->>Domain Validator: extractChatflowId(url)
Domain Validator-->>CORS Handler: chatflowId
CORS Handler->>Domain Validator: validateChatflowDomain(chatflowId, origin)
Domain Validator->>Chatflow DB: Fetch chatflow config
Chatflow DB-->>Domain Validator: allowedOrigins
Domain Validator-->>CORS Handler: allowed? (boolean)
alt Origin Allowed
CORS Handler-->>Client: ✓ Allow request
else Origin Not Allowed
Domain Validator->>Domain Validator: getUnauthorizedOriginError(chatflowId)
CORS Handler-->>Client: ✗ Deny request
end
else Non-Prediction Request
Request Classifier-->>CORS Handler: false
CORS Handler-->>Client: ✓ Allow request
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Areas requiring extra attention:
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (1)
packages/server/src/utils/domainValidation.ts (1)
52-68: Make chatflowId extraction robust and path-agnosticCurrent split logic is brittle and tied to 'prediction'. Use a regex that handles both '/prediction' and '/predictions', optional prefixes, and query strings.
-function extractChatflowId(url: string): string | null { - try { - const urlParts = url.split('/') - const predictionIndex = urlParts.indexOf('prediction') - if (predictionIndex !== -1 && urlParts.length > predictionIndex + 1) { - const chatflowId = urlParts[predictionIndex + 1] - // Remove query parameters if present - return chatflowId.split('?')[0] - } - return null - } catch (error) { - logger.error('Error extracting chatflow ID from URL:', error) - return null - } -} +function extractChatflowId(url: string): string | null { + try { + const m = url.match(/\/predictions?\/([^/?#]+)/i) + return m ? m[1] : null + } catch (error) { + logger.error('Error extracting chatflow ID from URL:', error) + return null + } +}
🧹 Nitpick comments (5)
packages/server/src/services/chatflows/index.ts (1)
377-379: Good: sensitive CORS fields removed from public configHiding allowedOrigins and allowedOriginsError is correct.
- Guard against non-object configs to avoid odd spreads:
-const parsedConfig = dbResponse.chatbotConfig ? JSON.parse(dbResponse.chatbotConfig) : {} +const raw = dbResponse.chatbotConfig ? JSON.parse(dbResponse.chatbotConfig) : {} +const parsedConfig = raw && typeof raw === 'object' ? raw : {}
- Consider documenting other sensitive keys to keep private for future additions.
packages/server/src/utils/XSS.ts (2)
28-39: Clarify and normalize CORS_ORIGINS parsingIf CORS_ORIGINS is documented as FQDNs, comparing them to full Origin values (scheme+host+port) will never match. Either document that CORS_ORIGINS must be full origins (e.g., https://example.com:3000), or normalize both sides to host[:port].
Example normalization:
-function parseAllowedOrigins(allowedOrigins: string): string[] { +function parseAllowedOrigins(allowedOrigins: string): string[] { if (!allowedOrigins) { return [] } if (allowedOrigins === '*') { return ['*'] } - return allowedOrigins - .split(',') - .map((origin) => origin.trim().toLowerCase()) - .filter((origin) => origin.length > 0) + return allowedOrigins + .split(',') + .map((v) => v.trim().toLowerCase()) + .filter((v) => v.length > 0) }Then ensure the caller lowercases the incoming origin (see previous diff).
64-81: checkRequestType: minor hardening
- Lowercase origin before passing to validateChatflowDomain.
- Add a fast path for missing chatflowId to avoid unnecessary lookups.
No separate diff needed if you apply the main origin handler change above.
packages/server/src/utils/domainValidation.ts (2)
85-101: Unused parameter and CORS messaging caveat
- Rename workspaceId to _workspaceId to silence lints until used.
- Note: CORS rejections happen at the browser; custom messages won’t surface on preflight. Use this helper where you return 403 from application endpoints.
-async function getUnauthorizedOriginError(chatflowId: string, workspaceId?: string): Promise<string> { +async function getUnauthorizedOriginError(chatflowId: string, _workspaceId?: string): Promise<string> {
102-102: Export a shared route segment constant to avoid driftConsider exporting PREDICTION_ROUTE_REGEX or a segment constant from here and reuse in XSS.ts and the router.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
packages/server/src/routes/predictions/index.ts(1 hunks)packages/server/src/services/chatflows/index.ts(1 hunks)packages/server/src/utils/XSS.ts(2 hunks)packages/server/src/utils/domainValidation.ts(1 hunks)
🧰 Additional context used
🪛 GitHub Check: build (ubuntu-latest, 18.15.0)
packages/server/src/utils/domainValidation.ts
[warning] 85-85:
'workspaceId' is defined but never used. Allowed unused args must match /^_/u
[warning] 11-11:
'workspaceId' is defined but never used. Allowed unused args must match /^_/u
🔇 Additional comments (2)
packages/server/src/utils/XSS.ts (1)
23-26: Default '' is a behavior change; confirm intendedReturning '' instead of '' makes cross-origin requests require explicit CORS_ORIGINS or per-chatflow allow (after the fix above). Confirm this is desired for backward compatibility; otherwise keep '' default.
If you intend restrictive-by-default, please update docs/env samples to reflect the change.
packages/server/src/routes/predictions/index.ts (1)
7-8: ****The mount path is
/prediction(singular) perroutes/index.ts:110, not/predictions(plural). All utility functions (isPredictionRequest,extractChatflowId) and constants consistently use the singular form, matching the comments. The folder naming convention (predictions) differs from the mount path, but this causes no functional mismatch or validation bypass. The code is correct as-is.Likely an incorrect or invalid review comment.
| export function getCorsOptions(): any { | ||
| const corsOptions = { | ||
| origin: function (origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) { | ||
| const allowedOrigins = getAllowedCorsOrigins() | ||
| if (!origin || allowedOrigins == '*' || allowedOrigins.indexOf(origin) !== -1) { | ||
| callback(null, true) | ||
| } else { | ||
| callback(null, false) | ||
| return (req: any, callback: (err: Error | null, options?: any) => void) => { | ||
| const corsOptions = { | ||
| origin: async (origin: string | undefined, originCallback: (err: Error | null, allow?: boolean) => void) => { | ||
| const allowedOrigins = getAllowedCorsOrigins() | ||
| const isPredictionReq = isPredictionRequest(req.url) | ||
|
|
||
| if (!origin || allowedOrigins === '*') { | ||
| await checkRequestType(isPredictionReq, req, origin, originCallback) | ||
| } else { | ||
| const allowedOriginsList = parseAllowedOrigins(allowedOrigins) | ||
| if (origin && allowedOriginsList.includes(origin)) { | ||
| await checkRequestType(isPredictionReq, req, origin, originCallback) | ||
| } else { | ||
| originCallback(null, false) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| callback(null, corsOptions) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: global CORS gate blocks chatflow-level validation for predictions
With allowedOrigins unset (''), any request with an Origin header is denied before checkRequestType runs. This breaks prediction requests that rely on per-chatflow allowedOrigins.
Apply this diff to evaluate chatflow rules first for prediction requests, and use OR semantics with the global allowlist:
export function getCorsOptions(): any {
- return (req: any, callback: (err: Error | null, options?: any) => void) => {
+ return (req: any, callback: (err: Error | null, options?: any) => void) => {
const corsOptions = {
- origin: async (origin: string | undefined, originCallback: (err: Error | null, allow?: boolean) => void) => {
- const allowedOrigins = getAllowedCorsOrigins()
- const isPredictionReq = isPredictionRequest(req.url)
-
- if (!origin || allowedOrigins === '*') {
- await checkRequestType(isPredictionReq, req, origin, originCallback)
- } else {
- const allowedOriginsList = parseAllowedOrigins(allowedOrigins)
- if (origin && allowedOriginsList.includes(origin)) {
- await checkRequestType(isPredictionReq, req, origin, originCallback)
- } else {
- originCallback(null, false)
- }
- }
- }
+ origin: async (origin: string | undefined, originCallback: (err: Error | null, allow?: boolean) => void) => {
+ const allowedOrigins = getAllowedCorsOrigins()
+ const isPredictionReq = isPredictionRequest(req.url)
+ const allowedList = parseAllowedOrigins(allowedOrigins)
+ const originLc = origin?.toLowerCase()
+
+ // Always allow no-Origin requests (same-origin, server-to-server)
+ if (!originLc) return originCallback(null, true)
+
+ // Global allow: '*' or exact match
+ const globallyAllowed = allowedOrigins === '*' || allowedList.includes(originLc)
+
+ if (isPredictionReq) {
+ // Per-chatflow allowlist OR globally allowed
+ const chatflowAllowed = await (async () => {
+ const chatflowId = extractChatflowId(req.url)
+ return chatflowId ? await validateChatflowDomain(chatflowId, originLc, req.user?.activeWorkspaceId) : true
+ })()
+ return originCallback(null, globallyAllowed || chatflowAllowed)
+ }
+
+ // Non-prediction: rely on global policy only
+ return originCallback(null, globallyAllowed)
+ }
}
callback(null, corsOptions)
}
}Also normalize the comparison by lowercasing origin before includes().
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In packages/server/src/utils/XSS.ts around lines 41-62, the origin handler
currently blocks prediction requests when the global allowedOrigins is unset and
runs the global check before the chatflow-level check; change the logic so that
for prediction requests you evaluate the chatflow rule (checkRequestType) first
and accept if it allows the request, otherwise fall back to the global
allowlist; when validating against lists normalize origin to lowercase and
compare against a lowercased allowedOrigins list, and treat the global allowlist
OR the chatflow allowlist as permissive (i.e., allow if either permits) so
prediction requests aren’t rejected prematurely.
Summary by CodeRabbit
New Features
Changes