Skip to content

Commit ef3c88c

Browse files
feat: remove strict ARN validation for Bedrock custom ARN users (#10110)
Co-authored-by: Roo Code <[email protected]>
1 parent a6caa55 commit ef3c88c

File tree

2 files changed

+122
-22
lines changed

2 files changed

+122
-22
lines changed

webview-ui/src/utils/__tests__/validate.test.ts renamed to webview-ui/src/utils/__tests__/validate.spec.ts

Lines changed: 110 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,23 @@ import type { ProviderSettings, OrganizationAllowList } from "@roo-code/types"
22

33
import { RouterModels } from "@roo/api"
44

5-
import { getModelValidationError, validateApiConfigurationExcludingModelErrors } from "../validate"
5+
// Mock i18next to return translation keys with interpolated values
6+
vi.mock("i18next", () => ({
7+
default: {
8+
t: (key: string, options?: Record<string, string>) => {
9+
if (options) {
10+
let result = key
11+
Object.entries(options).forEach(([k, v]) => {
12+
result += ` ${k}=${v}`
13+
})
14+
return result
15+
}
16+
return key
17+
},
18+
},
19+
}))
20+
21+
import { getModelValidationError, validateApiConfigurationExcludingModelErrors, validateBedrockArn } from "../validate"
622

723
describe("Model Validation Functions", () => {
824
const mockRouterModels: RouterModels = {
@@ -70,7 +86,7 @@ describe("Model Validation Functions", () => {
7086
}
7187

7288
const result = getModelValidationError(config, mockRouterModels, allowAllOrganization)
73-
expect(result).toBe("validation.modelAvailability")
89+
expect(result).toContain("settings:validation.modelAvailability")
7490
})
7591

7692
it("returns error for model not allowed by organization", () => {
@@ -100,7 +116,7 @@ describe("Model Validation Functions", () => {
100116
}
101117

102118
const result = getModelValidationError(config, mockRouterModels, allowAllOrganization)
103-
expect(result).toBe("validation.modelId")
119+
expect(result).toBe("settings:validation.modelId")
104120
})
105121

106122
it("handles undefined model IDs gracefully", () => {
@@ -110,7 +126,7 @@ describe("Model Validation Functions", () => {
110126
}
111127

112128
const result = getModelValidationError(config, mockRouterModels, allowAllOrganization)
113-
expect(result).toBe("validation.modelId")
129+
expect(result).toBe("settings:validation.modelId")
114130
})
115131
})
116132

@@ -134,7 +150,7 @@ describe("Model Validation Functions", () => {
134150
}
135151

136152
const result = validateApiConfigurationExcludingModelErrors(config, mockRouterModels, allowAllOrganization)
137-
expect(result).toBe("validation.apiKey")
153+
expect(result).toBe("settings:validation.apiKey")
138154
})
139155

140156
it("excludes model-specific errors", () => {
@@ -164,3 +180,92 @@ describe("Model Validation Functions", () => {
164180
})
165181
})
166182
})
183+
184+
describe("validateBedrockArn", () => {
185+
describe("always returns isValid: true (no strict format validation)", () => {
186+
it("accepts standard AWS Bedrock ARNs", () => {
187+
const result = validateBedrockArn(
188+
"arn:aws:bedrock:us-west-2:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-v2",
189+
)
190+
expect(result.isValid).toBe(true)
191+
expect(result.arnRegion).toBe("us-west-2")
192+
expect(result.errorMessage).toBeUndefined()
193+
})
194+
195+
it("accepts AWS GovCloud ARNs", () => {
196+
const result = validateBedrockArn(
197+
"arn:aws-us-gov:bedrock:us-gov-west-1:123456789012:inference-profile/model",
198+
)
199+
expect(result.isValid).toBe(true)
200+
expect(result.arnRegion).toBe("us-gov-west-1")
201+
expect(result.errorMessage).toBeUndefined()
202+
})
203+
204+
it("accepts AWS China ARNs", () => {
205+
const result = validateBedrockArn("arn:aws-cn:bedrock:cn-north-1:123456789012:inference-profile/model")
206+
expect(result.isValid).toBe(true)
207+
expect(result.arnRegion).toBe("cn-north-1")
208+
expect(result.errorMessage).toBeUndefined()
209+
})
210+
211+
it("accepts SageMaker ARNs", () => {
212+
const result = validateBedrockArn("arn:aws:sagemaker:us-east-1:123456789012:endpoint/my-endpoint")
213+
expect(result.isValid).toBe(true)
214+
expect(result.arnRegion).toBe("us-east-1")
215+
expect(result.errorMessage).toBeUndefined()
216+
})
217+
218+
it("accepts non-standard ARN formats without validation errors", () => {
219+
// Users are advanced - trust their input
220+
const result = validateBedrockArn("arn:custom:service:region:account:resource")
221+
expect(result.isValid).toBe(true)
222+
expect(result.arnRegion).toBe("region")
223+
expect(result.errorMessage).toBeUndefined()
224+
})
225+
226+
it("accepts completely custom ARN strings", () => {
227+
// Even unusual formats should be accepted
228+
const result = validateBedrockArn("some-custom-arn-format")
229+
expect(result.isValid).toBe(true)
230+
// May not be able to extract region from non-standard format
231+
expect(result.errorMessage).toBeUndefined()
232+
})
233+
})
234+
235+
describe("region mismatch warnings", () => {
236+
it("shows warning when ARN region differs from provided region", () => {
237+
const result = validateBedrockArn(
238+
"arn:aws:bedrock:us-west-2:123456789012:inference-profile/model",
239+
"us-east-1",
240+
)
241+
expect(result.isValid).toBe(true) // Still valid, just a warning
242+
expect(result.arnRegion).toBe("us-west-2")
243+
expect(result.errorMessage).toBeDefined()
244+
expect(result.errorMessage).toContain("us-west-2")
245+
})
246+
247+
it("shows no warning when ARN region matches provided region", () => {
248+
const result = validateBedrockArn(
249+
"arn:aws:bedrock:us-west-2:123456789012:inference-profile/model",
250+
"us-west-2",
251+
)
252+
expect(result.isValid).toBe(true)
253+
expect(result.arnRegion).toBe("us-west-2")
254+
expect(result.errorMessage).toBeUndefined()
255+
})
256+
257+
it("shows no warning when no region is provided to check against", () => {
258+
const result = validateBedrockArn("arn:aws:bedrock:us-west-2:123456789012:inference-profile/model")
259+
expect(result.isValid).toBe(true)
260+
expect(result.arnRegion).toBe("us-west-2")
261+
expect(result.errorMessage).toBeUndefined()
262+
})
263+
264+
it("shows no warning when region cannot be extracted from ARN", () => {
265+
const result = validateBedrockArn("non-arn-format", "us-east-1")
266+
expect(result.isValid).toBe(true)
267+
expect(result.arnRegion).toBeUndefined()
268+
expect(result.errorMessage).toBeUndefined()
269+
})
270+
})
271+
})

webview-ui/src/utils/validate.ts

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -216,39 +216,34 @@ function getModelIdForProvider(apiConfiguration: ProviderSettings, provider: Pro
216216
}
217217

218218
/**
219-
* Validates an Amazon Bedrock ARN format and optionally checks if the region in
219+
* Validates an Amazon Bedrock ARN and optionally checks if the region in
220220
* the ARN matches the provided region.
221221
*
222+
* Note: This function does not perform strict format validation on the ARN.
223+
* Users entering custom ARNs are advanced users who should be trusted to
224+
* provide valid ARNs without restriction. See issue #10108.
225+
*
222226
* @param arn The ARN string to validate
223227
* @param region Optional region to check against the ARN's region
224228
* @returns An object with validation results: { isValid, arnRegion, errorMessage }
225229
*/
226230
export function validateBedrockArn(arn: string, region?: string) {
227-
// Validate ARN format.
228-
const arnRegex = /^arn:aws:(?:bedrock|sagemaker):([^:]+):([^:]*):(?:([^/]+)\/([\w.\-:]+)|([^/]+))$/
229-
const match = arn.match(arnRegex)
230-
231-
if (!match) {
232-
return {
233-
isValid: false,
234-
arnRegion: undefined,
235-
errorMessage: i18next.t("settings:validation.arn.invalidFormat"),
236-
}
237-
}
238-
239-
// Extract region from ARN.
240-
const arnRegion = match[1]
231+
// Try to extract region from ARN for region mismatch warning.
232+
// This is a permissive regex that attempts to find the region component
233+
// without enforcing strict ARN format validation.
234+
const regionMatch = arn.match(/^arn:[^:]+:[^:]+:([^:]+):/)
235+
const arnRegion = regionMatch?.[1]
241236

242237
// Check if region in ARN matches provided region (if specified).
243-
if (region && arnRegion !== region) {
238+
if (region && arnRegion && arnRegion !== region) {
244239
return {
245240
isValid: true,
246241
arnRegion,
247242
errorMessage: i18next.t("settings:validation.arn.regionMismatch", { arnRegion, region }),
248243
}
249244
}
250245

251-
// ARN is valid and region matches (or no region was provided to check against).
246+
// ARN is always considered valid - trust the user to enter valid ARNs.
252247
return { isValid: true, arnRegion, errorMessage: undefined }
253248
}
254249

0 commit comments

Comments
 (0)