@@ -2,7 +2,6 @@ import type { Edge } from 'reactflow'
22import { sanitizeWorkflowForSharing } from '@/lib/workflows/credentials/credential-extractor'
33import type { BlockState , Loop , Parallel , WorkflowState } from '@/stores/workflows/workflow/types'
44import { generateLoopBlocks , generateParallelBlocks } from '@/stores/workflows/workflow/utils'
5- import { TRIGGER_PERSISTED_SUBBLOCK_IDS } from '@/triggers/constants'
65
76/**
87 * Sanitized workflow state for copilot (removes all UI-specific data)
@@ -65,41 +64,6 @@ export interface ExportWorkflowState {
6564 }
6665}
6766
68- /**
69- * Check if a subblock contains sensitive/secret data
70- */
71- function isSensitiveSubBlock ( key : string , subBlock : BlockState [ 'subBlocks' ] [ string ] ) : boolean {
72- if ( TRIGGER_PERSISTED_SUBBLOCK_IDS . includes ( key ) ) {
73- return false
74- }
75-
76- // Check if it's an OAuth input type
77- if ( subBlock . type === 'oauth-input' ) {
78- return true
79- }
80-
81- // Check if the field name suggests it contains sensitive data
82- const sensitivePattern = / c r e d e n t i a l | o a u t h | a p i [ _ - ] ? k e y | t o k e n | s e c r e t | a u t h | p a s s w o r d | b e a r e r / i
83- if ( sensitivePattern . test ( key ) ) {
84- return true
85- }
86-
87- // Check if the value itself looks like a secret (but not environment variable references)
88- if ( typeof subBlock . value === 'string' && subBlock . value . length > 0 ) {
89- // Don't sanitize environment variable references like {{VAR_NAME}}
90- if ( subBlock . value . startsWith ( '{{' ) && subBlock . value . endsWith ( '}}' ) ) {
91- return false
92- }
93-
94- // If it matches sensitive patterns in the value, it's likely a hardcoded secret
95- if ( sensitivePattern . test ( subBlock . value ) ) {
96- return true
97- }
98- }
99-
100- return false
101- }
102-
10367/**
10468 * Sanitize condition blocks by removing UI-specific metadata
10569 * Returns cleaned JSON string (not parsed array)
@@ -171,86 +135,62 @@ function sanitizeTools(tools: any[]): any[] {
171135}
172136
173137/**
174- * Sanitize subblocks by removing null values, secrets, and simplifying structure
138+ * Sort object keys recursively for consistent comparison
139+ */
140+ function sortKeysRecursively ( item : any ) : any {
141+ if ( Array . isArray ( item ) ) {
142+ return item . map ( sortKeysRecursively )
143+ }
144+ if ( item !== null && typeof item === 'object' ) {
145+ return Object . keys ( item )
146+ . sort ( )
147+ . reduce ( ( result : any , key : string ) => {
148+ result [ key ] = sortKeysRecursively ( item [ key ] )
149+ return result
150+ } , { } )
151+ }
152+ return item
153+ }
154+
155+ /**
156+ * Sanitize subblocks by removing null values and simplifying structure
175157 * Maps each subblock key directly to its value instead of the full object
176- * Note: responseFormat is kept as an object for better copilot understanding
177158 */
178159function sanitizeSubBlocks (
179160 subBlocks : BlockState [ 'subBlocks' ]
180161) : Record < string , string | number | string [ ] [ ] | object > {
181162 const sanitized : Record < string , string | number | string [ ] [ ] | object > = { }
182163
183164 Object . entries ( subBlocks ) . forEach ( ( [ key , subBlock ] ) => {
184- // Special handling for responseFormat - process BEFORE null check
185- // so we can detect when it's added/removed
165+ // Skip null/undefined values
166+ if ( subBlock . value === null || subBlock . value === undefined ) {
167+ return
168+ }
169+
170+ // Normalize responseFormat for consistent key ordering (important for training data)
186171 if ( key === 'responseFormat' ) {
187172 try {
188- // Handle null/undefined - skip if no value
189- if ( subBlock . value === null || subBlock . value === undefined ) {
190- return
191- }
192-
193173 let obj = subBlock . value
194174
195- // Handle string values - parse them first
175+ // Parse JSON string if needed
196176 if ( typeof subBlock . value === 'string' ) {
197177 const trimmed = subBlock . value . trim ( )
198178 if ( ! trimmed ) {
199- // Empty string - skip this field
200179 return
201180 }
202181 obj = JSON . parse ( trimmed )
203182 }
204183
205- // Handle object values - normalize keys and keep as object for copilot
184+ // Sort keys for consistent comparison
206185 if ( obj && typeof obj === 'object' ) {
207- // Sort keys recursively for consistent comparison
208- const sortKeys = ( item : any ) : any => {
209- if ( Array . isArray ( item ) ) {
210- return item . map ( sortKeys )
211- }
212- if ( item !== null && typeof item === 'object' ) {
213- return Object . keys ( item )
214- . sort ( )
215- . reduce ( ( result : any , key : string ) => {
216- result [ key ] = sortKeys ( item [ key ] )
217- return result
218- } , { } )
219- }
220- return item
221- }
222-
223- // Keep as object (not stringified) for better copilot understanding
224- const normalized = sortKeys ( obj )
225- sanitized [ key ] = normalized
186+ sanitized [ key ] = sortKeysRecursively ( obj )
226187 return
227188 }
228-
229- // If we get here, obj is not an object (maybe null or primitive) - skip it
230- return
231- } catch ( error ) {
232- // Invalid JSON - skip this field to avoid crashes
233- return
234- }
235- }
236-
237- // Skip null/undefined values for other fields
238- if ( subBlock . value === null || subBlock . value === undefined ) {
239- return
240- }
241-
242- // For sensitive fields, either omit or replace with placeholder
243- if ( isSensitiveSubBlock ( key , subBlock ) ) {
244- // If it's an environment variable reference, keep it
245- if (
246- typeof subBlock . value === 'string' &&
247- subBlock . value . startsWith ( '{{' ) &&
248- subBlock . value . endsWith ( '}}' )
249- ) {
189+ } catch {
190+ // Invalid JSON - pass through as-is
250191 sanitized [ key ] = subBlock . value
192+ return
251193 }
252- // Otherwise omit the sensitive value entirely
253- return
254194 }
255195
256196 // Special handling for condition-input type - clean UI metadata
0 commit comments