mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 22:32:04 +00:00
fix(http): refuse state changes a browser sends from another site
The session cookie is SameSite=Lax, and SameSite compares the registrable domain, so a page on a sibling subdomain of the instance could send a CORS-simple POST with the person's cookie attached. safeJson accepted any Content-Type containing "application/json", so a text/plain body that mentions it in a parameter, which needs no preflight, was parsed as the person's own request: readings added, entries deleted, integrations disconnected, a delegate invited within the re-proof window. apiHandler now refuses a POST, PUT, PATCH or DELETE before authentication when the browser marks it Sec-Fetch-Site same-site or cross-site, or, from a browser that sends no fetch metadata, when its Origin names a host other than the one the request went to or APP_URL. A request with an Authorization header, or with neither header (the phone app, scripts, webhook senders), is unaffected, as are the webhook and URL-credential routes that authenticate with their own secret. The JSON media type is compared exactly, with parameters allowed, in safeJson and in every route that branched on a substring of it.
This commit is contained in:
1 parent
fa50bcd1cb
commit
77ace106b3
20 files changed
+436
-47
No files matched your search
@@ -50,6 +50,7 @@ import {
|
||||
getClientIp,
|
||||
safeJson,
|
||||
sanitiseZodIssues,
|
||||
isJsonContentType,
|
||||
} from "@/lib/api-response";
|
||||
import { AI_BUDGETS } from "@/lib/ai/ai-budgets";
|
||||
import { requireAiCapability } from "@/lib/ai/capabilities/gate";
|
||||
@@ -152,7 +153,7 @@ export const POST = apiHandler(
|
||||
|
||||
const background = prefersRespondAsync(request);
|
||||
const contentType = request.headers.get("content-type") ?? "";
|
||||
if (contentType.includes("application/json")) {
|
||||
if (isJsonContentType(contentType)) {
|
||||
// One body read for both JSON modes, BEFORE any bucket charge — a
|
||||
// malformed body never needs a refund because nothing was charged yet.
|
||||
const { data: body, error: jsonError } = await safeJson(request, {
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
getClientIp,
|
||||
safeJson,
|
||||
sanitiseZodIssues,
|
||||
isJsonContentType,
|
||||
} from "@/lib/api-response";
|
||||
import { requireAiCapability } from "@/lib/ai/capabilities/gate";
|
||||
import {
|
||||
@@ -92,7 +93,7 @@ export const POST = apiHandler(
|
||||
|
||||
const background = prefersRespondAsync(request);
|
||||
const contentType = request.headers.get("content-type") ?? "";
|
||||
if (contentType.includes("application/json")) {
|
||||
if (isJsonContentType(contentType)) {
|
||||
return handleTextIndex(request, user.id, document, background);
|
||||
}
|
||||
return handleVisionIndex(request, user.id, document, background);
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
getClientIp,
|
||||
safeJson,
|
||||
sanitiseZodIssues,
|
||||
isJsonContentType,
|
||||
} from "@/lib/api-response";
|
||||
import { AI_BUDGETS } from "@/lib/ai/ai-budgets";
|
||||
import { requireAiCapability } from "@/lib/ai/capabilities/gate";
|
||||
@@ -94,7 +95,7 @@ export const POST = apiHandler(
|
||||
|
||||
const background = prefersRespondAsync(request);
|
||||
const contentType = request.headers.get("content-type") ?? "";
|
||||
if (contentType.includes("application/json")) {
|
||||
if (isJsonContentType(contentType)) {
|
||||
return handleTextSuggest(request, user.id, document, background);
|
||||
}
|
||||
return handleVisionSuggest(request, user.id, document, background);
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
getClientIp,
|
||||
safeJson,
|
||||
sanitiseZodIssues,
|
||||
isJsonContentType,
|
||||
} from "@/lib/api-response";
|
||||
import { AI_BUDGETS } from "@/lib/ai/ai-budgets";
|
||||
import { requireAiCapability } from "@/lib/ai/capabilities/gate";
|
||||
@@ -165,7 +166,7 @@ export const POST = apiHandler(
|
||||
background: prefersRespondAsync(request),
|
||||
};
|
||||
const contentType = request.headers.get("content-type") ?? "";
|
||||
if (contentType.includes("application/json")) {
|
||||
if (isJsonContentType(contentType)) {
|
||||
return handleTextSummary(request, ask);
|
||||
}
|
||||
return handleVisionSummary(request, ask);
|
||||
|
||||
Reference in new issue
Block a user