Skip to content

Commit 0a44ed5

Browse files
authored
fix(credentials): allow encoded Oracle connection payloads (#8856)
* fix(credentials): allow encoded Oracle connection payloads * fix(credentials): keep regression assertions at HTTP boundary
1 parent 970e030 commit 0a44ed5

3 files changed

Lines changed: 100 additions & 4 deletions

File tree

‎apps/docs/openapi-v2-resources.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13842,7 +13842,7 @@
1384213842
"credentials": {
1384313843
"type": "string",
1384413844
"minLength": 1,
13845-
"maxLength": 131072,
13845+
"maxLength": 4325376,
1384613846
"description": "Write-only JSON object string containing the fields declared by credential-provider discovery.",
1384713847
"writeOnly": true
1384813848
}

‎apps/sim/app/api/v2/credentials/route.test.ts‎

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,95 @@ describe('POST /api/v2/credentials', () => {
252252
expect(body).not.toContain('exact passphrase')
253253
})
254254

255+
it.each(['multiline wallet', 'maximum-size JSON with escaped whitespace'])(
256+
'accepts an Oracle connection with %s through the serialized credential envelope',
257+
async (variant) => {
258+
const connection = JSON.stringify({
259+
host: 'database.example.com',
260+
protocol: 'tcps',
261+
serviceName: 'database',
262+
username: 'sim',
263+
password: 'write-only-password',
264+
walletContent: `-----BEGIN CERTIFICATE-----\n${`${'A'.repeat(64)}\n`.repeat(4096)}-----END CERTIFICATE-----`,
265+
})
266+
const serviceAccountJson =
267+
variant === 'multiline wallet' ? connection : connection.padEnd(2 * 1024 * 1024, '\n')
268+
const response = await POST(
269+
new NextRequest('http://localhost:3000/api/v2/credentials', {
270+
method: 'POST',
271+
headers: { 'content-type': 'application/json' },
272+
body: JSON.stringify({
273+
workspaceId: WORKSPACE_ID,
274+
type: 'service_account',
275+
providerId: 'oracledb-service-account',
276+
credentials: JSON.stringify({ serviceAccountJson }),
277+
}),
278+
})
279+
)
280+
281+
expect(response.status).toBe(201)
282+
const body = await response.text()
283+
expect(body).not.toContain('write-only-password')
284+
expect(body).not.toContain('BEGIN CERTIFICATE')
285+
}
286+
)
287+
288+
it('rejects an oversized Oracle connection at the nested field limit', async () => {
289+
const response = await POST(
290+
new NextRequest('http://localhost:3000/api/v2/credentials', {
291+
method: 'POST',
292+
headers: { 'content-type': 'application/json' },
293+
body: JSON.stringify({
294+
workspaceId: WORKSPACE_ID,
295+
type: 'service_account',
296+
providerId: 'oracledb-service-account',
297+
credentials: JSON.stringify({
298+
serviceAccountJson: '{}'.padEnd(2 * 1024 * 1024 + 1, ' '),
299+
}),
300+
}),
301+
})
302+
)
303+
304+
expect(response.status).toBe(400)
305+
expect(await response.json()).toMatchObject({
306+
error: {
307+
code: 'BAD_REQUEST',
308+
details: expect.arrayContaining([
309+
expect.objectContaining({
310+
path: ['credentials', 'serviceAccountJson'],
311+
message: expect.stringContaining('2097152'),
312+
}),
313+
]),
314+
},
315+
})
316+
})
317+
318+
it('rejects excessive serialized envelope padding before credential verification', async () => {
319+
const response = await POST(
320+
new NextRequest('http://localhost:3000/api/v2/credentials', {
321+
method: 'POST',
322+
headers: { 'content-type': 'application/json' },
323+
body: JSON.stringify({
324+
workspaceId: WORKSPACE_ID,
325+
type: 'service_account',
326+
providerId: 'oracledb-service-account',
327+
credentials: JSON.stringify({ serviceAccountJson: '{}' }).padEnd(
328+
4 * 1024 * 1024 + 128 * 1024 + 1,
329+
' '
330+
),
331+
}),
332+
})
333+
)
334+
335+
expect(response.status).toBe(400)
336+
expect(await response.json()).toMatchObject({
337+
error: {
338+
code: 'BAD_REQUEST',
339+
details: expect.arrayContaining([expect.objectContaining({ path: ['credentials'] })]),
340+
},
341+
})
342+
})
343+
255344
/**
256345
* `credentials create slack-custom-bot` used to fail twice over: discovery
257346
* demanded a client-generated id, and a caller who then supplied a slug was

‎apps/sim/lib/api/contracts/v2/credentials.ts‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -402,12 +402,16 @@ export const v2CreateCredentialConnectionContract = defineRouteContract({
402402
},
403403
})
404404

405+
const MAX_SERVICE_ACCOUNT_JSON_CHARS = 2 * 1024 * 1024
406+
// JSON escaping can double the configuration; retain room for the other credential fields.
407+
const MAX_CREDENTIALS_JSON_CHARS = 2 * MAX_SERVICE_ACCOUNT_JSON_CHARS + 128 * 1024
408+
405409
const v2ServiceAccountCredentialFieldsSchema = z
406410
.object({
407411
serviceAccountJson: z
408412
.string()
409413
.min(1)
410-
.max(2 * 1024 * 1024)
414+
.max(MAX_SERVICE_ACCOUNT_JSON_CHARS)
411415
.optional()
412416
.describe(
413417
'Write-only provider service-account JSON configuration, including Oracle Database connection fields.'
@@ -501,7 +505,10 @@ type V2ServiceAccountCredentialFields = z.output<typeof v2ServiceAccountCredenti
501505
const v2ServiceAccountCredentialsJsonSchema = z
502506
.string({ error: missingFieldError('credentials is required') })
503507
.min(1, 'credentials cannot be empty')
504-
.max(131_072, 'credentials must be at most 131072 characters')
508+
.max(
509+
MAX_CREDENTIALS_JSON_CHARS,
510+
`credentials must be at most ${MAX_CREDENTIALS_JSON_CHARS} characters`
511+
)
505512
.describe(
506513
'Write-only JSON object string containing the fields declared by credential-provider discovery.'
507514
)
@@ -674,7 +681,7 @@ const v2ServiceAccountSecretFieldsShape = {
674681
serviceAccountJson: z
675682
.string()
676683
.min(1)
677-
.max(2 * 1024 * 1024)
684+
.max(MAX_SERVICE_ACCOUNT_JSON_CHARS)
678685
.optional()
679686
.describe(
680687
'Write-only provider service-account JSON configuration, including Oracle Database connection fields.'

0 commit comments

Comments
 (0)