From af902118c9bc361fc0898ef3a629efdef9cc4b5b Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Sat, 23 May 2026 16:46:15 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=B1=20fix:=20Validate=20Bedrock=20User?= =?UTF-8?q?=20Credentials=20(#13277)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/endpoints/bedrock/initialize.spec.ts | 38 +++++++++ .../api/src/endpoints/bedrock/initialize.ts | 81 +++++++++++++++---- 2 files changed, 104 insertions(+), 15 deletions(-) diff --git a/packages/api/src/endpoints/bedrock/initialize.spec.ts b/packages/api/src/endpoints/bedrock/initialize.spec.ts index 65be1c65b8..009776fd20 100644 --- a/packages/api/src/endpoints/bedrock/initialize.spec.ts +++ b/packages/api/src/endpoints/bedrock/initialize.spec.ts @@ -483,6 +483,26 @@ describe('initializeBedrock', () => { expect(result.llmConfig).not.toHaveProperty('credentials'); }); + it('should reject a non-string user-provided Bedrock API key', async () => { + process.env.BEDROCK_AWS_BEARER_TOKEN = AuthType.USER_PROVIDED; + process.env.BEDROCK_AWS_ACCESS_KEY_ID = AuthType.USER_PROVIDED; + process.env.BEDROCK_AWS_SECRET_ACCESS_KEY = AuthType.USER_PROVIDED; + const params = createMockParams(); + (params.db.getUserKey as jest.Mock).mockResolvedValue( + JSON.stringify({ + apiKey: JSON.stringify({ + bearerToken: {}, + accessKeyId: 'user-access-key', + secretAccessKey: 'user-secret-key', + }), + }), + ); + + await expect(initializeBedrock(params)).rejects.toThrow( + 'Bedrock credentials not provided. Please provide them again.', + ); + }); + it('should not use stored access keys when only bearer token mode is configured', async () => { process.env.BEDROCK_AWS_BEARER_TOKEN = AuthType.USER_PROVIDED; const params = createMockParams(); @@ -513,6 +533,24 @@ describe('initializeBedrock', () => { }); expect(result.llmConfig).not.toHaveProperty('client'); }); + + it('should reject non-string user-provided access key values', async () => { + process.env.BEDROCK_AWS_ACCESS_KEY_ID = AuthType.USER_PROVIDED; + process.env.BEDROCK_AWS_SECRET_ACCESS_KEY = AuthType.USER_PROVIDED; + const params = createMockParams(); + (params.db.getUserKey as jest.Mock).mockResolvedValue( + JSON.stringify({ + apiKey: JSON.stringify({ + accessKeyId: {}, + secretAccessKey: 'user-secret-key', + }), + }), + ); + + await expect(initializeBedrock(params)).rejects.toThrow( + 'Bedrock credentials not provided. Please provide them again.', + ); + }); }); describe('Credentials Edge Cases', () => { diff --git a/packages/api/src/endpoints/bedrock/initialize.ts b/packages/api/src/endpoints/bedrock/initialize.ts index bd19da1acb..c3e987f6c8 100644 --- a/packages/api/src/endpoints/bedrock/initialize.ts +++ b/packages/api/src/endpoints/bedrock/initialize.ts @@ -15,12 +15,61 @@ import type { BaseInitializeParams, InitializeResultBase, BedrockCredentials, - BedrockUserCredentials, GuardrailConfiguration, InferenceProfileConfig, } from '~/types'; import { checkUserKeyExpiry } from '~/utils'; +const BEDROCK_CREDENTIALS_ERROR = 'Bedrock credentials not provided. Please provide them again.'; + +type UserCredentialKey = 'accessKeyId' | 'secretAccessKey' | 'sessionToken' | 'bearerToken'; +type UserCredentialValue = string | number | boolean | object | null; +type ParsedBedrockUserCredentials = Partial> & { + apiKey?: string; +}; + +function isParsedBedrockUserCredentials(value: unknown): value is ParsedBedrockUserCredentials { + return value != null && typeof value === 'object' && !Array.isArray(value); +} + +function parseBedrockUserCredentials(userKey: string): ParsedBedrockUserCredentials { + const storedCredentials = JSON.parse(userKey) as unknown; + if (!isParsedBedrockUserCredentials(storedCredentials)) { + throw new Error(BEDROCK_CREDENTIALS_ERROR); + } + + if (typeof storedCredentials.apiKey !== 'string') { + return storedCredentials; + } + + const nestedCredentials = JSON.parse(storedCredentials.apiKey) as unknown; + if (!isParsedBedrockUserCredentials(nestedCredentials)) { + throw new Error(BEDROCK_CREDENTIALS_ERROR); + } + + return nestedCredentials; +} + +function getUserCredentialValue( + credentials: ParsedBedrockUserCredentials, + key: UserCredentialKey, +): string | undefined { + if (!Object.prototype.hasOwnProperty.call(credentials, key)) { + return undefined; + } + + const value = credentials[key]; + if (value === '') { + return undefined; + } + + if (typeof value !== 'string') { + throw new Error(BEDROCK_CREDENTIALS_ERROR); + } + + return value; +} + /** * Initializes Bedrock endpoint configuration. * @@ -101,35 +150,37 @@ export async function initializeBedrock({ }); if (!userKey) { - throw new Error('Bedrock credentials not provided. Please provide them again.'); + throw new Error(BEDROCK_CREDENTIALS_ERROR); } - let userCredentials: BedrockUserCredentials; + let userCredentials: ParsedBedrockUserCredentials; try { - const storedCredentials = JSON.parse(userKey) as BedrockUserCredentials & { apiKey?: string }; - userCredentials = - typeof storedCredentials.apiKey === 'string' - ? (JSON.parse(storedCredentials.apiKey) as BedrockUserCredentials) - : storedCredentials; + userCredentials = parseBedrockUserCredentials(userKey); } catch { - throw new Error('Bedrock credentials not provided. Please provide them again.'); + throw new Error(BEDROCK_CREDENTIALS_ERROR); } - if (userProvidesBearerToken && userCredentials.bearerToken) { - bearerToken = userCredentials.bearerToken; + const userBearerToken = userProvidesBearerToken + ? getUserCredentialValue(userCredentials, 'bearerToken') + : undefined; + + if (userBearerToken) { + bearerToken = userBearerToken; } else { const canUseAccessKeys = userProvidesAccessKeyId || userProvidesSecretAccessKey || userProvidesSessionToken; - const accessKeyId = userProvidesAccessKeyId ? userCredentials.accessKeyId : staticAccessKeyId; + const accessKeyId = userProvidesAccessKeyId + ? getUserCredentialValue(userCredentials, 'accessKeyId') + : staticAccessKeyId; const secretAccessKey = userProvidesSecretAccessKey - ? userCredentials.secretAccessKey + ? getUserCredentialValue(userCredentials, 'secretAccessKey') : staticSecretAccessKey; const sessionToken = userProvidesSessionToken - ? userCredentials.sessionToken + ? getUserCredentialValue(userCredentials, 'sessionToken') : staticSessionToken; if (!canUseAccessKeys || !accessKeyId || !secretAccessKey) { - throw new Error('Bedrock credentials not provided. Please provide them again.'); + throw new Error(BEDROCK_CREDENTIALS_ERROR); } credentials = {