Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b7e377e
v0.5.91: docs i18n, turborepo upgrade
waleedlatif1 Feb 16, 2026
da46a38
v0.5.92: shortlinks, copilot scrolling stickiness, pagination
waleedlatif1 Feb 17, 2026
fdca736
v0.5.93: NextJS config changes, MCP and Blocks whitelisting, copilot …
waleedlatif1 Feb 18, 2026
15ace5e
v0.5.94: vercel integration, folder insertion, migrated tracking redi…
waleedlatif1 Feb 19, 2026
67aa4bb
v0.5.95: gemini 3.1 pro, cloudflare, dataverse, revenuecat, redis, up…
waleedlatif1 Feb 20, 2026
34d92fa
v0.5.96: sim oauth provider, slack ephemeral message tool and blockki…
waleedlatif1 Feb 21, 2026
115f04e
v0.5.97: oidc discovery for copilot mcp
waleedlatif1 Feb 21, 2026
0d86ea0
v0.5.98: change detection improvements, rate limit and code execution…
waleedlatif1 Feb 22, 2026
af59234
v0.5.99: local dev improvements, live workflow logs in terminal
waleedlatif1 Feb 23, 2026
67f8a68
v0.5.100: multiple credentials, 40% speedup, gong, attio, audit log i…
waleedlatif1 Feb 25, 2026
4fd0989
v0.5.101: circular dependency mitigation, confluence enhancements, go…
waleedlatif1 Feb 26, 2026
0d2e6ff
v0.5.102: new integrations, new tools, ci speedups, memory leak instr…
waleedlatif1 Feb 28, 2026
e07e3c3
v0.5.103: memory util instrumentation, API docs, amplitude, google pa…
waleedlatif1 Mar 2, 2026
f1ec5fe
v0.5.104: memory improvements, nested subflows, careers page redirect…
waleedlatif1 Mar 4, 2026
70c36cb
v0.5.105: slack remove reaction, nested subflow locks fix, servicenow…
waleedlatif1 Mar 5, 2026
3ce9475
v0.5.106: condition block and legacy kbs fixes, GPT 5.4
icecrasher321 Mar 6, 2026
6586c5c
v0.5.107: new reddit, slack tools
waleedlatif1 Mar 6, 2026
8c0a2e0
v0.5.108: workflow input params in agent tools, bun upgrade, dropdown…
icecrasher321 Mar 7, 2026
ecd3536
v0.5.109: obsidian and evernote integrations, slack fixes, remove mem…
waleedlatif1 Mar 9, 2026
1c2c2c6
v0.5.110: webhook execution speedups, SSRF patches
waleedlatif1 Mar 11, 2026
36612ae
v0.5.111: non-polling webhook execs off trigger.dev, gmail subject he…
icecrasher321 Mar 12, 2026
e9bdc57
v0.5.112: trace spans improvements, fathom integration, jira fixes, c…
waleedlatif1 Mar 12, 2026
4c12914
v0.5.113: jira, ashby, google ads, grain updates
icecrasher321 Mar 13, 2026
84d6fdc
v0.6: mothership, tables, connectors
waleedlatif1 Mar 17, 2026
4f3bc37
v0.6.1: added better auth admin plugin
waleedlatif1 Mar 17, 2026
4bd0731
v0.6.2: mothership stability, chat iframe embedding, KB upserts, new …
waleedlatif1 Mar 18, 2026
30f2d1a
v0.6.3: hubspot integration, kb block improvements
waleedlatif1 Mar 18, 2026
ff7b5b5
v0.6.4: subflows, docusign, ashby new tools, box, workday, billing bu…
waleedlatif1 Mar 19, 2026
9fcd02f
v0.6.5: email validation, integrations page, mothership and custom to…
waleedlatif1 Mar 19, 2026
1731a4d
v0.6.6: landing improvements, styling consistency, mothership table r…
waleedlatif1 Mar 20, 2026
19442f1
v0.6.7: kb improvements, edge z index fix, captcha, new trust center,…
waleedlatif1 Mar 21, 2026
c78c870
v0.6.8: mothership tool loop
Sg312 Mar 24, 2026
ed9a71f
v0.6.9: general ux improvements for tables, mothership
icecrasher321 Mar 25, 2026
7b572f1
v0.6.10: tour fix, connectors reliability improvements, tooltip gif f…
waleedlatif1 Mar 25, 2026
6bebbc5
v0.6.11: billing fixes, rippling, hubspot, UI improvements, demo modal
waleedlatif1 Mar 26, 2026
ca87d7c
v0.6.12: billing, blogs UI
waleedlatif1 Mar 26, 2026
aa0060e
fix(telegram): allow plain URLs in photo/video/audio/animation fields
cyphercodes Mar 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions apps/sim/blocks/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { describe, expect, it } from 'vitest'
import { normalizeFileInput } from './utils'

describe('normalizeFileInput', () => {
describe('URL string handling', () => {
it('should return URL string as-is for single option', () => {
const url = 'https://example.com/photo.jpg'
const result = normalizeFileInput(url, { single: true })
expect(result).toBe(url)
})

it('should return URL in array for non-single option', () => {
const url = 'https://example.com/photo.jpg'
const result = normalizeFileInput(url, { single: false })
expect(result).toEqual([url])
})

it('should handle HTTP URLs', () => {
const url = 'http://example.com/photo.jpg'
const result = normalizeFileInput(url, { single: true })
expect(result).toBe(url)
})

it('should trim whitespace from URLs', () => {
const url = ' https://example.com/photo.jpg '
const result = normalizeFileInput(url, { single: true })
expect(result).toBe('https://example.com/photo.jpg')
})

it('should return undefined for non-URL strings that fail JSON parse', () => {
const notAUrl = 'just some text'
const result = normalizeFileInput(notAUrl, { single: true })
expect(result).toBeUndefined()
})
})

describe('JSON string handling', () => {
it('should parse JSON string to object for single option', () => {
const fileObj = { name: 'test.jpg', url: 'https://example.com/test.jpg' }
const result = normalizeFileInput(JSON.stringify(fileObj), { single: true })
expect(result).toEqual(fileObj)
})

it('should parse JSON string array for non-single option', () => {
const fileArray = [
{ name: 'test1.jpg', url: 'https://example.com/test1.jpg' },
{ name: 'test2.jpg', url: 'https://example.com/test2.jpg' },
]
const result = normalizeFileInput(JSON.stringify(fileArray), { single: false })
expect(result).toEqual(fileArray)
})
})

describe('Object and array handling', () => {
it('should return single object wrapped in array for non-single option', () => {
const fileObj = { name: 'test.jpg', url: 'https://example.com/test.jpg' }
const result = normalizeFileInput(fileObj, { single: false })
expect(result).toEqual([fileObj])
})

it('should return single object as-is for single option', () => {
const fileObj = { name: 'test.jpg', url: 'https://example.com/test.jpg' }
const result = normalizeFileInput(fileObj, { single: true })
expect(result).toEqual(fileObj)
})

it('should return array as-is for non-single option', () => {
const fileArray = [
{ name: 'test1.jpg', url: 'https://example.com/test1.jpg' },
{ name: 'test2.jpg', url: 'https://example.com/test2.jpg' },
]
const result = normalizeFileInput(fileArray, { single: false })
expect(result).toEqual(fileArray)
})
})

describe('Edge cases', () => {
it('should return undefined for null', () => {
const result = normalizeFileInput(null, { single: true })
expect(result).toBeUndefined()
})

it('should return undefined for undefined', () => {
const result = normalizeFileInput(undefined, { single: true })
expect(result).toBeUndefined()
})

it('should return undefined for empty string', () => {
const result = normalizeFileInput('', { single: true })
expect(result).toBeUndefined()
})

it('should throw error for multiple files when single is true', () => {
const fileArray = [
{ name: 'test1.jpg', url: 'https://example.com/test1.jpg' },
{ name: 'test2.jpg', url: 'https://example.com/test2.jpg' },
]
expect(() => normalizeFileInput(fileArray, { single: true })).toThrow(
'File reference must be a single file'
)
})
})
})
12 changes: 9 additions & 3 deletions apps/sim/blocks/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,18 +326,24 @@ const DEFAULT_MULTIPLE_FILES_ERROR =
export function normalizeFileInput(
fileParam: unknown,
options: { single: true; errorMessage?: string }
): object | undefined
): object | string | undefined
export function normalizeFileInput(
fileParam: unknown,
options?: { single?: false }
): object[] | undefined
): object[] | string | undefined
Comment on lines 330 to +333
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.

P1 Non-single overload return type doesn't include string[]

When a URL is passed with single: false (or no options), the implementation returns [trimmed] — a string[]. However, the non-single overload declares object[] | string | undefined as its return type. TypeScript doesn't flag this because string[] is assignable to the implementation's object variant (arrays are objects), but it means callers receive a typed promise of object[] while actually getting string[] at runtime.

Any caller that iterates over the returned array and accesses file-object properties (.name, .url, etc.) would silently get undefined instead of failing loudly with a type error. The telegram_send_document path (normalizeFileInput(params.files)) is one such non-single caller — a URL string passed as params.files would return [url] typed as object[] | string | undefined, which could confuse downstream handlers.

The overload should reflect the actual runtime shape:

export function normalizeFileInput(
  fileParam: unknown,
  options?: { single?: false }
): (object | string)[] | string | undefined

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Non-single overload return type doesn't match actual return

Medium Severity

When single is falsy, the URL path returns [trimmed] which is string[], but the non-single overload declares the return type as object[] | string | undefined. Since string is a primitive and not assignable to object, string[] is neither object[] nor string. A caller using this overload that checks typeof result === 'string' to detect a URL will never match (because it's actually an array), and a caller narrowing to object[] will get string elements incorrectly typed as object, potentially leading to runtime property-access errors on the array items.

Additional Locations (1)
Fix in Cursor Fix in Web

export function normalizeFileInput(
fileParam: unknown,
options?: { single?: boolean; errorMessage?: string }
): object | object[] | undefined {
): object | object[] | string | undefined {
if (!fileParam) return undefined

if (typeof fileParam === 'string') {
// Check if it's a plain URL (http/https) - return as-is for tools that accept URLs
const trimmed = fileParam.trim()
if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
return options?.single ? trimmed : [trimmed]
}
Comment on lines +342 to +345
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.

P1 URL passthrough affects all normalizeFileInput callers, not just Telegram

This change is correct for Telegram media blocks, but normalizeFileInput is used by ~15 other blocks (Box, Confluence, Google Drive, Fireflies, Jira, Linear, etc.). Previously, passing a URL string to any of these would silently return undefined, causing a validation error. Now the URL string is returned to the caller.

For upload-oriented blocks (e.g. Box's upload_file, Confluence attachments), the caller assigns the result directly to params.file / baseParams.file and passes it to the tool handler. A URL string where those handlers expect a file object with { name, url, size } will likely cause a confusing downstream error instead of the current clear validation failure.

Consider scoping this change to Telegram-only, for example by adding an allowUrl?: boolean option to normalizeFileInput, so the URL shortcut doesn't silently change behaviour for other integrations:

export function normalizeFileInput(
  fileParam: unknown,
  options: { single: true; allowUrl?: boolean; errorMessage?: string }
): object | string | undefined


try {
fileParam = JSON.parse(fileParam)
} catch {
Expand Down