Skip to content

Commit e120029

Browse files
fix(custom-block): bind publish authz to the source workflow's workspace
1 parent dfe5c6f commit e120029

2 files changed

Lines changed: 25 additions & 4 deletions

File tree

apps/sim/app/api/custom-blocks/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
111111
try {
112112
const block = await publishCustomBlock({
113113
organizationId,
114+
workspaceId,
114115
workflowId,
115116
userId,
116117
name,

apps/sim/lib/workflows/custom-blocks/operations.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,20 +188,33 @@ export class CustomBlockValidationError extends Error {
188188
}
189189

190190
/**
191-
* Publish a deployed workflow as an org-wide custom block. Validates that the
192-
* source workflow belongs to `organizationId` and is deployed, then inserts the
193-
* row. Caller must have already authorized admin + enterprise.
191+
* Publish a deployed workflow as an org-wide custom block. The source workflow
192+
* must live in `workspaceId` — the workspace the caller was verified to admin —
193+
* so a caller cannot publish another workspace's workflow (which then runs under
194+
* that workspace owner's credentials and returns caller-chosen outputs). Also
195+
* validates the workspace belongs to `organizationId` and the workflow is
196+
* deployed, then inserts the row.
194197
*/
195198
export async function publishCustomBlock(params: {
196199
organizationId: string
200+
workspaceId: string
197201
workflowId: string
198202
userId: string
199203
name: string
200204
description: string
201205
iconUrl?: string
202206
exposedOutputs?: CustomBlockOutput[]
203207
}): Promise<CustomBlockWithInputs> {
204-
const { organizationId, workflowId, userId, name, description, iconUrl, exposedOutputs } = params
208+
const {
209+
organizationId,
210+
workspaceId,
211+
workflowId,
212+
userId,
213+
name,
214+
description,
215+
iconUrl,
216+
exposedOutputs,
217+
} = params
205218

206219
const [wf] = await db
207220
.select({ id: workflow.id, workspaceId: workflow.workspaceId, isDeployed: workflow.isDeployed })
@@ -214,6 +227,13 @@ export async function publishCustomBlock(params: {
214227
throw new CustomBlockValidationError('Workflow must be deployed before publishing as a block')
215228
}
216229

230+
// Authorization boundary: the caller proved admin on `workspaceId` (route), so
231+
// the source workflow must actually live there. Without this a workspace admin
232+
// could publish a different workspace's workflow in the same org.
233+
if (wf.workspaceId !== workspaceId) {
234+
throw new CustomBlockValidationError('You can only publish a workflow from its own workspace')
235+
}
236+
217237
const ws = wf.workspaceId ? await getWorkspaceWithOwner(wf.workspaceId) : null
218238
if (!ws?.organizationId || ws.organizationId !== organizationId) {
219239
throw new CustomBlockValidationError('Workflow does not belong to this organization')

0 commit comments

Comments
 (0)