Skip to content
Merged
Binary file modified apps/docs/public/static/introduction.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@ import type {
} from '@/stores/modals/search/types'
import { useSettingsModalStore } from '@/stores/modals/settings/store'

function customFilter(value: string, search: string): number {
const searchLower = search.toLowerCase()
const valueLower = value.toLowerCase()

if (valueLower === searchLower) return 1
if (valueLower.startsWith(searchLower)) return 0.9
if (valueLower.includes(searchLower)) return 0.7

const searchWords = searchLower.split(/\s+/).filter(Boolean)
if (searchWords.length > 1) {
const allWordsMatch = searchWords.every((word) => valueLower.includes(word))
if (allWordsMatch) return 0.5
}

return 0
}
Comment thread
waleedlatif1 marked this conversation as resolved.
Comment thread
waleedlatif1 marked this conversation as resolved.
Comment thread
waleedlatif1 marked this conversation as resolved.

interface SearchModalProps {
open: boolean
onOpenChange: (open: boolean) => void
Expand Down Expand Up @@ -62,7 +79,6 @@ export function SearchModal({
const workspaceId = params.workspaceId as string
const brand = useBrandConfig()
const inputRef = useRef<HTMLInputElement>(null)
const [search, setSearch] = useState('')
const [mounted, setMounted] = useState(false)
const openSettingsModal = useSettingsModalStore((state) => state.openModal)

Expand Down Expand Up @@ -110,23 +126,26 @@ export function SearchModal({
name: 'Settings',
icon: Settings,
onClick: openSettingsModal,
shortcut: '⌘,',
},
],
[workspaceId, openHelpModal, openSettingsModal]
)

useEffect(() => {
if (open) {
setSearch('')
requestAnimationFrame(() => {
inputRef.current?.focus()
})
if (open && inputRef.current) {
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype,
'value'
)?.set
if (nativeInputValueSetter) {
nativeInputValueSetter.call(inputRef.current, '')
inputRef.current.dispatchEvent(new Event('input', { bubbles: true }))
}
inputRef.current.focus()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Overly complex DOM manipulation to clear input

Low Severity

The new approach uses Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set to clear the search input, which is more complex than the previous controlled React state pattern (setSearch('')). This DOM manipulation technique relies on browser implementation details and is harder to understand and maintain than standard React state management.

Fix in Cursor Fix in Web

}
}, [open])

const handleSearchChange = useCallback((value: string) => {
setSearch(value)
const handleSearchChange = useCallback(() => {
requestAnimationFrame(() => {
const list = document.querySelector('[cmdk-list]')
if (list) {
Expand Down Expand Up @@ -228,28 +247,6 @@ export function SearchModal({
const showToolOperations = isOnWorkflowPage && toolOperations.length > 0
const showDocs = isOnWorkflowPage && docs.length > 0

const customFilter = useCallback((value: string, search: string, keywords?: string[]) => {
const searchLower = search.toLowerCase()
const valueLower = value.toLowerCase()

if (valueLower === searchLower) return 1
if (valueLower.startsWith(searchLower)) return 0.8
if (valueLower.includes(searchLower)) return 0.6

const searchWords = searchLower.split(/\s+/).filter(Boolean)
const allWordsMatch = searchWords.every((word) => valueLower.includes(word))
if (allWordsMatch && searchWords.length > 0) return 0.4

if (keywords?.length) {
const keywordsLower = keywords.join(' ').toLowerCase()
if (keywordsLower.includes(searchLower)) return 0.3
const keywordWordsMatch = searchWords.every((word) => keywordsLower.includes(word))
if (keywordWordsMatch && searchWords.length > 0) return 0.2
}

return 0
}, [])

if (!mounted) return null

return createPortal(
Expand Down Expand Up @@ -278,7 +275,6 @@ export function SearchModal({
<Command label='Search' filter={customFilter}>
<Command.Input
ref={inputRef}
value={search}
autoFocus
onValueChange={handleSearchChange}
placeholder='Search anything...'
Expand All @@ -295,7 +291,6 @@ export function SearchModal({
<CommandItem
key={block.id}
value={`${block.name} block-${block.id}`}
keywords={[block.description]}
onSelect={() => handleBlockSelect(block, 'block')}
icon={block.icon}
bgColor={block.bgColor}
Expand All @@ -313,7 +308,6 @@ export function SearchModal({
<CommandItem
key={tool.id}
value={`${tool.name} tool-${tool.id}`}
keywords={[tool.description]}
onSelect={() => handleBlockSelect(tool, 'tool')}
icon={tool.icon}
bgColor={tool.bgColor}
Expand All @@ -331,7 +325,6 @@ export function SearchModal({
<CommandItem
key={trigger.id}
value={`${trigger.name} trigger-${trigger.id}`}
keywords={[trigger.description]}
onSelect={() => handleBlockSelect(trigger, 'trigger')}
icon={trigger.icon}
bgColor={trigger.bgColor}
Expand Down Expand Up @@ -371,7 +364,6 @@ export function SearchModal({
<CommandItem
key={op.id}
value={`${op.searchValue} operation-${op.id}`}
keywords={op.keywords}
onSelect={() => handleToolOperationSelect(op)}
icon={op.icon}
bgColor={op.bgColor}
Expand Down Expand Up @@ -458,7 +450,6 @@ const groupHeadingClassName =

interface CommandItemProps {
value: string
keywords?: string[]
onSelect: () => void
icon: React.ComponentType<{ className?: string }>
bgColor: string
Expand All @@ -468,7 +459,6 @@ interface CommandItemProps {

function CommandItem({
value,
keywords,
onSelect,
icon: Icon,
bgColor,
Expand All @@ -478,7 +468,6 @@ function CommandItem({
return (
<Command.Item
value={value}
keywords={keywords}
onSelect={onSelect}
className='group flex h-[28px] w-full cursor-pointer items-center gap-[8px] rounded-[6px] px-[10px] text-left text-[15px] aria-selected:bg-[var(--border)] aria-selected:shadow-sm data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50'
>
Expand Down
10 changes: 10 additions & 0 deletions apps/sim/blocks/blocks/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ Example:
generationType: 'json-object',
},
},
{
id: 'timeout',
title: 'Timeout (ms)',
type: 'short-input',
placeholder: '300000',
description:
'Request timeout in milliseconds (default: 300000 = 5 minutes, max: 600000 = 10 minutes)',
mode: 'advanced',
},
],
tools: {
access: ['http_request'],
Expand All @@ -90,6 +99,7 @@ Example:
headers: { type: 'json', description: 'Request headers' },
body: { type: 'json', description: 'Request body data' },
params: { type: 'json', description: 'URL query parameters' },
timeout: { type: 'number', description: 'Request timeout in milliseconds' },
},
outputs: {
data: { type: 'json', description: 'API response data (JSON, text, or other formats)' },
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/lib/core/security/input-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -931,7 +931,7 @@ export async function secureFetchWithPinnedIP(
method: options.method || 'GET',
headers: sanitizedHeaders,
agent,
timeout: options.timeout || 30000,
timeout: options.timeout || 300000, // Default 5 minutes
Comment thread
waleedlatif1 marked this conversation as resolved.
}

const protocol = isHttps ? https : http
Expand Down
5 changes: 5 additions & 0 deletions apps/sim/tools/http/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ export const requestTool: ToolConfig<RequestParams, RequestResponse> = {
visibility: 'user-or-llm',
description: 'Form data to send (will set appropriate Content-Type)',
},
timeout: {
type: 'number',
visibility: 'user-only',
description: 'Request timeout in milliseconds (default: 300000 = 5 minutes)',
},
},

request: {
Expand Down
1 change: 1 addition & 0 deletions apps/sim/tools/http/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface RequestParams {
params?: TableRow[]
pathParams?: Record<string, string>
formData?: Record<string, string | Blob>
timeout?: number
}

export interface RequestResponse extends ToolResponse {
Expand Down
28 changes: 23 additions & 5 deletions apps/sim/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -625,11 +625,28 @@ async function executeToolRequest(
let response: Response

if (isInternalRoute) {
response = await fetch(fullUrl, {
method: requestParams.method,
headers: headers,
body: requestParams.body,
})
// Set up AbortController for timeout support on internal routes
const controller = new AbortController()
const timeoutId = requestParams.timeout
? setTimeout(() => controller.abort(), requestParams.timeout)
: undefined
Comment thread
waleedlatif1 marked this conversation as resolved.
Outdated

try {
response = await fetch(fullUrl, {
method: requestParams.method,
headers: headers,
body: requestParams.body,
signal: controller.signal,
})
} catch (error) {
// Convert AbortError to a timeout error message
if (error instanceof Error && error.name === 'AbortError') {
throw new Error(`Request timed out after ${requestParams.timeout}ms`)
}
throw error
} finally {
if (timeoutId) clearTimeout(timeoutId)
}
} else {
const urlValidation = await validateUrlWithDNS(fullUrl, 'toolUrl')
if (!urlValidation.isValid) {
Expand All @@ -640,6 +657,7 @@ async function executeToolRequest(
method: requestParams.method,
headers: headersRecord,
body: requestParams.body ?? undefined,
timeout: requestParams.timeout,
})

const responseHeaders = new Headers(secureResponse.headers.toRecord())
Expand Down
10 changes: 9 additions & 1 deletion apps/sim/tools/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ interface RequestParams {
method: string
headers: Record<string, string>
body?: string
timeout?: number
}

/**
Expand Down Expand Up @@ -122,7 +123,14 @@ export function formatRequestParams(tool: ToolConfig, params: Record<string, any
}
}

return { url, method, headers, body }
// Get timeout from params (if specified) and ensure it's a valid positive number
// The short-input subBlock returns a string, so we need to convert it
const rawTimeout = params.timeout
const timeout = rawTimeout != null ? Number(rawTimeout) : undefined
const validTimeout =
timeout != null && !Number.isNaN(timeout) && timeout > 0 ? timeout : undefined
Comment thread
waleedlatif1 marked this conversation as resolved.
Outdated
Comment thread
waleedlatif1 marked this conversation as resolved.
Outdated

return { url, method, headers, body, timeout: validTimeout }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Timeout parameter incorrectly applied to all tools

Medium Severity

The new timeout handling in formatRequestParams extracts params.timeout for ALL tools and uses it as the HTTP request timeout. However, tools like firecrawl, apify, and elasticsearch have their own timeout parameter that serves a different purpose—it's an API-level parameter (e.g., how long firecrawl should spend scraping), not an HTTP request timeout. When a user sets timeout=60000 for firecrawl intending 60 seconds of scrape time, the HTTP request will now also abort after 60 seconds, potentially failing before the API completes its work and returns a response.

Additional Locations (1)

Fix in Cursor Fix in Web

}

/**
Expand Down