Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

HttpRouter: ignoreDuplicateSlashes (on by default) collapses // inside query string values

Open Beginner friendly
#8,881 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
88/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
typescript
Domain
backend

Research direction

Start at packages/effect/src/http/FindMyWay/internal/router.ts, in find() where removeDuplicateSlashes(path) is called before safeDecodeURI(path) (around L344-L350). Restructure so the querystring is split off first and only the path portion is normalized, preserving the invariant that sliceParameter works on the same string as noted in the comment above the call. Add a test under packages/effect/test/http covering a query value containing unencoded // (e.g. ?u=https://x.com/y) with default router config; done when the query value reaches the handler unchanged while duplicate slashes in the path are still collapsed.

Written by the indexing model from the issue text.

Description

What version of Effect is running?

[email protected]. The code is unchanged on main (757821fe).

What steps can reproduce the bug?

The vendored FindMyWay router enables ignoreDuplicateSlashes: true by default (router.ts#L56). In find(), when the URL is not "clean", removeDuplicateSlashes(path) runs on the whole URL (L344-L346) before safeDecodeURI(path) splits off the querystring (L350). As a result, // in the query string is collapsed too.

import { Effect } from "effect"
import { FindMyWay, HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/http"

// 1. FindMyWay directly
const router = FindMyWay.make<string>({})
router.on("GET", "/a", "handler")
console.log("FindMyWay:", router.find("GET", "/a?u=https://x.com/y")?.searchParams)

// 2. HttpRouter end to end
const app = HttpRouter.add(
  "GET",
  "/oauth/authorize",
  Effect.gen(function*() {
    const params = yield* HttpServerRequest.ParsedSearchParams
    return HttpServerResponse.jsonUnsafe(params)
  })
)
for (const routerConfig of [undefined, { ignoreDuplicateSlashes: false }]) {
  const { handler, dispose } = HttpRouter.toWebHandler(app, { routerConfig })
  const res = await handler(new Request("http://localhost/oauth/authorize?resource=https://example.com/api/mcp"))
  console.log(`HttpRouter (routerConfig: ${JSON.stringify(routerConfig)}):`, await res.text())
  await dispose()
}

Output with bun repro.ts and [email protected]:

FindMyWay: { u: "https:/x.com/y" }
HttpRouter (routerConfig: undefined): {"resource":"https:/example.com/api/mcp"}
HttpRouter (routerConfig: {"ignoreDuplicateSlashes":false}): {"resource":"https://example.com/api/mcp"}
What is the expected behavior?

Duplicate-slash normalization should apply to the path only. Query values should reach the handler (ParsedSearchParams, HttpApi query schemas) unchanged: resource = "https://example.com/api/mcp".

What do you see instead?

resource = "https:/example.com/api/mcp". Percent-encoded values (https%3A%2F%2F…) are not affected. Unencoded values are, and RFC 3986 §3.4 allows unencoded : and / in a query. OAuth 2.0 parameters such as RFC 8707 resource and redirect_uri are URLs, and some clients send them unencoded. Because ignoreDuplicateSlashes is enabled by default, these requests are silently corrupted with no error. In our case, an MCP OAuth /authorize endpoint rejected a valid resource.

Additional information

Suggested fix: in find(), split on the first ? (or let safeDecodeURI separate the querystring first), run removeDuplicateSlashes on the path part only, then continue. The comment above that call says it must run before safeDecodeURI so sliceParameter works on the same string. Normalizing only the path part before reattaching ? + querystring keeps that invariant. Route registration (on, L98-L100) is not affected in practice because route paths have no query.

Upstream: delvedor/find-my-way has the same ordering on its current main (index.js find), and [email protected] with ignoreDuplicateSlashes: true produces the same { u: "https:/x.com/y" }. I found no upstream issue or fix for it. Upstream defaults this option to false, so the bug has more impact here, where it is enabled by default.

Workaround: disable the option through RouterConfig, for example HttpRouter.toWebHandler(app, { routerConfig: { ignoreDuplicateSlashes: false } }) / HttpRouter.serve(app, { routerConfig: ... }), or provide HttpRouter.RouterConfig.

Dominant language
TypeScript
Stars
16.7k
Forks
808
Avg merge
10h 36m
Merged PRs (30d)
449

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from Effect-TS/effect

All issues in Effect-TS/effect

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.