Skip to content

Conversation

@0xi4o
Copy link
Contributor

@0xi4o 0xi4o commented Oct 10, 2025

Summary by CodeRabbit

  • New Features

    • Added domain validation for prediction requests
    • Enhanced cross-origin request validation with improved request handling
  • Changes

    • Updated public API responses to refine configuration data exposure

@0xi4o 0xi4o self-assigned this Oct 10, 2025
@0xi4o 0xi4o marked this pull request as draft October 10, 2025 09:26
@HenryHengZJ
Copy link
Contributor

here's PR opened by vasu: #5297

@0xi4o 0xi4o marked this pull request as ready for review October 27, 2025 07:35
@0xi4o 0xi4o requested a review from HenryHengZJ October 27, 2025 07:35
@vasunous
Copy link

Overall logic looks good. Added few minor comments.

@HenryHengZJ
Copy link
Contributor

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Oct 29, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Oct 29, 2025

Walkthrough

These 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

Cohort / File(s) Summary
CORS Configuration and Domain Validation Infrastructure
packages/server/src/utils/XSS.ts, packages/server/src/utils/domainValidation.ts
Refactored getCorsOptions() to return an async handler that validates prediction requests against chatflow-allowed domains; added domain validation utilities including validateChatflowDomain(), extractChatflowId(), isPredictionRequest(), and getUnauthorizedOriginError()
Chatflow Config Filtering
packages/server/src/services/chatflows/index.ts
Removed allowedOrigins and allowedOriginsError properties from the response in getSinglePublicChatbotConfig
Route Documentation
packages/server/src/routes/predictions/index.ts
Added clarifying comments about chatflow ID extraction from prediction URLs

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Areas requiring extra attention:

  • XSS.ts: The refactoring from static CORS options to async handler introduces new control flow; verify that callback handling and error scenarios are properly managed
  • domainValidation.ts: Ensure error handling is robust for missing chatflows and invalid URL formats; validate that database queries don't introduce performance issues
  • chatflows/index.ts: Confirm that removing allowedOrigins and allowedOriginsError from public responses doesn't break existing API consumers

Poem

🐰 Through prediction URLs we hop and bound,
With domains verified and chatflows found!
CORS guards the gates with origin care,
While secrets stay hidden, just right to share! 🔐

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "Fix: CORS-related issues" directly relates to the main changes in the changeset. The primary modifications are in XSS.ts, where getCorsOptions() is rewritten to include prediction request detection and domain-based origin validation, and a new domainValidation.ts file is added to support CORS logic. The title is concise, clear, and specific enough that a teammate scanning history would understand the primary change concerns CORS improvements. The title avoids noise and vague terminology while accurately summarizing the focus of the PR.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/cors

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a 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-agnostic

Current 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 config

Hiding 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 parsing

If 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 drift

Consider 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

📥 Commits

Reviewing files that changed from the base of the PR and between ac794ab and c01c33b.

📒 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 intended

Returning '' 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) per routes/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.

Comment on lines 41 to +62
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)
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

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.

4 participants