Describe the bug
handleServerFunctionRequest gates GET dispatch on the GET(fn) declaration and answers 405
otherwise — "no crafted GET URLs against functions that never opted in". The check is
request.method === "GET" (packages/web/server-functions/src/server.ts:1626 on next), so a
HEAD request skips it entirely: any registered function id can be invoked, with arguments
from ?args=, whether or not it ever declared GET — including functions that only exist to
mutate.
HEAD is defined as safe and idempotent, and it is sent by link checkers, uptime probes,
prefetchers and proxies warming a URL, so this is reachable without anyone intending to call
the function.
Steps to reproduce
mkdir sf-repro && cd sf-repro && npm init -y
npm i @solidjs/web@2.0.0-rc.3 # pulls solid-js@2.0.0-rc.3
node --conditions=development repro.mjs # the dev condition only makes the error text readable
repro.mjs:
import { configureServerFunctionsServer, handleServerFunctionRequest, registerServerReference } from "@solidjs/web/server-functions";
import { provideRequestEvent } from "@solidjs/web/storage";
configureServerFunctionsServer({ provideEvent: (event, run) => provideRequestEvent(event, run) });
let ran = false;
registerServerReference("post-only-0", async () => {
ran = true; // stands in for a mutation
return "side effect happened";
});
const headers = { "Sec-Fetch-Site": "same-origin" };
for (const method of ["HEAD", "GET"]) {
ran = false;
const res = await handleServerFunctionRequest(
new Request("http://app.test/_server?id=post-only-0", { method, headers })
);
console.log(method, res.status, await res.text(), "executed:", ran);
}
Output on @solidjs/web@2.0.0-rc.3 (with solid-js@2.0.0-rc.3):
HEAD 200 side effect happened executed: true
GET 405 Method not allowed for server function: post-only-0 executed: false
Arguments come along too: HEAD /_server?id=echo-0&args=%5B%22from%20HEAD%22%5D reaches the
function as ["from HEAD"], because parseArguments reads the query whenever the
X-Server-Function-Instance header is absent.
Expected behavior
HEAD should be gated exactly like GET: 405 for functions that never declared GET, dispatch
for those that did (with the body dropped by the runtime, as HEAD requires). Today the method
enforcement added for GET is bypassable by changing one word of the request line.
Environment
|
|
@solidjs/web |
2.0.0-rc.3 (published) |
solid-js |
2.0.0-rc.3 |
| Node |
v24.19.0 |
| OS |
macOS (darwin 25.6.0) |
| Code references |
next @ f5939ef |
Describe the bug
handleServerFunctionRequestgates GET dispatch on theGET(fn)declaration and answers 405otherwise — "no crafted GET URLs against functions that never opted in". The check is
request.method === "GET"(packages/web/server-functions/src/server.ts:1626onnext), so aHEAD request skips it entirely: any registered function id can be invoked, with arguments
from
?args=, whether or not it ever declared GET — including functions that only exist tomutate.
HEAD is defined as safe and idempotent, and it is sent by link checkers, uptime probes,
prefetchers and proxies warming a URL, so this is reachable without anyone intending to call
the function.
Steps to reproduce
repro.mjs:Output on
@solidjs/web@2.0.0-rc.3(withsolid-js@2.0.0-rc.3):Arguments come along too:
HEAD /_server?id=echo-0&args=%5B%22from%20HEAD%22%5Dreaches thefunction as
["from HEAD"], becauseparseArgumentsreads the query whenever theX-Server-Function-Instanceheader is absent.Expected behavior
HEAD should be gated exactly like GET: 405 for functions that never declared
GET, dispatchfor those that did (with the body dropped by the runtime, as HEAD requires). Today the method
enforcement added for
GETis bypassable by changing one word of the request line.Environment
@solidjs/websolid-jsnext@f5939ef