Skip to content

feat(custom-block): deploy a workflow as a reusable org-scoped block#5407

Open
TheodoreSpeaks wants to merge 6 commits into
stagingfrom
feat/custom-block
Open

feat(custom-block): deploy a workflow as a reusable org-scoped block#5407
TheodoreSpeaks wants to merge 6 commits into
stagingfrom
feat/custom-block

Conversation

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator

Summary

  • Publish a deployed workflow as a reusable block, scoped to the org and usable across every workspace like a native block
  • Start-block input fields become the block's inputs (anchored on stable field ids so renaming a field never orphans a placed value); curated, renamable outputs
  • Runs the source workflow's latest deployment under the owner's authority (invocation boundary) — cross-workspace consumers need no access to the source workflow
  • Gated by the deploy-as-block feature flag; org admins disable it via Access Control like any other block
  • Exposed to the copilot: VFS block files + a Custom Blocks section in the workspace context + an edit_workflow/get_blocks_metadata registry overlay (the typed VFS snapshot is untouched, so the Go diff is unaffected)
  • Billing: the child run's hosted-key cost rolls onto the block so the org is charged exactly as if the workflow ran directly
  • New custom_block table (migration 0254 — additive, backward-compatible)

Type of Change

  • New feature

Testing

  • Tested manually
  • bun run lint, bun run check:api-validation:strict, and bun run check:migrations origin/staging all pass
  • 170 unit tests pass (build-config, overlay, serializer, input-format, copilot VFS/context)

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 4, 2026 2:23am

Request Review

@cursor

cursor Bot commented Jul 4, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Changes workflow execution identity, cross-workspace invocation, billing aggregation, and block registry resolution—large surface area beyond new CRUD APIs.

Overview
Adds deploy-as-block: orgs on enterprise can publish a deployed workflow as a custom_block_* type that appears in the toolbar, deploy modal, and access control like a native block.

Backend & data: New custom_block table and /api/custom-blocks routes (list/publish, PATCH/DELETE) gated by deploy-as-block (AppConfig + DEPLOY_AS_BLOCK) and enterprise billing. Blocks synthesize BlockConfig from Start inputs (stable field ids) and optional curated outputs; client and server registry overlays hydrate enabled blocks per workspace/org.

Execution: Custom blocks run via workflow_executor against the source workflow’s latest deployment under the owner’s user/workspace/env (cross-workspace within the org), with authority resolved from the DB—not serialized workflowId. Child trace spans are hidden; aggregated child cost bills on the block. executeWorkflowCore, pre-exec serialization, and copilot edit_workflow / get_blocks_metadata wrap withCustomBlockOverlay.

UI: Deploy modal Block tab (BlockDeploy: icon, outputs picker, publish/update/unpublish), CustomBlocksLoader in workspace layout, toolbar Custom Blocks section (excludes the current workflow), trace view labels custom blocks, shared DropZone for icon upload.

Copilot: Custom blocks in WORKSPACE.md, per-block VFS JSON (plumbing hidden), metadata tool treats them as self-contained; typed VFS snapshot unchanged.

Other: extractInputFieldsFromBlocks preserves field id; serializer avoids stale workflowId/inputMapping on custom blocks; input-format and overlay unit tests added.

Reviewed by Cursor Bugbot for commit dfe5c6f. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread apps/sim/executor/handlers/workflow/workflow-handler.ts
Comment thread apps/sim/lib/workflows/custom-blocks/operations.ts
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@greptile-apps

greptile-apps Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR implements "Deploy as Block" — a new Enterprise/org-scoped feature that lets an org admin publish any deployed workflow as a reusable block available across every workspace in the org, gated by the deploy-as-block feature flag. The custom block runs the source workflow's latest deployment under the source owner's identity (invocation boundary), so cross-workspace consumers need no access to the source workflow's credentials or environment.

  • New custom_block table (migration 0254, additive): stores type slug, bound workflowId, curated outputs, and enabled flag; FK cascades on workflow/org deletion prevent orphaned blocks.
  • Block execution path: WorkflowBlockHandler.execute now dispatches custom_block_* types through getCustomBlockAuthority (org-scoped DB lookup), remaps consumer input keys from stable field IDs to current field names, runs the child with the owner's env/credentials, and projects curated outputs via projectCustomBlockOutput.
  • Overlay architecture: a per-request AsyncLocalStorage-backed server overlay injects custom block configs into the synchronous @/blocks/registry accessors, keeping all existing callers (serializer, executor, copilot tools) unchanged; the client overlay hydrates the block palette on mount via CustomBlocksLoader.

Confidence Score: 5/5

The new execution path correctly enforces org-scoping at the authority lookup, applies owner-identity substitution, and cleans up child workflow outputs before returning them to the consumer — no new blocking issues found.

The invocation boundary model is implemented correctly across the authority lookup, handler, and overlay layers. New findings are quality-of-life concerns that do not affect correctness or security.

apps/sim/lib/workflows/custom-blocks/operations.ts for the N+1 query pattern in the list path; apps/sim/executor/handlers/workflow/workflow-handler.ts for the reserved output name collision noted in a prior review round.

Important Files Changed

Filename Overview
apps/sim/lib/workflows/custom-blocks/operations.ts Core DB operations for custom blocks: publish/update/delete, authority lookup, and list with live-derived input fields. Authority lookup correctly scopes to the consumer's org. listCustomBlocksWithInputs fires N concurrent loadWorkflowFromNormalizedTables queries.
apps/sim/executor/handlers/workflow/workflow-handler.ts Extends WorkflowBlockHandler for custom block execution: authority lookup, cross-workspace boundary skip, owner-identity substitution, field-ID to name remapping, and curated output projection. projectCustomBlockOutput initializes output = { success: true } then writes exposed output names, so a name of "success" or "error" would overwrite the sentinel fields.
apps/sim/blocks/custom/server-overlay.ts Per-request AsyncLocalStorage overlay making custom_block_* types resolvable through the synchronous registry accessors. Clean, well-isolated design.
apps/sim/blocks/custom/build-config.ts Synthesizes BlockConfig from a DB row and live input fields. Uses stable field IDs as sub-block IDs for rename-safe wiring.
packages/db/schema.ts New custom_block table with correct FK cascades. Unique index on (organization_id, type) but no unique constraint on (organization_id, workflow_id).
apps/sim/app/api/custom-blocks/route.ts GET returns all blocks so the client can filter. POST validates admin access, org membership, enterprise plan, and feature flag before publishing.
apps/sim/app/api/custom-blocks/[id]/route.ts PATCH/DELETE handlers with correct authz order — loads block first to derive org, then checks feature flag and admin permission.
apps/sim/app/workspace/[workspaceId]/providers/custom-blocks-loader.tsx Client-side hydrator. Correctly filters by enabled before calling hydrateClientCustomBlocks.
apps/sim/lib/workflows/executor/execution-core.ts Wraps execution with the custom-block overlay at the shared choke point for all execution paths (sync route + background job).
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/block/block.tsx BlockDeploy form handling publish/update/unpublish. Icon upload reuses workspace-logos storage context. Dirty comparison for outputs uses untrimmed local state against trimmed server data.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Consumer as Consumer Workspace
    participant Handler as WorkflowBlockHandler
    participant Auth as getCustomBlockAuthority
    participant DB as Database
    participant OwnerEnv as Owner Env/Identity
    participant Child as Child Executor

    Consumer->>Handler: execute(custom_block_xyz block)
    Handler->>Auth: getCustomBlockAuthority(type, consumerWorkspaceId)
    Auth->>DB: getWorkspaceWithOwner(consumerWorkspaceId)
    Auth->>DB: "SELECT custom_block JOIN workflow WHERE type=? AND org=?"
    DB-->>Auth: workflowId, ownerUserId, exposedOutputs
    Auth-->>Handler: authority
    Handler->>DB: checkChildDeployment(workflowId, ownerUserId)
    Handler->>DB: loadChildWorkflowDeployed(workflowId, ownerUserId)
    Handler->>Handler: remapCustomBlockInputKeys(mapping, childBlocks)
    Handler->>OwnerEnv: getPersonalAndWorkspaceEnv(ownerUserId, ownerWorkspaceId)
    OwnerEnv-->>Handler: personalDecrypted + workspaceDecrypted
    Handler->>Child: new Executor(serializedState, ownerEnvVars, ownerUserId)
    Child-->>Handler: ExecutionResult
    Handler->>Handler: projectCustomBlockOutput(result, exposedOutputs, childCost)
    Handler-->>Consumer: BlockOutput success + namedOutputs
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Consumer as Consumer Workspace
    participant Handler as WorkflowBlockHandler
    participant Auth as getCustomBlockAuthority
    participant DB as Database
    participant OwnerEnv as Owner Env/Identity
    participant Child as Child Executor

    Consumer->>Handler: execute(custom_block_xyz block)
    Handler->>Auth: getCustomBlockAuthority(type, consumerWorkspaceId)
    Auth->>DB: getWorkspaceWithOwner(consumerWorkspaceId)
    Auth->>DB: "SELECT custom_block JOIN workflow WHERE type=? AND org=?"
    DB-->>Auth: workflowId, ownerUserId, exposedOutputs
    Auth-->>Handler: authority
    Handler->>DB: checkChildDeployment(workflowId, ownerUserId)
    Handler->>DB: loadChildWorkflowDeployed(workflowId, ownerUserId)
    Handler->>Handler: remapCustomBlockInputKeys(mapping, childBlocks)
    Handler->>OwnerEnv: getPersonalAndWorkspaceEnv(ownerUserId, ownerWorkspaceId)
    OwnerEnv-->>Handler: personalDecrypted + workspaceDecrypted
    Handler->>Child: new Executor(serializedState, ownerEnvVars, ownerUserId)
    Child-->>Handler: ExecutionResult
    Handler->>Handler: projectCustomBlockOutput(result, exposedOutputs, childCost)
    Handler-->>Consumer: BlockOutput success + namedOutputs
Loading

Reviews (4): Last reviewed commit: "feat(custom-block): run child under sour..." | Re-trigger Greptile

Comment thread apps/sim/lib/workflows/custom-blocks/operations.ts
Comment on lines +140 to +157
* deletes the workflow → the custom_block row, so there is never an orphaned block.
* `null` when no enabled block matches the type.
*/
export async function getCustomBlockAuthority(type: string): Promise<{
workflowId: string
organizationId: string
ownerUserId: string
exposedOutputs: CustomBlockOutput[]
} | null> {
const [row] = await db
.select({
workflowId: customBlock.workflowId,
organizationId: customBlock.organizationId,
enabled: customBlock.enabled,
outputs: customBlock.outputs,
ownerUserId: workflow.userId,
})
.from(customBlock)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 getCustomBlockAuthority resolves by type only — no org-boundary check

The query uses only eq(customBlock.type, type) without joining to the executing workspace's org. If a workflow snapshot ever contains a custom_block_* type from a different org, the executor would silently load and run the source workflow under its owner's credentials with no cross-org check. Adding an organizationId parameter to the function and including it in the WHERE clause would make the invocation boundary explicit rather than relying solely on the overlay's serialization guard.

@greptile-apps

greptile-apps Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces the "deploy-as-block" feature, allowing org admins to publish a deployed workflow as a reusable, org-scoped custom block that appears in the block palette like any native block. It is a large but well-structured addition gated behind a feature flag and an enterprise plan check.

  • New custom_block table (migration 0254) with FK cascades to organization, workflow, and user; a composite unique index on (organization_id, type) ensures the generated type slug is unique per org.
  • Dual-environment block registry overlay: an AsyncLocalStorage-backed server overlay for request isolation and a useSyncExternalStore-backed client overlay hydrated by CustomBlocksLoader, giving every synchronous getBlock/getAllBlocks call site access to custom block configs without breaking either environment.
  • Invocation-boundary execution model: authority (bound workflow id + owner user id + exposed outputs) is always read from the DB at runtime, never from the serialized block, so cross-workspace consumers need no permission on the source workflow.

Confidence Score: 3/5

The core invocation-boundary model and registry overlay are solid, but two correctness issues in the client overlay and the DB schema need fixing before this ships to production.

The client overlay is hydrated with all blocks including disabled ones — an admin who disables a block to pull it from users palettes will find it still appears and is placeable (execution then fails with a confusing error). Separately, the DB has no unique constraint on (organization_id, workflow_id), so direct API calls can create multiple custom blocks for the same source workflow; the UI only surfaces the first match, leaving extras permanently orphaned in the DB but still resolvable at runtime.

custom-blocks-loader.tsx (disabled-block filter missing), packages/db/schema.ts and operations.ts (missing unique constraint + server-side validation on workflow_id per org), and operations.ts getCustomBlockAuthority (no index on type alone for the execution hot path).

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/providers/custom-blocks-loader.tsx Hydrates the client overlay with ALL custom blocks including disabled ones — disabled blocks appear in the palette and can be placed on workflows.
packages/db/schema.ts New custom_block table with correct FK cascades and org-type unique index, but missing a unique constraint on (organization_id, workflow_id) that would prevent duplicate blocks for the same workflow.
apps/sim/lib/workflows/custom-blocks/operations.ts Core CRUD and authority-resolution logic; getCustomBlockAuthority queries by type alone without a dedicated index, and publishCustomBlock lacks a server-side uniqueness check per workflow per org.
apps/sim/executor/handlers/workflow/workflow-handler.ts Custom block execution added via invocation-boundary pattern; authority loaded from DB, owner userId used for deployed-context load, and curated outputs projected correctly without leaking child internals.
apps/sim/blocks/custom/server-overlay.ts AsyncLocalStorage overlay correctly isolates per-org custom block resolution across concurrent requests; placeholder icon and schema-agnostic empty inputFields appropriately omit client-only concerns.
apps/sim/blocks/custom/build-config.ts Well-structured block config synthesis: stable field-id anchoring for rename-safety, correct sub-block type mapping, and clean output projection.
apps/sim/app/api/custom-blocks/route.ts GET returns all blocks (enabled + disabled) to any workspace member; admin-only vs member visibility of disabled blocks is not differentiated.
apps/sim/app/api/custom-blocks/[id]/route.ts PATCH and DELETE correctly gate on feature flag + org admin/owner; authorizeManage loads the block first to derive organizationId, preventing org spoofing.
apps/sim/lib/workflows/executor/execution-core.ts Wraps execution in the custom-block overlay at the shared choke point; queries getCustomBlockRowsForWorkspace independently, duplicating the fetch already done in the execute route.
packages/db/migrations/0254_custom_block.sql Additive migration with correct FK + cascade semantics; no index on type alone for the execution-time authority lookup.
apps/sim/blocks/custom/client-overlay.ts External-store pattern for notifying useSyncExternalStore subscribers on overlay changes is correct; version bump on hydrate ensures Access Control block list refreshes without a page reload.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/block/block.tsx Deploy UI correctly derives output options from live workflow state and reconciles existing curated outputs; JSON.stringify comparison for dirty detection is order-sensitive but unlikely to cause problems in practice.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Admin as Org Admin
    participant API as POST /api/custom-blocks
    participant DB as custom_block table
    participant Loader as CustomBlocksLoader
    participant Overlay as Client Overlay
    participant Canvas as Block Palette
    participant Exec as WorkflowBlockHandler
    participant Auth as getCustomBlockAuthority
    participant Child as Child Workflow

    Admin->>API: Publish workflow as block
    API->>DB: INSERT custom_block
    DB-->>API: row
    API-->>Admin: customBlock

    Loader->>API: GET /api/custom-blocks
    API->>DB: SELECT all blocks for org
    DB-->>API: all rows including disabled
    API-->>Loader: customBlocks array
    Loader->>Overlay: hydrateClientCustomBlocks all blocks
    Overlay-->>Canvas: custom_block types resolve

    Canvas->>Exec: execute custom block
    Exec->>Auth: getCustomBlockAuthority by type
    Auth->>DB: SELECT WHERE type equals value
    DB-->>Auth: authority row
    Auth-->>Exec: workflowId ownerUserId exposedOutputs
    Exec->>Child: loadChildWorkflowDeployed
    Child-->>Exec: deployed snapshot
    Exec->>Child: execute
    Child-->>Exec: ExecutionResult
    Exec->>Exec: projectCustomBlockOutput
    Exec-->>Canvas: BlockOutput curated only
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Admin as Org Admin
    participant API as POST /api/custom-blocks
    participant DB as custom_block table
    participant Loader as CustomBlocksLoader
    participant Overlay as Client Overlay
    participant Canvas as Block Palette
    participant Exec as WorkflowBlockHandler
    participant Auth as getCustomBlockAuthority
    participant Child as Child Workflow

    Admin->>API: Publish workflow as block
    API->>DB: INSERT custom_block
    DB-->>API: row
    API-->>Admin: customBlock

    Loader->>API: GET /api/custom-blocks
    API->>DB: SELECT all blocks for org
    DB-->>API: all rows including disabled
    API-->>Loader: customBlocks array
    Loader->>Overlay: hydrateClientCustomBlocks all blocks
    Overlay-->>Canvas: custom_block types resolve

    Canvas->>Exec: execute custom block
    Exec->>Auth: getCustomBlockAuthority by type
    Auth->>DB: SELECT WHERE type equals value
    DB-->>Auth: authority row
    Auth-->>Exec: workflowId ownerUserId exposedOutputs
    Exec->>Child: loadChildWorkflowDeployed
    Child-->>Exec: deployed snapshot
    Exec->>Child: execute
    Child-->>Exec: ExecutionResult
    Exec->>Exec: projectCustomBlockOutput
    Exec-->>Canvas: BlockOutput curated only
Loading

Reviews (2): Last reviewed commit: "feat(custom-block): deploy a workflow as..." | Re-trigger Greptile

Comment thread apps/sim/app/workspace/[workspaceId]/providers/custom-blocks-loader.tsx Outdated
Comment thread apps/sim/app/workspace/[workspaceId]/providers/custom-blocks-loader.tsx Outdated
Comment thread packages/db/schema.ts
Comment on lines +138 to +165
* API/schedule/webhook run executes as. Using the owner (not the publisher) means
* the owner always has read on their own workflow, and owner deletion cascade-
* deletes the workflow → the custom_block row, so there is never an orphaned block.
* `null` when no enabled block matches the type.
*/
export async function getCustomBlockAuthority(type: string): Promise<{
workflowId: string
organizationId: string
ownerUserId: string
exposedOutputs: CustomBlockOutput[]
} | null> {
const [row] = await db
.select({
workflowId: customBlock.workflowId,
organizationId: customBlock.organizationId,
enabled: customBlock.enabled,
outputs: customBlock.outputs,
ownerUserId: workflow.userId,
})
.from(customBlock)
.innerJoin(workflow, eq(workflow.id, customBlock.workflowId))
.where(eq(customBlock.type, type))
.limit(1)

if (!row || !row.enabled) return null
return {
workflowId: row.workflowId,
organizationId: row.organizationId,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Execution-time authority lookup scans the table on type alone

getCustomBlockAuthority filters by WHERE type = $1 with no other predicate. The only index that includes type is the composite unique index on (organization_id, type). PostgreSQL uses a B-tree left-to-right, so a filter on just the second column of the composite index results in a sequential scan. This lookup runs on every custom block invocation — adding a standalone index on type would make the hot path efficient as the table grows.

Comment thread apps/sim/app/api/workflows/[id]/execute/route.ts
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

Comment thread apps/sim/executor/handlers/workflow/workflow-handler.ts
description,
iconUrl,
exposedOutputs,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Publish without source workspace admin

Medium Severity

Publishing checks workspace admin on the workspaceId in the request body only. publishCustomBlock verifies the target workflow belongs to that workspace’s organization, not that the workflow lives in that workspace or that the caller admins the workflow’s home workspace.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7c6b2f0. Configure here.

Comment thread apps/sim/lib/workflows/custom-blocks/operations.ts
name: name.trim(),
description: description.trim(),
exposedOutputs,
...(iconChanged ? { iconUrl: iconUrl || null } : {}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale outputs mark form dirty

Medium Severity

The Update action compares visibleOutputs (filtered when workflow data loads) to existing.exposedOutputs. Orphaned outputs hidden from the UI still differ from stored data, so the form becomes dirty without edits and save persists the trimmed list.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7c6b2f0. Configure here.

# Conflicts:
#	scripts/check-api-validation-contracts.ts
Comment thread apps/sim/executor/handlers/workflow/workflow-handler.ts
Comment thread apps/sim/lib/api/contracts/custom-blocks.ts
const allCustomBlocks = useMemo(() => {
if (!customBlocksData?.length) return []
return customBlocksData
.filter((cb) => cb.workflowId !== currentWorkflowId)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Toolbar shows disabled blocks

Medium Severity

The Custom Blocks toolbar section builds its list from useCustomBlocks without filtering enabled, while CustomBlocksLoader explicitly drops disabled blocks so runs do not fail. Disabled org blocks still appear in the palette and can be placed, but execution rejects them as unavailable.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3be6122. Configure here.

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit dfe5c6f. Configure here.

const ws = wf.workspaceId ? await getWorkspaceWithOwner(wf.workspaceId) : null
if (!ws?.organizationId || ws.organizationId !== organizationId) {
throw new CustomBlockValidationError('Workflow does not belong to this organization')
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Publish skips workspace check

Medium Severity

Publishing a custom block checks org membership and workspace admin on the request’s workspaceId, but never verifies that workflowId belongs to that workspace. An org admin in one workspace can publish another workspace’s deployed workflow as an org-wide block via the API.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit dfe5c6f. Configure here.

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