fix(oauth): preserve Kiro IDC region in SSO-cache auto-import (#6113)

preserve Kiro IDC region in SSO-cache auto-import (port PR #2314). VPS-validated live by operator (Hard Rule #18); kiro-auto-import-idc 9/9. Integrated into release/v3.8.46.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-06 20:39:06 -03:00
committed by GitHub
parent 58f53e3a35
commit 3cc48edb35
3 changed files with 89 additions and 14 deletions

View File

@@ -31,6 +31,7 @@
### 🐛 Bug Fixes
- **fix(oauth):** the Kiro SSO-cache auto-import now **preserves the IDC region** — cross-region Amazon Q / Kiro profiles imported from the SSO cache are no longer collapsed to the default region ([#6113](https://github.com/diegosouzapw/OmniRoute/pull/6113)). Regression guard: `tests/unit/kiro-auto-import-idc-2059.test.ts`. VPS-validated via live operator login (Hard Rule #18).
- **fix(dashboard):** passthrough model aliases no longer collide when two namespaced model ids share a last segment. `enx/gpt-5.5` and `enx/codebuddy/gpt-5.5` both auto-generated the alias `gpt-5.5`, so the second model could never be added (the UI just alerted "alias already exists"). Aliases are now disambiguated deterministically — bare last segment when free, then parent-qualified (`codebuddy-gpt-5.5`), then a numeric suffix — while re-adding the exact same model id is still blocked. Regression guard: `tests/unit/passthrough-alias-1850.test.ts`. (thanks @arpicato)
- **fix(translator):** preserve a Gemini `functionResponse` co-located with other parts (another `functionCall`, or trailing `text`) in the same content when translating **Gemini → OpenAI**. `convertGeminiContent()` early-returned the tool message on the first `functionResponse` part, dropping any co-located parts; such contents are now pre-split (one tool message per `functionResponse`, emitted first, plus one message for the remaining parts). Regression guard: `tests/unit/gemini-to-openai-function-response.test.ts`. (thanks @warelik)
- **fix(headroom):** detect a python interpreter managed by **mise / pyenv / asdf / conda**. Headroom's python probe (`src/lib/headroom/detect.ts`) searched a hardcoded `PATH`, but version managers expose their interpreters via shim dirs that only join `PATH` through interactive-shell activation — which the non-interactive server never runs, so a managed python (≥3.10) was invisible and Headroom reported it missing. The search path now prepends the well-known shim/bin dirs (`~/.local/share/mise/shims`, `~/.pyenv/shims`, `~/.asdf/shims`, `$CONDA_PREFIX/bin`, `~/.local/bin`, respecting `MISE_DATA_DIR`/`PYENV_ROOT`/`ASDF_DATA_DIR` when set), and a new `HEADROOM_PYTHON` env override lets operators point straight at their interpreter (mirroring `HEADROOM_URL`). Still shell-free (`execFileSync`). Regression guard: `tests/unit/headroom-detect.test.ts` (5). (thanks @loopyd)

View File

@@ -258,8 +258,13 @@ async function tryAwsSsoCache(targetProvider: string): Promise<{
}
// Read profileArn from Kiro IDE's profile.json.
// The runtime gateway requires us-east-1 in the ARN regardless of the IDC
// region, so we normalize the ARN region to us-east-1 (#2059).
// Kiro IDC (Identity Center) accounts can live in regions other than
// us-east-1. #2059 forced every ARN's region segment to us-east-1,
// which 403s the runtime gateway for non-us-east-1 IDC accounts. The
// OAuth device-code path (src/lib/oauth/providers/kiro.ts) already
// discovers the correct region-matched ARN; mirror that here by
// preserving the profile's ARN region verbatim instead of rewriting
// it.
let profileArn: string | null = null;
const kiroProfilePaths = [
join(
@@ -285,11 +290,7 @@ async function tryAwsSsoCache(targetProvider: string): Promise<{
const profileContent = await readFile(profilePath, "utf-8");
const profileData = JSON.parse(profileContent);
if (profileData.arn) {
// Normalize region to us-east-1 for the runtime gateway
profileArn = profileData.arn.replace(
/arn:aws:codewhisperer:[^:]+:/,
"arn:aws:codewhisperer:us-east-1:"
);
profileArn = profileData.arn;
break;
}
} catch {

View File

@@ -6,7 +6,7 @@
* (a) read `${clientIdHash}.json` from the same cache dir to obtain
* `clientId` / `clientSecret`;
* (b) probe Kiro IDE's `profile.json` (Windows + Linux paths) for `arn` and
* normalize the ARN region to `us-east-1`;
* preserve its region verbatim (see region-preservation note below);
* (c) include all IDC fields in the returned JSON so the import UI can pass
* them along to /api/oauth/kiro/import.
*
@@ -15,6 +15,20 @@
*
* The import schema (`kiroImportSchema`) must accept the new optional IDC
* fields and reject invalid types.
*
* REGION PRESERVATION (upstream #2314, reverses the #2059 forced-normalization
* behavior originally pinned by this file): #2059 forced every profile.json
* ARN's region segment to `us-east-1`, on the assumption the runtime gateway
* always required it. That assumption was wrong for Kiro IDC (Identity
* Center) accounts that live in a non-us-east-1 region — forcing us-east-1
* 403s those accounts. The OAuth device-code path
* (src/lib/oauth/providers/kiro.ts) already does correct region-aware ARN
* discovery (it picks the ARN whose region matches the token's region), so
* forcing us-east-1 only in this auto-import fallback was inconsistent. The
* fix removes the forced rewrite and preserves the profile's ARN region
* as-is, matching the already-correct OAuth path. The tests below were
* updated to assert preservation instead of normalization — this is
* realignment with the correct behavior, not test-weakening.
*/
import test from "node:test";
import assert from "node:assert/strict";
@@ -219,7 +233,7 @@ test("auto-import: when clientIdHash is present, reads client registration file
);
});
test("auto-import: when clientIdHash is present and profile.json exists, returns normalized ARN with us-east-1", async () => {
test("auto-import: when clientIdHash is present and profile.json exists, preserves the ARN's original (non-us-east-1) region — #2314 reverses #2059 forced normalization", async () => {
const CLIENT_ID_HASH = "hash999";
writeAwsSsoCache({
@@ -241,13 +255,72 @@ test("auto-import: when clientIdHash is present and profile.json exists, returns
const { body } = await callGet();
assert.equal(body.found, true, `expected found:true, got: ${JSON.stringify(body)}`);
assert.ok(
typeof body.profileArn === "string" && body.profileArn.includes("us-east-1"),
`profileArn should be normalized to us-east-1, got: ${body.profileArn}`
// #2059 forced this to be rewritten to us-east-1, which 403s non-us-east-1
// IDC accounts. #2314 reverses that: the profile's ARN region must be
// preserved verbatim, matching the OAuth device-code path's region-aware
// ARN discovery.
assert.equal(
body.profileArn,
"arn:aws:codewhisperer:ap-southeast-1:123456789012:profile/MyProfile",
`profileArn region must be preserved verbatim (not forced to us-east-1), got: ${body.profileArn}`
);
assert.ok(
!(body.profileArn as string).includes("ap-southeast-1"),
`normalized profileArn must not contain original region ap-southeast-1, got: ${body.profileArn}`
!(body.profileArn as string).includes("us-east-1"),
`profileArn must NOT be rewritten to us-east-1, got: ${body.profileArn}`
);
});
test("auto-import: when the profile.json ARN is already us-east-1, it is left unchanged (no-op)", async () => {
const CLIENT_ID_HASH = "hash-useast1";
writeAwsSsoCache({
tokenData: {
refreshToken: "aorAAAAAGidc-useast1-test",
clientIdHash: CLIENT_ID_HASH,
region: "us-east-1",
authMethod: "idc",
},
clientIdHash: CLIENT_ID_HASH,
clientData: { clientId: "cid", clientSecret: "csec" },
});
writeKiroProfileJson("arn:aws:codewhisperer:us-east-1:123456789012:profile/MyProfile");
stubFetchForRefresh();
const { body } = await callGet();
assert.equal(body.found, true, `expected found:true, got: ${JSON.stringify(body)}`);
assert.equal(
body.profileArn,
"arn:aws:codewhisperer:us-east-1:123456789012:profile/MyProfile",
`us-east-1 ARN must be left unchanged, got: ${body.profileArn}`
);
});
test("auto-import: when profile.json is missing/malformed, profileArn is null and import still succeeds", async () => {
const CLIENT_ID_HASH = "hash-noprofile";
writeAwsSsoCache({
tokenData: {
refreshToken: "aorAAAAAGidc-noprofile-test",
clientIdHash: CLIENT_ID_HASH,
region: "eu-west-1",
authMethod: "idc",
},
clientIdHash: CLIENT_ID_HASH,
clientData: { clientId: "cid", clientSecret: "csec" },
});
// Intentionally do NOT write profile.json — simulates a missing/absent file.
stubFetchForRefresh();
const { body } = await callGet();
assert.equal(body.found, true, `expected found:true, got: ${JSON.stringify(body)}`);
assert.ok(
body.profileArn === null || body.profileArn === undefined,
`profileArn must be null/undefined when profile.json is absent, got: ${body.profileArn}`
);
});