GitHub Security Advisory: GHSA-7gcc-r8m5-44qm
Package: koa (NPM)
Severity: HIGH (CVSS 7.5)
CWE: CWE-20: Improper Input Validation, CWE-74: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
Vulnerable versions: >= 3.0.0, < 3.1.2
Patched in: 3.1.2

Summary: Koa has Host Header Injection via ctx.hostname

Details:
## 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