Not sure whether this is a bug or by design. For us it created a security issue. If you run two HttpRouter.serve layers on different ports in the same app, each server also answers the other one's routes.
As far as I can tell, serve provides the module-level HttpRouter.layer, and since layers are memoized by reference, every serve in the same Effect.provide / Layer.launch ends up with the same router instance.
Repro:
import { Effect, Layer } from "effect"
import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
import { BunHttpServer } from "@effect/platform-bun"
const internal = HttpRouter.add("GET", "/internal", HttpServerResponse.text("internal"))
const pub = HttpRouter.add("GET", "/public", HttpServerResponse.text("public"))
const Servers = Layer.mergeAll(
HttpRouter.serve(pub).pipe(Layer.provide(BunHttpServer.layer({ port: 3000 }))),
HttpRouter.serve(internal).pipe(Layer.provide(BunHttpServer.layer({ port: 3001 })))
)
const status = (url: string) => Effect.promise(() => fetch(url).then((r) => r.status))
Effect.gen(function*() {
console.log("GET :3000/internal ->", yield* status("http://localhost:3000/internal"))
console.log("GET :3001/public ->", yield* status("http://localhost:3001/public"))
}).pipe(Effect.provide(Servers), Effect.runPromise)
GET :3000/internal -> 200
GET :3001/public -> 200
I'd expect 404 for both.
Passing routerConfig doesn't change anything, since it still wraps the same layer. What works for us is giving each app layer its own router:
HttpRouter.serve(Layer.provideMerge(app, Layer.fresh(HttpRouter.layer)))
We hit this in production: a route that was only meant for an internal port was reachable on our public one, and nothing in the code hinted at it, since the two servers look completely independent. Would it make sense for serve to use Layer.fresh(layer) internally? If the sharing is intended, a short note in the serve docs would already help.
Not sure whether this is a bug or by design. For us it created a security issue. If you run two
HttpRouter.servelayers on different ports in the same app, each server also answers the other one's routes.As far as I can tell,
serveprovides the module-levelHttpRouter.layer, and since layers are memoized by reference, everyservein the sameEffect.provide/Layer.launchends up with the same router instance.Repro:
I'd expect 404 for both.
Passing
routerConfigdoesn't change anything, since it still wraps the samelayer. What works for us is giving each app layer its own router:We hit this in production: a route that was only meant for an internal port was reachable on our public one, and nothing in the code hinted at it, since the two servers look completely independent. Would it make sense for
serveto useLayer.fresh(layer)internally? If the sharing is intended, a short note in theservedocs would already help.