Skip to content

Commit 56ab7d0

Browse files
fix(security): redact nested credential references
1 parent 89ce0fd commit 56ab7d0

2 files changed

Lines changed: 42 additions & 1 deletion

File tree

apps/sim/lib/workflows/credentials/credential-extractor.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ vi.mock('@/lib/workflows/search-replace/indexer', () => ({
2525
id: paramId,
2626
type: 'short-input',
2727
password: paramId === 'apiKey' || paramId === 'token',
28+
canonicalParamId: paramId === 'manualCredential' ? 'oauthCredential' : undefined,
2829
},
2930
})),
3031
}))
@@ -147,6 +148,40 @@ describe('export sanitizer resource coverage', () => {
147148
])
148149
})
149150

151+
it('withholds advanced credential selectors nested inside tool inputs', () => {
152+
const value = [
153+
{
154+
type: 'gmail',
155+
toolId: 'gmail_send',
156+
operation: 'send_gmail',
157+
params: {
158+
manualCredential: 'credential-id',
159+
query: 'safe input',
160+
},
161+
},
162+
]
163+
vi.mocked(getBlock).mockReturnValue({
164+
name: 'Test',
165+
description: '',
166+
subBlocks: [{ id: 'field', title: 'Field', type: 'tool-input' }],
167+
outputs: {},
168+
} as never)
169+
170+
const sanitized = sanitizeWorkflowForSharing(stateWithSubBlock('tool-input', value), {
171+
preserveEnvVars: true,
172+
redactOpaqueCredentialInputs: true,
173+
})
174+
175+
expect(sanitized.blocks?.b1?.subBlocks?.field?.value).toEqual([
176+
{
177+
type: 'gmail',
178+
toolId: 'gmail_send',
179+
operation: 'send_gmail',
180+
params: { manualCredential: null, query: 'safe input' },
181+
},
182+
])
183+
})
184+
150185
it('withholds opaque table values from public snapshots', () => {
151186
const value = [
152187
{ Key: 'Authorization', Value: 'Bearer plaintext-secret' },

apps/sim/lib/workflows/credentials/credential-extractor.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ const WORKSPACE_SPECIFIC_TYPES: ReadonlySet<string> = new Set<string>([
7070
* type-keyed registry above cannot supply, so this list stays explicit.
7171
*/
7272
const WORKSPACE_SPECIFIC_FIELDS = new Set([
73+
'credentialId',
74+
'oauthCredential',
7375
'knowledgeBaseId',
7476
'tagFilters',
7577
'documentTags',
@@ -339,7 +341,11 @@ function sanitizeConfiguredSubBlockValue(
339341
if (config.password === true) {
340342
return options.preserveEnvVars && isEnvironmentVariableReference(value) ? value : null
341343
}
342-
if (WORKSPACE_SPECIFIC_TYPES.has(config.type) || WORKSPACE_SPECIFIC_FIELDS.has(config.id)) {
344+
if (
345+
WORKSPACE_SPECIFIC_TYPES.has(config.type) ||
346+
WORKSPACE_SPECIFIC_FIELDS.has(config.id) ||
347+
(config.canonicalParamId != null && WORKSPACE_SPECIFIC_FIELDS.has(config.canonicalParamId))
348+
) {
343349
return null
344350
}
345351
return value

0 commit comments

Comments
 (0)