Skip to content

Commit 237be12

Browse files
waleedlatif1claude
andcommitted
fix(security): exclude archived workflows from webhook deploy path conflict
findConflictingWebhookPathOwner now joins workflow and filters isNull(workflow.archivedAt), matching the runtime dispatcher (findAllWebhooksForPath). A webhook on an archived workflow can never receive deliveries at runtime, so it must not block legitimate path reuse with a 409. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 592e048 commit 237be12

1 file changed

Lines changed: 18 additions & 4 deletions

File tree

apps/sim/lib/webhooks/deploy.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { db } from '@sim/db'
2-
import { account, credentialSetMember, webhook, workflowDeploymentVersion } from '@sim/db/schema'
2+
import {
3+
account,
4+
credentialSetMember,
5+
webhook,
6+
workflow,
7+
workflowDeploymentVersion,
8+
} from '@sim/db/schema'
39
import { createLogger } from '@sim/logger'
410
import { generateShortId } from '@sim/utils/id'
511
import { and, eq, inArray, isNotNull, isNull, or } from 'drizzle-orm'
@@ -28,8 +34,8 @@ const CREDENTIAL_SET_PREFIX = 'credentialSet:'
2834
* the given path, or `null` if the path is free or owned by this workflow.
2935
* Guards against cross-tenant path collisions, since paths are user-controlled
3036
* and only unique per deployment version. Mirrors the runtime dispatcher's
31-
* `isActive`/`archivedAt` filter so inactive (e.g. undeployed) webhooks — which
32-
* never receive deliveries don't permanently reserve a path.
37+
* filter (active, non-archived webhook on a non-archived workflow) so webhooks
38+
* that can never receive deliveries don't permanently reserve a path.
3339
*/
3440
async function findConflictingWebhookPathOwner(params: {
3541
path: string
@@ -40,7 +46,15 @@ async function findConflictingWebhookPathOwner(params: {
4046
const existing = await db
4147
.select({ workflowId: webhook.workflowId })
4248
.from(webhook)
43-
.where(and(eq(webhook.path, path), eq(webhook.isActive, true), isNull(webhook.archivedAt)))
49+
.innerJoin(workflow, eq(webhook.workflowId, workflow.id))
50+
.where(
51+
and(
52+
eq(webhook.path, path),
53+
eq(webhook.isActive, true),
54+
isNull(webhook.archivedAt),
55+
isNull(workflow.archivedAt)
56+
)
57+
)
4458

4559
const conflict = existing.find((row) => row.workflowId !== workflowId)
4660
return conflict ? conflict.workflowId : null

0 commit comments

Comments
 (0)