From ee5f18cf8735617b2b37dcdef9303596e084c060 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 15 Dec 2025 21:36:45 +0000 Subject: [PATCH] feat: remove strict ARN validation for Bedrock custom ARN users This change removes the strict regex validation from validateBedrockArn() to allow users to enter any ARN format without restriction. Users entering custom ARNs are advanced users who should be trusted to provide valid ARNs. The region mismatch warning is preserved as a helpful hint. Closes #10108 --- .../{validate.test.ts => validate.spec.ts} | 115 +++++++++++++++++- webview-ui/src/utils/validate.ts | 29 ++--- 2 files changed, 122 insertions(+), 22 deletions(-) rename webview-ui/src/utils/__tests__/{validate.test.ts => validate.spec.ts} (52%) diff --git a/webview-ui/src/utils/__tests__/validate.test.ts b/webview-ui/src/utils/__tests__/validate.spec.ts similarity index 52% rename from webview-ui/src/utils/__tests__/validate.test.ts rename to webview-ui/src/utils/__tests__/validate.spec.ts index 46de15ea916..6078d993adc 100644 --- a/webview-ui/src/utils/__tests__/validate.test.ts +++ b/webview-ui/src/utils/__tests__/validate.spec.ts @@ -2,7 +2,23 @@ import type { ProviderSettings, OrganizationAllowList } from "@roo-code/types" import { RouterModels } from "@roo/api" -import { getModelValidationError, validateApiConfigurationExcludingModelErrors } from "../validate" +// Mock i18next to return translation keys with interpolated values +vi.mock("i18next", () => ({ + default: { + t: (key: string, options?: Record) => { + if (options) { + let result = key + Object.entries(options).forEach(([k, v]) => { + result += ` ${k}=${v}` + }) + return result + } + return key + }, + }, +})) + +import { getModelValidationError, validateApiConfigurationExcludingModelErrors, validateBedrockArn } from "../validate" describe("Model Validation Functions", () => { const mockRouterModels: RouterModels = { @@ -70,7 +86,7 @@ describe("Model Validation Functions", () => { } const result = getModelValidationError(config, mockRouterModels, allowAllOrganization) - expect(result).toBe("validation.modelAvailability") + expect(result).toContain("settings:validation.modelAvailability") }) it("returns error for model not allowed by organization", () => { @@ -100,7 +116,7 @@ describe("Model Validation Functions", () => { } const result = getModelValidationError(config, mockRouterModels, allowAllOrganization) - expect(result).toBe("validation.modelId") + expect(result).toBe("settings:validation.modelId") }) it("handles undefined model IDs gracefully", () => { @@ -110,7 +126,7 @@ describe("Model Validation Functions", () => { } const result = getModelValidationError(config, mockRouterModels, allowAllOrganization) - expect(result).toBe("validation.modelId") + expect(result).toBe("settings:validation.modelId") }) }) @@ -134,7 +150,7 @@ describe("Model Validation Functions", () => { } const result = validateApiConfigurationExcludingModelErrors(config, mockRouterModels, allowAllOrganization) - expect(result).toBe("validation.apiKey") + expect(result).toBe("settings:validation.apiKey") }) it("excludes model-specific errors", () => { @@ -164,3 +180,92 @@ describe("Model Validation Functions", () => { }) }) }) + +describe("validateBedrockArn", () => { + describe("always returns isValid: true (no strict format validation)", () => { + it("accepts standard AWS Bedrock ARNs", () => { + const result = validateBedrockArn( + "arn:aws:bedrock:us-west-2:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-v2", + ) + expect(result.isValid).toBe(true) + expect(result.arnRegion).toBe("us-west-2") + expect(result.errorMessage).toBeUndefined() + }) + + it("accepts AWS GovCloud ARNs", () => { + const result = validateBedrockArn( + "arn:aws-us-gov:bedrock:us-gov-west-1:123456789012:inference-profile/model", + ) + expect(result.isValid).toBe(true) + expect(result.arnRegion).toBe("us-gov-west-1") + expect(result.errorMessage).toBeUndefined() + }) + + it("accepts AWS China ARNs", () => { + const result = validateBedrockArn("arn:aws-cn:bedrock:cn-north-1:123456789012:inference-profile/model") + expect(result.isValid).toBe(true) + expect(result.arnRegion).toBe("cn-north-1") + expect(result.errorMessage).toBeUndefined() + }) + + it("accepts SageMaker ARNs", () => { + const result = validateBedrockArn("arn:aws:sagemaker:us-east-1:123456789012:endpoint/my-endpoint") + expect(result.isValid).toBe(true) + expect(result.arnRegion).toBe("us-east-1") + expect(result.errorMessage).toBeUndefined() + }) + + it("accepts non-standard ARN formats without validation errors", () => { + // Users are advanced - trust their input + const result = validateBedrockArn("arn:custom:service:region:account:resource") + expect(result.isValid).toBe(true) + expect(result.arnRegion).toBe("region") + expect(result.errorMessage).toBeUndefined() + }) + + it("accepts completely custom ARN strings", () => { + // Even unusual formats should be accepted + const result = validateBedrockArn("some-custom-arn-format") + expect(result.isValid).toBe(true) + // May not be able to extract region from non-standard format + expect(result.errorMessage).toBeUndefined() + }) + }) + + describe("region mismatch warnings", () => { + it("shows warning when ARN region differs from provided region", () => { + const result = validateBedrockArn( + "arn:aws:bedrock:us-west-2:123456789012:inference-profile/model", + "us-east-1", + ) + expect(result.isValid).toBe(true) // Still valid, just a warning + expect(result.arnRegion).toBe("us-west-2") + expect(result.errorMessage).toBeDefined() + expect(result.errorMessage).toContain("us-west-2") + }) + + it("shows no warning when ARN region matches provided region", () => { + const result = validateBedrockArn( + "arn:aws:bedrock:us-west-2:123456789012:inference-profile/model", + "us-west-2", + ) + expect(result.isValid).toBe(true) + expect(result.arnRegion).toBe("us-west-2") + expect(result.errorMessage).toBeUndefined() + }) + + it("shows no warning when no region is provided to check against", () => { + const result = validateBedrockArn("arn:aws:bedrock:us-west-2:123456789012:inference-profile/model") + expect(result.isValid).toBe(true) + expect(result.arnRegion).toBe("us-west-2") + expect(result.errorMessage).toBeUndefined() + }) + + it("shows no warning when region cannot be extracted from ARN", () => { + const result = validateBedrockArn("non-arn-format", "us-east-1") + expect(result.isValid).toBe(true) + expect(result.arnRegion).toBeUndefined() + expect(result.errorMessage).toBeUndefined() + }) + }) +}) diff --git a/webview-ui/src/utils/validate.ts b/webview-ui/src/utils/validate.ts index bf49e03a4ba..06dde701105 100644 --- a/webview-ui/src/utils/validate.ts +++ b/webview-ui/src/utils/validate.ts @@ -216,31 +216,26 @@ function getModelIdForProvider(apiConfiguration: ProviderSettings, provider: Pro } /** - * Validates an Amazon Bedrock ARN format and optionally checks if the region in + * Validates an Amazon Bedrock ARN and optionally checks if the region in * the ARN matches the provided region. * + * Note: This function does not perform strict format validation on the ARN. + * Users entering custom ARNs are advanced users who should be trusted to + * provide valid ARNs without restriction. See issue #10108. + * * @param arn The ARN string to validate * @param region Optional region to check against the ARN's region * @returns An object with validation results: { isValid, arnRegion, errorMessage } */ export function validateBedrockArn(arn: string, region?: string) { - // Validate ARN format. - const arnRegex = /^arn:aws:(?:bedrock|sagemaker):([^:]+):([^:]*):(?:([^/]+)\/([\w.\-:]+)|([^/]+))$/ - const match = arn.match(arnRegex) - - if (!match) { - return { - isValid: false, - arnRegion: undefined, - errorMessage: i18next.t("settings:validation.arn.invalidFormat"), - } - } - - // Extract region from ARN. - const arnRegion = match[1] + // Try to extract region from ARN for region mismatch warning. + // This is a permissive regex that attempts to find the region component + // without enforcing strict ARN format validation. + const regionMatch = arn.match(/^arn:[^:]+:[^:]+:([^:]+):/) + const arnRegion = regionMatch?.[1] // Check if region in ARN matches provided region (if specified). - if (region && arnRegion !== region) { + if (region && arnRegion && arnRegion !== region) { return { isValid: true, arnRegion, @@ -248,7 +243,7 @@ export function validateBedrockArn(arn: string, region?: string) { } } - // ARN is valid and region matches (or no region was provided to check against). + // ARN is always considered valid - trust the user to enter valid ARNs. return { isValid: true, arnRegion, errorMessage: undefined } }