From 126aa244c5b51b74ab8c7594e3418fcf4437bf6f Mon Sep 17 00:00:00 2001 From: decolua Date: Fri, 19 Jun 2026 15:09:18 +0700 Subject: [PATCH] fix(kiro): validate region to prevent SSRF (GHSA-6mwv-4mrm-5p3m) Reject non-AWS region values before interpolating them into upstream URLs and stop reflecting upstream response bodies to the client. Co-authored-by: Cursor --- src/app/api/oauth/kiro/api-key/route.js | 6 +++++- src/lib/oauth/constants/oauth.js | 11 +++++++++++ src/lib/oauth/providers.js | 3 +++ src/lib/oauth/services/kiro.js | 10 ++++++++-- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/app/api/oauth/kiro/api-key/route.js b/src/app/api/oauth/kiro/api-key/route.js index 9e9fa152..139df9b5 100644 --- a/src/app/api/oauth/kiro/api-key/route.js +++ b/src/app/api/oauth/kiro/api-key/route.js @@ -58,6 +58,10 @@ export async function POST(request) { }); } catch (error) { console.log("Kiro API key import error:", error); - return NextResponse.json({ error: error.message }, { status: 500 }); + // Do not reflect upstream response body to the client (SSRF hardening) + return NextResponse.json( + { error: "API key validation failed" }, + { status: 500 } + ); } } diff --git a/src/lib/oauth/constants/oauth.js b/src/lib/oauth/constants/oauth.js index 5d035f63..d26cac27 100644 --- a/src/lib/oauth/constants/oauth.js +++ b/src/lib/oauth/constants/oauth.js @@ -67,6 +67,17 @@ export const GITHUB_CONFIG = { ...PROVIDER_OAUTH["github"] }; // Kiro OAuth Configuration (multi-method: AWS Builder ID / IDC / Social / Import Token) export const KIRO_CONFIG = { ...PROVIDER_OAUTH["kiro"] }; +// AWS region allowlist pattern — prevents SSRF via region injection into upstream URLs (GHSA-6mwv-4mrm-5p3m) +export const AWS_REGION_PATTERN = /^[a-z]{2}-[a-z]+-\d{1,2}$/; + +// Reject any region that is not a valid AWS region before interpolating it into a URL +export function assertValidAwsRegion(region) { + if (typeof region !== "string" || !AWS_REGION_PATTERN.test(region)) { + throw new Error("Invalid region"); + } + return region; +} + // Cursor OAuth Configuration (Import Token from Cursor IDE) // tokenStoragePaths: user-reference only, not stored in registry export const CURSOR_CONFIG = { diff --git a/src/lib/oauth/providers.js b/src/lib/oauth/providers.js index 2c8a63f7..f2de1f6b 100644 --- a/src/lib/oauth/providers.js +++ b/src/lib/oauth/providers.js @@ -18,6 +18,7 @@ import { ANTIGRAVITY_CONFIG, GITHUB_CONFIG, KIRO_CONFIG, + assertValidAwsRegion, CURSOR_CONFIG, KIMI_CODING_CONFIG, KILOCODE_CONFIG, @@ -792,6 +793,7 @@ const PROVIDERS = { requestDeviceCode: async (config, codeChallenge, options = {}) => { const trimmedRegion = typeof options.region === "string" ? options.region.trim() : ""; const region = trimmedRegion || "us-east-1"; + assertValidAwsRegion(region); const trimmedStartUrl = typeof options.startUrl === "string" ? options.startUrl.trim() : ""; const startUrl = trimmedStartUrl || config.startUrl; const authMethod = options.authMethod === "idc" ? "idc" : "builder-id"; @@ -860,6 +862,7 @@ const PROVIDERS = { }, pollToken: async (config, deviceCode, codeVerifier, extraData) => { const region = extraData?._region || "us-east-1"; + assertValidAwsRegion(region); const tokenUrl = `https://oidc.${region}.amazonaws.com/token`; const response = await fetch(tokenUrl, { method: "POST", diff --git a/src/lib/oauth/services/kiro.js b/src/lib/oauth/services/kiro.js index d21c82aa..a739661e 100644 --- a/src/lib/oauth/services/kiro.js +++ b/src/lib/oauth/services/kiro.js @@ -1,4 +1,4 @@ -import { KIRO_CONFIG } from "../constants/oauth.js"; +import { KIRO_CONFIG, assertValidAwsRegion } from "../constants/oauth.js"; /** * Kiro OAuth Service @@ -17,6 +17,7 @@ export class KiroService { * Returns clientId and clientSecret for device code flow */ async registerClient(region = "us-east-1") { + assertValidAwsRegion(region); const endpoint = `https://oidc.${region}.amazonaws.com/client/register`; const response = await fetch(endpoint, { @@ -50,6 +51,7 @@ export class KiroService { * Start device authorization for AWS Builder ID or IDC */ async startDeviceAuthorization(clientId, clientSecret, startUrl, region = "us-east-1") { + assertValidAwsRegion(region); const endpoint = `https://oidc.${region}.amazonaws.com/device_authorization`; const response = await fetch(endpoint, { @@ -84,6 +86,7 @@ export class KiroService { * Poll for token using device code (AWS Builder ID/IDC) */ async pollDeviceToken(clientId, clientSecret, deviceCode, region = "us-east-1") { + assertValidAwsRegion(region); const endpoint = `https://oidc.${region}.amazonaws.com/token`; const response = await fetch(endpoint, { @@ -176,7 +179,9 @@ export class KiroService { // AWS SSO OIDC refresh (Builder ID or IDC) if (clientId && clientSecret) { - const endpoint = `https://oidc.${region || "us-east-1"}.amazonaws.com/token`; + const safeRegion = region || "us-east-1"; + assertValidAwsRegion(safeRegion); + const endpoint = `https://oidc.${safeRegion}.amazonaws.com/token`; const response = await fetch(endpoint, { method: "POST", @@ -262,6 +267,7 @@ export class KiroService { * JSON-1.0 surface returns `arn`). */ async listAvailableProfiles(accessToken, region = "us-east-1") { + assertValidAwsRegion(region); const endpoint = `https://codewhisperer.${region}.amazonaws.com`; const response = await fetch(endpoint, {