Skip to content

Fix cross-workspace chatflow access#987

Merged
maxtechera merged 2 commits into
stagingfrom
fix/cross-workspace-predictions
Mar 2, 2026
Merged

Fix cross-workspace chatflow access#987
maxtechera merged 2 commits into
stagingfrom
fix/cross-workspace-predictions

Conversation

@maxtechera
Copy link
Copy Markdown
Collaborator

Summary

  • allow internal predictions to access chatflows across assigned workspaces by removing active workspace filtering
  • enforce workspace membership checks before internal predictions execute
  • list chatflows across all assigned workspaces via IN query in getAllChatflows

Testing

  • not run (local build fails on Node v24/gulp ESM mismatch)

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
answerai-docs Building Building Preview Mar 2, 2026 3:15pm
the-answerai Building Building Preview Mar 2, 2026 3:15pm

Request Review

@maxtechera maxtechera merged commit 4a815aa into staging Mar 2, 2026
7 of 8 checks passed
@maxtechera maxtechera deleted the fix/cross-workspace-predictions branch March 2, 2026 15:20
@claude
Copy link
Copy Markdown

claude Bot commented Mar 2, 2026

PR Review: Fix cross-workspace chatflow access

Reviewer: Claude Code PR Review Agent
Date: 2026-03-02


Summary

This PR allows users with assigned workspaces to access chatflows across all their workspaces rather than only their active workspace. The change touches three files: the chatflows controller, the internal-predictions controller, and the chatflows service. The intent is sound, but there are several correctness and security concerns that need to be addressed before merging.


Critical Issues

1. Access Control Bypass When chatflow.workspaceId Is Null

Location: packages/server/src/controllers/internal-predictions/index.ts, lines 20-25

Issue: When a chatflow has no workspaceId (i.e., chatflow.workspaceId is falsy), hasWorkspaceAccess is unconditionally set to false, which will deny access even to users who may legitimately be allowed to access workspace-less chatflows. The prior code handled this by passing workspaceId into getChatflowById, which optionally filtered on it — the null case was implicitly permissive. The new code inverts that to implicitly restrictive, which may be a behaviour change that has not been evaluated.

// Current code
const hasWorkspaceAccess = chatflow.workspaceId
    ? assignedWorkspaces.some((ws: { id: string }) => ws.id === chatflow.workspaceId)
    : false  // <-- always denies for workspaceId-less chatflows

if (!hasWorkspaceAccess) {
    throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${req.params.id} not found`)
}

If chatflows without a workspaceId should never be accessible via internal predictions, add a comment or a dedicated error to document that intent. If they should be accessible, the logic must be changed. This is a logic correctness issue that could silently break legitimate use cases.


2. Workspace Access Check Is Not Verified Against the Streaming Execution Path

Location: packages/server/src/controllers/internal-predictions/index.ts, lines 27-30

Issue: The workspace access check runs before the streaming branch is evaluated, so it appears to apply to both paths. However, createAndStreamInternalPrediction re-fetches the chatflow internally via utilBuildChatflow, which calls chatflowsService.getChatflowForPrediction rather than getChatflowById. This means the streaming path acquires a potentially different chatflow object without re-validating workspace membership. If the two service calls can return different data (e.g., due to published version logic), the access check performed on the first fetch may not correctly cover the second fetch.

This gap needs to be verified or the check and execution need to operate on the same object.


3. Missing organizationId Filter in getAllChatflows

Location: packages/server/src/services/chatflows/index.ts, lines 147-192

Issue: The updated getAllChatflows function filters chatflows only by workspaceId or workspaceIds, with no organizationId constraint. The project conventions (CLAUDE.md) explicitly require that all database queries filter by organizationId to prevent cross-organization data leaks. Workspace IDs alone are not a substitute for an organizationId constraint.

The getAllChatflowsCountByOrganization function in the same file shows the correct pattern: first resolve workspaces for a given organizationId, then filter chatflows by those workspace IDs.

// Recommended pattern (from getAllChatflowsCountByOrganization in the same file)
const workspaces = await appServer.AppDataSource.getRepository(Workspace)
    .findBy({ organizationId: user.organizationId })
const safeWorkspaceIds = workspaces.map(ws => ws.id)
// Then intersect with assignedWorkspaces and filter chatflows

4. getAllChatflows Falls Through to No Workspace Filter When Both workspaceId and workspaceIds Are Undefined

Location: packages/server/src/controllers/chatflows/index.ts, lines 76-78

Issue: When a user has no assignedWorkspaces (the array is empty or undefined), workspaceIds is set to undefined and workspaceId falls back to req.user?.activeWorkspaceId. If activeWorkspaceId is also undefined (e.g., for a misconfigured user session), both workspaceId and workspaceIds are undefined, and the service query runs with no workspace filter at all, returning all chatflows in the database.

// If assignedWorkspaces is empty: workspaceIds = undefined
// If activeWorkspaceId is also missing: workspaceId = undefined
// Service receives (type, undefined, page, limit, undefined)
// -> No workspace filter applied -> returns all chatflows

A fallback to returning all chatflows when workspace context is absent should be an explicit error condition, not a silent pass-through.


Major Concerns

5. No Tests Provided; Author Confirms Tests Were Not Run

Location: PR description

Issue: The PR description explicitly states "not run (local build fails on Node v24/gulp ESM mismatch)". The changes modify multi-workspace access control — a security-sensitive area — and unverified changes create risk. The Node v24/gulp mismatch should be resolved and a CI run confirming the test suite passes should be present before this PR is merged.


6. req.user as any Cast Bypasses Type Safety

Location: packages/server/src/controllers/chatflows/index.ts, line 76

Issue: The cast (req.user as any)?.assignedWorkspaces silently accepts any shape. The internal-predictions controller in the same PR uses a more precise cast. The two controllers should be consistent.

// Current (chatflows controller) - loses type safety
const assignedWorkspaces = (req.user as any)?.assignedWorkspaces as Array<{ id: string }> | undefined

// Better - consistent with internal-predictions controller
const assignedWorkspaces = (req.user as { assignedWorkspaces?: Array<{ id: string }> } | undefined)?.assignedWorkspaces

Minor Issues and Suggestions

7. Inconsistent Error Message Format

Location: packages/server/src/controllers/internal-predictions/index.ts, line 24

The error message Chatflow ${req.params.id} not found does not follow the project convention of Error: {serviceName}.{methodName} - {description}.

// Suggested format
throw new InternalFlowiseError(
    StatusCodes.NOT_FOUND,
    `Error: internalPredictionsController.createInternalPrediction - Chatflow ${req.params.id} not found or access denied`
)

8. Intent of NOT_FOUND vs UNAUTHORIZED for Authorization Denial Should Be Documented

Location: packages/server/src/controllers/internal-predictions/index.ts, line 24

Returning NOT_FOUND rather than UNAUTHORIZED for an authorization failure is a good security practice (avoids confirming resource existence to unauthorized callers). A brief inline comment would clarify this choice for future maintainers.

9. assignedWorkspaces Type on IUser Should Be Tightened

Location: packages/server/src/Interface.ts, line 147

assignedWorkspaces?: any[] should reference the IAssignedWorkspace type already defined in Interface.Enterprise.ts. This would allow TypeScript to catch inconsistent property access patterns at compile time.


Positive Observations

  • The approach of fetching chatflows for all assigned workspaces via a single IN query rather than multiple individual queries is efficient and correct from a database perspective.
  • The use of parameterized TypeORM query builder syntax (IN (:...workspaceIds)) correctly prevents SQL injection.
  • The change from import { ... } to import type { ... } in internal-predictions/index.ts is a good hygiene improvement.
  • The workspace access check is placed before the streaming/non-streaming branch split, which is architecturally correct — one check governs both code paths at the entry point.
  • The PR correctly targets staging and not main.

TheAnswer-Specific Checklist


Overall Recommendation: Request Changes

The intent of this PR is valid and the core approach (IN query for multiple workspaces, membership check before execution) is sound. However, there are four critical correctness and security issues that must be resolved:

  1. The chatflow.workspaceId === null access denial edge case needs an explicit, documented decision.
  2. The getAllChatflows query must include an organizationId constraint to match project multi-tenancy requirements.
  3. The empty-assignedWorkspaces + missing-activeWorkspaceId fallback must not silently remove all workspace filtering.
  4. The streaming path relationship between the pre-check chatflow and the execution chatflow needs to be verified or unified.

The PR should also include a passing test run to confirm the changes work as intended before merge.

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.

1 participant