Skip to content

Conversation

@icecrasher321
Copy link
Collaborator

Summary

Response blocks should be singleton. Causing issues with workflow block, and in general should be like an API contract.

Type of Change

  • Bug fix

Testing

Tested manually

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
Copy link

vercel bot commented Jan 10, 2026

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

1 Skipped Deployment
Project Deployment Review Updated (UTC)
docs Skipped Skipped Jan 10, 2026 8:16pm

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 10, 2026

Greptile Overview

Greptile Summary

This PR implements singleton enforcement for Response blocks to ensure only one Response block can exist per workflow. The implementation follows the existing pattern used for trigger blocks and includes comprehensive validation across most user interaction paths.

What Works Well

Core Implementation:

  • Added singleInstance: true flag to ResponseBlock configuration
  • Extended BlockConfig type system with optional singleInstance field
  • Created three reusable utility methods in TriggerUtils class for validation:
    • isSingleInstanceBlockType() - checks if block type is singleton
    • wouldViolateSingleInstanceBlock() - checks if adding would violate constraint
    • getSingleInstanceBlockIssue() - returns validation error details
  • Updated naming logic so Response blocks always named "Response" (no numbering)

Validation Coverage:

  • ✅ Toolbar drops (via checkTriggerConstraints in workflow.tsx)
  • ✅ Toolbar clicks/adds (via checkTriggerConstraints)
  • ✅ Context menu paste (via validateTriggerPaste)
  • ✅ Keyboard paste Cmd+V (via validateTriggerPaste)
  • ✅ Context menu duplicate (via validateTriggerPaste)
  • ✅ Copilot workflow edits (via getSingleInstanceBlockIssue in edit-workflow.ts)
  • ✅ Duplicate button hidden from UI for Response blocks

Critical Issue Found

❌ Action Bar Duplicate Function Bypass:
The handleDuplicateBlock function in action-bar.tsx does NOT validate single-instance constraints before duplicating. While the duplicate button is visually hidden for Response blocks, the function itself can still be invoked programmatically, creating a security bypass. All other duplication paths properly validate via validateTriggerPaste.

Minor Issue

Error Message Inconsistency:
The error messages for single-instance blocks use slightly different formatting than trigger blocks in the duplicate case, though both are functionally correct.

Architecture Notes

The solution cleverly reuses TriggerUtils class (despite the name) to handle both trigger singleton constraints and general block singleton constraints, maintaining consistency with existing patterns. The validation occurs at all major entry points except the action bar duplicate handler.

Confidence Score: 3/5

  • This PR has a critical validation bypass that allows singleton constraint violation through the action bar duplicate handler
  • Score reflects excellent implementation of the core singleton pattern across 7 of 8 files, but the critical bypass in action-bar.tsx's handleDuplicateBlock function means the singleton enforcement is incomplete. While the duplicate button is hidden in the UI, the underlying function lacks validation, creating a potential vulnerability. The fix is straightforward (add validateTriggerPaste call), but until implemented, users could bypass the constraint through programmatic invocation or future UI changes.
  • apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/action-bar/action-bar.tsx - MUST add validation to handleDuplicateBlock before the PR can be safely merged

Important Files Changed

File Analysis

Filename Score Overview
apps/sim/blocks/blocks/response.ts 5/5 Added singleInstance flag to ResponseBlock config - clean and straightforward change
apps/sim/lib/workflows/triggers/triggers.ts 5/5 Added three utility methods for single-instance block validation - follows existing trigger validation pattern
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-canvas-helpers.ts 5/5 Updated validateTriggerPaste to check single-instance constraints - properly integrated
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/action-bar/action-bar.tsx 2/5 Hides duplicate button for Response blocks but handleDuplicateBlock lacks validation - critical bypass vulnerability

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as UI Layer
    participant Validation as validateTriggerPaste
    participant TriggerUtils
    participant BlockRegistry as Block Registry
    participant Store as Workflow Store

    Note over User,Store: Adding Response Block via Toolbar
    User->>UI: Drag Response block to canvas
    UI->>UI: checkTriggerConstraints(blockType)
    UI->>TriggerUtils: getSingleInstanceBlockIssue(blocks, 'response')
    TriggerUtils->>BlockRegistry: getBlock('response')
    BlockRegistry-->>TriggerUtils: { singleInstance: true, name: 'Response' }
    TriggerUtils->>TriggerUtils: Check if 'response' exists in blocks
    alt Response already exists
        TriggerUtils-->>UI: { issue: 'duplicate', blockName: 'Response' }
        UI->>User: Show error notification
    else No existing Response
        TriggerUtils-->>UI: null
        UI->>Store: addBlock('response', ...)
        Store-->>User: Block added successfully
    end

    Note over User,Store: Pasting/Duplicating Response Block
    User->>UI: Paste (Cmd+V) or Context Menu Duplicate
    UI->>Validation: validateTriggerPaste([{type:'response'}], blocks, action)
    Validation->>TriggerUtils: getSingleInstanceBlockIssue(blocks, 'response')
    TriggerUtils->>BlockRegistry: getBlock('response')
    BlockRegistry-->>TriggerUtils: { singleInstance: true, name: 'Response' }
    TriggerUtils->>TriggerUtils: wouldViolateSingleInstanceBlock(blocks, 'response')
    alt Response already exists
        TriggerUtils-->>Validation: { issue: 'duplicate', blockName: 'Response' }
        Validation-->>UI: { isValid: false, message: 'Cannot duplicate...' }
        UI->>User: Show error notification
    else No existing Response
        TriggerUtils-->>Validation: null
        Validation-->>UI: { isValid: true }
        UI->>Store: collaborativeBatchAddBlocks(...)
        Store-->>User: Block(s) added successfully
    end

    Note over User,Store: VULNERABILITY: Action Bar Duplicate Button
    User->>UI: Click duplicate on Response action bar
    UI->>UI: handleDuplicateBlock()
    Note right of UI: ⚠️ NO VALIDATION!
    UI->>Store: collaborativeBatchAddBlocks(duplicateBlock)
    Store-->>User: Duplicate created (violates singleton!)

Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 10, 2026

Additional Comments (1)

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/action-bar/action-bar.tsx
The handleDuplicateBlock function does not validate single-instance block constraints before duplicating. This means a user can click the duplicate button on a Response block's action bar and create multiple Response blocks, bypassing the singleton constraint that this PR is supposed to enforce.

The context menu duplicate handler (handleContextDuplicate in workflow.tsx) correctly validates using validateTriggerPaste, but this direct duplication path does not.

Fix

Add validation before duplicating:

    const handleDuplicateBlock = useCallback(() => {
      const sourceBlock = blocks[blockId]
      if (!sourceBlock) return

      // Validate single-instance constraints
      const validation = validateTriggerPaste([{ type: sourceBlock.type }], blocks, 'duplicate')
      if (!validation.isValid) {
        addNotification({
          level: 'error',
          message: validation.message!,
          workflowId: activeWorkflowId || undefined,
        })
        return
      }

      const newId = crypto.randomUUID()
      const newName = getUniqueBlockName(sourceBlock.name, blocks)
      const subBlockValues = subBlockStore.workflowValues[activeWorkflowId || '']?.[blockId] || {}

      const { block, subBlockValues: filteredValues } = prepareDuplicateBlockState({
        sourceBlock,
        newId,
        newName,
        positionOffset: DEFAULT_DUPLICATE_OFFSET,
        subBlockValues,
      })

      collaborativeBatchAddBlocks([block], [], {}, {}, { [newId]: filteredValues })
    }, [
      blockId,
      blocks,
      activeWorkflowId,
      subBlockStore.workflowValues,
      collaborativeBatchAddBlocks,
      addNotification,
    ])

You'll also need to import validateTriggerPaste and addNotification at the top of the file.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/action-bar/action-bar.tsx
Line: 44:67

Comment:
The `handleDuplicateBlock` function does not validate single-instance block constraints before duplicating. This means a user can click the duplicate button on a Response block's action bar and create multiple Response blocks, bypassing the singleton constraint that this PR is supposed to enforce.

The context menu duplicate handler (`handleContextDuplicate` in workflow.tsx) correctly validates using `validateTriggerPaste`, but this direct duplication path does not.

## Fix

Add validation before duplicating:

```suggestion
    const handleDuplicateBlock = useCallback(() => {
      const sourceBlock = blocks[blockId]
      if (!sourceBlock) return

      // Validate single-instance constraints
      const validation = validateTriggerPaste([{ type: sourceBlock.type }], blocks, 'duplicate')
      if (!validation.isValid) {
        addNotification({
          level: 'error',
          message: validation.message!,
          workflowId: activeWorkflowId || undefined,
        })
        return
      }

      const newId = crypto.randomUUID()
      const newName = getUniqueBlockName(sourceBlock.name, blocks)
      const subBlockValues = subBlockStore.workflowValues[activeWorkflowId || '']?.[blockId] || {}

      const { block, subBlockValues: filteredValues } = prepareDuplicateBlockState({
        sourceBlock,
        newId,
        newName,
        positionOffset: DEFAULT_DUPLICATE_OFFSET,
        subBlockValues,
      })

      collaborativeBatchAddBlocks([block], [], {}, {}, { [newId]: filteredValues })
    }, [
      blockId,
      blocks,
      activeWorkflowId,
      subBlockStore.workflowValues,
      collaborativeBatchAddBlocks,
      addNotification,
    ])
```

You'll also need to import `validateTriggerPaste` and `addNotification` at the top of the file.

How can I resolve this? If you propose a fix, please make it concise.

@icecrasher321
Copy link
Collaborator Author

@greptile

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 48 to 52
const singleInstanceIssue = TriggerUtils.getSingleInstanceBlockIssue(existingBlocks, block.type)
if (singleInstanceIssue) {
const actionText = action === 'paste' ? 'paste' : 'duplicate'
const message = `A workflow can only have one ${singleInstanceIssue.blockName} block. ${action === 'paste' ? 'Please remove the existing one before pasting.' : `Cannot ${actionText}.`}`
return { isValid: false, message }
Copy link
Contributor

Choose a reason for hiding this comment

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

Style: Inconsistent error message formatting

The error message for single-instance blocks uses different formatting than trigger blocks:

Single-instance blocks (line 51):

  • Paste: "...Please remove the existing one before pasting."
  • Duplicate: "...Cannot duplicate."

Trigger blocks (line 43):

  • Paste: "...Please remove the existing one before pasting."
  • Duplicate: "...Cannot duplicate."

While functionally equivalent, for better consistency with the existing trigger error message pattern on line 43, consider using the same format:

Suggested change
const singleInstanceIssue = TriggerUtils.getSingleInstanceBlockIssue(existingBlocks, block.type)
if (singleInstanceIssue) {
const actionText = action === 'paste' ? 'paste' : 'duplicate'
const message = `A workflow can only have one ${singleInstanceIssue.blockName} block. ${action === 'paste' ? 'Please remove the existing one before pasting.' : `Cannot ${actionText}.`}`
return { isValid: false, message }
const singleInstanceIssue = TriggerUtils.getSingleInstanceBlockIssue(existingBlocks, block.type)
if (singleInstanceIssue) {
const actionText = action === 'paste' ? 'paste' : 'duplicate'
const message = `A workflow can only have one ${singleInstanceIssue.blockName} block. ${action === 'paste' ? 'Please remove the existing one before pasting.' : 'Cannot duplicate.'}`
return { isValid: false, message }
}

This maintains identical phrasing to the trigger validation above for a more cohesive user experience.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-canvas-helpers.ts
Line: 48:52

Comment:
## Style: Inconsistent error message formatting

The error message for single-instance blocks uses different formatting than trigger blocks:

**Single-instance blocks (line 51):**
- Paste: "...Please remove the existing one before pasting."
- Duplicate: "...Cannot duplicate."

**Trigger blocks (line 43):**
- Paste: "...Please remove the existing one before pasting."
- Duplicate: "...Cannot duplicate."

While functionally equivalent, for better consistency with the existing trigger error message pattern on line 43, consider using the same format:

```suggestion
    const singleInstanceIssue = TriggerUtils.getSingleInstanceBlockIssue(existingBlocks, block.type)
    if (singleInstanceIssue) {
      const actionText = action === 'paste' ? 'paste' : 'duplicate'
      const message = `A workflow can only have one ${singleInstanceIssue.blockName} block. ${action === 'paste' ? 'Please remove the existing one before pasting.' : 'Cannot duplicate.'}`
      return { isValid: false, message }
    }
```

This maintains identical phrasing to the trigger validation above for a more cohesive user experience.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 10, 2026

Additional Comments (1)

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/action-bar/action-bar.tsx

Critical: Single-instance constraint bypass in handleDuplicateBlock

The handleDuplicateBlock function does not validate single-instance block constraints before duplicating. While the duplicate button is now hidden for Response blocks (line 143), this function can still be invoked programmatically or through other UI paths, allowing users to bypass the singleton constraint.

Impact: Users could potentially duplicate Response blocks through:

  1. Programmatic invocation
  2. Future UI changes that expose this path
  3. Browser dev tools or extensions

Comparison with other duplication paths:

  • ✅ Context menu duplicate (handleContextDuplicate in workflow.tsx) validates via validateTriggerPaste
  • ✅ Keyboard paste (Cmd+V in workflow.tsx) validates via validateTriggerPaste
  • ✅ Context menu paste validates via validateTriggerPaste
  • ❌ Action bar duplicate does NOT validate

Fix Required:
Add validation before duplicating:

    const handleDuplicateBlock = useCallback(() => {
      const sourceBlock = blocks[blockId]
      if (!sourceBlock) return

      // Validate single-instance constraints
      const validation = validateTriggerPaste([{ type: sourceBlock.type }], blocks, 'duplicate')
      if (!validation.isValid) {
        addNotification({
          level: 'error',
          message: validation.message!,
          workflowId: activeWorkflowId || undefined,
        })
        return
      }

      const newId = crypto.randomUUID()

You'll need to import:

  • validateTriggerPaste from '@/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-canvas-helpers'
  • useNotificationStore from '@/stores/notifications'
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/action-bar/action-bar.tsx
Line: 44:67

Comment:
## Critical: Single-instance constraint bypass in handleDuplicateBlock

The `handleDuplicateBlock` function does not validate single-instance block constraints before duplicating. While the duplicate button is now hidden for Response blocks (line 143), this function can still be invoked programmatically or through other UI paths, allowing users to bypass the singleton constraint.

**Impact:** Users could potentially duplicate Response blocks through:
1. Programmatic invocation
2. Future UI changes that expose this path
3. Browser dev tools or extensions

**Comparison with other duplication paths:**
- ✅ Context menu duplicate (`handleContextDuplicate` in workflow.tsx) validates via `validateTriggerPaste`
- ✅ Keyboard paste (Cmd+V in workflow.tsx) validates via `validateTriggerPaste`
- ✅ Context menu paste validates via `validateTriggerPaste`
- ❌ Action bar duplicate does NOT validate

**Fix Required:**
Add validation before duplicating:

```suggestion
    const handleDuplicateBlock = useCallback(() => {
      const sourceBlock = blocks[blockId]
      if (!sourceBlock) return

      // Validate single-instance constraints
      const validation = validateTriggerPaste([{ type: sourceBlock.type }], blocks, 'duplicate')
      if (!validation.isValid) {
        addNotification({
          level: 'error',
          message: validation.message!,
          workflowId: activeWorkflowId || undefined,
        })
        return
      }

      const newId = crypto.randomUUID()
```

You'll need to import:
- `validateTriggerPaste` from `'@/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-canvas-helpers'`
- `useNotificationStore` from `'@/stores/notifications'`

How can I resolve this? If you propose a fix, please make it concise.

@icecrasher321 icecrasher321 merged commit 9a16e7c into staging Jan 10, 2026
6 checks passed
@waleedlatif1 waleedlatif1 deleted the improvement/single-response-block branch January 12, 2026 04:08
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.

2 participants