fix(apiproxy): refuse non-JSON media types on /api POST bodies

Browsers send "simple" POSTs (text/plain, form encodings) without a CORS
preflight, so a malicious page could execute side-effectful RPCs blind —
the response stays unreadable cross-origin, but session.prompt would
still run. The carrier now answers 415 unless the declared media type is
application/json, forcing every cross-site attempt into a preflight this
server never answers. Raw-fetch specs gain the header; a new handler case
proves the fence rejects before the impl runs.
This commit is contained in:
creatixchu
2026-07-28 14:40:13 +08:00
parent 4c6fb8b957
commit d2fea6d789
6 changed files with 53 additions and 24 deletions
+13 -2
View File
@@ -2,8 +2,8 @@
* Server side of the fetch carrier: maps an ApiProxy onto a pure
* WHATWG Request->Response function. Two-level parse: full form (type/rpcId/method +
* path==method) -> payload dispatched per method. HTTP status expresses only the carrier
* (404 unknown path / 400 non-JSON body / 500 handler crash); business errors are always
* 200 + ServerResponse.
* (404 unknown path / 415 non-JSON media type / 400 non-JSON body / 500 handler crash);
* business errors are always 200 + ServerResponse.
*/
import { randomUUID } from 'node:crypto'
@@ -188,6 +188,17 @@ export function toFetchHandler(api: ApiProxy): { fetch: typeof fetch } {
return new Response('not found', { status: 404 })
}
// Cross-site write fence: browsers send "simple" POSTs (text/plain,
// form encodings) without a CORS preflight, so a malicious page could
// otherwise execute side-effectful RPCs blind — the response stays
// unreadable cross-origin, but session.prompt would still run. Only the
// JSON media type is accepted; anything else is forced into a preflight
// this server never answers. 415 = carrier layer, like the 400 below.
const mediaType = req.headers.get('content-type')?.split(';', 1)[0]?.trim().toLowerCase()
if (mediaType !== 'application/json') {
return new Response('content type must be application/json', { status: 415 })
}
let body: unknown
try {
body = await req.json()