GitHub Security Advisory: GHSA-2j6q-whv2-gh6w
Package: h3 (NPM)
Severity: LOW (CVSS 3.7)
CWE: CWE-706: Use of Incorrectly-Resolved Name or Reference
Vulnerable versions: >= 2.0.1-alpha.0, <= 2.0.1-rc.16
Patched in: 2.0.1-rc.17

Summary: h3: Missing Path Segment Boundary Check in `mount()` Causes Middleware Execution on Unrelated Prefix-Matching Routes

Details:
## Summary

The `mount()` method in h3 uses a simple `startsWith()` check to determine whether incoming requests fall under a mounted sub-application's path prefix. Because this check does not verify a path segment boundary (i.e., that the next character after the base is `/` or end-of-string), middleware registered on a mount like `/admin` will also execute for unrelated routes such as `/admin-public`, `/administrator`, or `/adminstuff`. This allows an attacker to trigger context-setting middleware on paths it was never intended to cover, potentially polluting request context with unintended privilege flags.

## Details

The root cause is in `src/h3.ts:127` within the `mount()` method:

```typescript
// src/h3.ts:122-135
mount(base: string, input: FetchHandler | FetchableObject | H3Type) {
  if ("handler" in input) {
    if (input["~middleware"].length > 0) {
      this["~middleware"].push((event, next) => {
        const originalPathname = event.url.pathname;
        if (!originalPathname.startsWith(base)) {  // <-- BUG: no segment boundary check
          return next();
        }
        event.url.pathname = event.url.pathname.slice(base.length) || "/";
        return callMiddleware(event, input["~middleware"], () => {
          event.url.pathname = originalPathname;
          return next();
        });
      });
    }
```

When a sub-app is mounted at `/admin`, the check `originalPathname.startsWith("/admin")` returns `true` for `/admin`, `/admin/`, `/admin/dashboard`, but also for `/admin-public`, `/administrator`, `/adminFoo`, etc. The mounted sub-app's entire middleware chain then executes for these unrelated paths.

A secondary instance of the same flaw exists in `src/utils/internal/path.ts:40`:

```typescript
// src/utils/internal/path.ts:35-45
export function withoutBase(input: string = "", base: string = ""): string {
  if (!base || base === "/") {
    return input;
  }
  const _base = withoutTrailingSlash(base);
  if (!input.startsWith(_base)) {  // <-- Same flaw: no segment boundary check
    return input;
  }
  const trimmed = input.slice(_base.length);
  return trimmed[0] === "/" ? trimmed : "/" + trimmed;
}
```

The `withoutBase()` utility will incorrectly strip the base from paths that merely share a string prefix, returning mangled paths (e.g., `withoutBase("/admin-public/info", "/admin")` returns `/-public/info`).

**Exploitation flow:**

1. Developer mounts a sub-app at `/admin` with middleware that sets `event.context.isAdmin = true`
2. Developer defines a separate route `/admin-public/info` on the parent app that reads `event.context.isAdmin`
3. Attacker requests `GET /admin-public/info`
4. The `/admin` mount's `startsWith` check passes → admin middleware executes → sets `isAdmin = true`
5. The middleware's "restore pathname" callback fires, control returns to the parent app
6. The `/admin-public/info` handler sees `event.context.isAdmin === true`

## PoC

```javascript
// poc.js — demonstrates context pollution across mount