Skip to content

Commit f14ba49

Browse files
committed
fix(flow): validate paths and correct JSON generation
1 parent e7fa06f commit f14ba49

3 files changed

Lines changed: 48 additions & 12 deletions

File tree

‎apps/sim/blocks/blocks/flow.ts‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ export const FlowBlock: BlockConfig = {
638638
language: 'json',
639639
wandConfig: {
640640
enabled: true,
641-
generationType: 'json-object',
641+
generationType: 'json-array',
642642
prompt:
643643
'Generate valid JSON for Flow entities: Array of 1\u2013100 entities: [{entityType, name, id?}]. IDs may be omitted for server generation. Return only JSON. Use only supplied category IDs and field keys.',
644644
placeholder: 'Describe the entities to generate',
@@ -710,9 +710,8 @@ export const FlowBlock: BlockConfig = {
710710
language: 'json',
711711
wandConfig: {
712712
enabled: true,
713-
generationType: 'json-object',
714713
prompt:
715-
'Generate valid JSON for Flow value: Field value as JSON, matching the data model: string, number, boolean, array, object, or null. Return only JSON. Use only supplied category IDs and field keys.',
714+
'Generate valid JSON for Flow value: Field value as JSON, matching the data model: string, number, boolean, array, object, or null. Use only supplied category IDs and field keys. Return ONLY one raw JSON value, without markdown fences or prose.',
716715
placeholder: 'Describe the value to generate',
717716
},
718717
},
@@ -732,7 +731,7 @@ export const FlowBlock: BlockConfig = {
732731
language: 'json',
733732
wandConfig: {
734733
enabled: true,
735-
generationType: 'json-object',
734+
generationType: 'json-array',
736735
prompt:
737736
'Generate valid JSON for Flow updates: Array of 1\u2013100 field updates: [{entityId, key, value}]. Return only JSON. Use only supplied category IDs and field keys.',
738737
placeholder: 'Describe the updates to generate',

‎apps/sim/lib/sim-search/live/flow.test.ts‎

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ const entity = {
1515
links: { inbound: [], outbound: [], crosslinks: [], inboundOrder: {} },
1616
updatedAt: '2026-10-01T12:00:00Z',
1717
}
18-
const input: NativeSearchInput = {
18+
const input = {
1919
query: 'temperature',
2020
limit: 10,
2121
scopes: [],
2222
native: { provider: 'flow', query: 'temperature', project: PROJECT_ID },
23-
}
23+
} satisfies NativeSearchInput
2424

2525
function client(json: NativeClient['json']): NativeClient {
2626
return {
@@ -36,6 +36,38 @@ function client(json: NativeClient['json']): NativeClient {
3636

3737
/** Failure modes: stale hits bypassing current reads, missing project scope, malformed entity states, and wrong entity identities. */
3838
describe('Flow live entity boundary', () => {
39+
it.each([
40+
{ location: 'search project', id: '.' },
41+
{ location: 'search project', id: '..' },
42+
{ location: 'read project', id: '.' },
43+
{ location: 'read project', id: '..' },
44+
{ location: 'read entity', id: '.' },
45+
{ location: 'read entity', id: '..' },
46+
])(
47+
'rejects a dot segment in $location ($id) before requesting a different endpoint',
48+
async ({ location, id }) => {
49+
const requests: string[] = []
50+
const api = client(async (path) => {
51+
requests.push(path)
52+
return []
53+
})
54+
await expect(
55+
location === 'search project'
56+
? searchFlow(
57+
api,
58+
{ ...input, native: { ...input.native, provider: 'flow', project: id } },
59+
ORIGIN
60+
)
61+
: readFlow(
62+
api,
63+
{ ...reference, ...(location === 'read project' ? { container: id } : { id }) },
64+
ORIGIN
65+
)
66+
).rejects.toThrow('path traversal')
67+
expect(requests).toEqual([])
68+
}
69+
)
70+
3971
it('reads current project-bound content rather than returning stale search previews', async () => {
4072
const api = client(async (path) => {
4173
if (path === `/project/${PROJECT_ID}/entities/search`) {

‎apps/sim/lib/sim-search/live/flow.ts‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { isRecordLike } from '@sim/utils/object'
22
import { truncate } from '@sim/utils/string'
33
import { nativeText } from '@/lib/sim-search/live/dates'
4-
import { array, NativeSearchError, object, segment, string } from '@/lib/sim-search/live/http'
4+
import { array, NativeSearchError, object, string } from '@/lib/sim-search/live/http'
55
import { collectNativePages } from '@/lib/sim-search/live/pages'
66
import type {
77
NativeClient,
@@ -10,14 +10,15 @@ import type {
1010
NativeSearchInput,
1111
} from '@/lib/sim-search/live/types'
1212
import { htmlToPlainText } from '@/connectors/utils'
13+
import { safeUrlPathSegment } from '@/tools/url-path'
1314

1415
const MAX_PROJECTS = 6
1516
const MAX_ITEM_TEXT = 120_000
1617

1718
function itemDocument(
1819
row: Record<string, unknown>,
1920
origin: string,
20-
project?: string,
21+
project: string,
2122
humanId?: string
2223
): NativeDocument | undefined {
2324
const id = string(row.id)
@@ -42,7 +43,10 @@ function itemDocument(
4243
container: project,
4344
kind: string(row.type) || 'item',
4445
title: [humanId, string(values.name) || `Item ${id}`].filter(Boolean).join(' · '),
45-
url: new URL(`/project/${segment(project ?? '')}/entities/${segment(id)}`, origin).toString(),
46+
url: new URL(
47+
`/project/${safeUrlPathSegment(project, 'projectId')}/entities/${safeUrlPathSegment(id, 'entityId')}`,
48+
origin
49+
).toString(),
4650
content: truncate(
4751
[
4852
...fields,
@@ -65,7 +69,8 @@ async function searchProject(
6569
project: string
6670
): Promise<NativePage> {
6771
const limit = Math.min(input.limit, 100)
68-
const response = await client.json(`/project/${segment(project)}/entities/search`, {
72+
const projectPath = safeUrlPathSegment(project, 'projectId')
73+
const response = await client.json(`/project/${projectPath}/entities/search`, {
6974
query: {
7075
query: nativeText(input),
7176
branch_id: 'master',
@@ -78,7 +83,7 @@ async function searchProject(
7883
const ids = [...new Set(hits.map((row) => string(row.entity_id)).filter(Boolean))]
7984
if (!ids.length) return { documents: [], partial: response.length > 0 }
8085
const details = await client.json(
81-
`/project/${segment(project)}/entities/${ids.map(segment).join(',')}`,
86+
`/project/${projectPath}/entities/${ids.map((id) => safeUrlPathSegment(id, 'entityId')).join(',')}`,
8287
{ query: { branch_id: 'master', include_metadata: 'true' } }
8388
)
8489
if (!Array.isArray(details))
@@ -140,7 +145,7 @@ export async function readFlow(
140145
if (!origin || !reference.id || reference.id.length > 256 || !reference.container)
141146
throw new NativeSearchError('unavailable', 'Invalid Flow item reference.')
142147
const response = await client.json(
143-
`/project/${segment(reference.container)}/entities/${segment(reference.id)}`,
148+
`/project/${safeUrlPathSegment(reference.container, 'projectId')}/entities/${safeUrlPathSegment(reference.id, 'entityId')}`,
144149
{ query: { branch_id: 'master', include_metadata: 'true' } }
145150
)
146151
if (!Array.isArray(response))

0 commit comments

Comments
 (0)