detached 00a75bb

fix(security): block state-changing requests with missing Origin header

commit
00a75bb372cd4f5f3b187f74fc5a8fc3b55bbcbc
author
Kit · · 2026-02-16 11:02 UTC
parent
c31e89e
signature
SSH UnverifiedSHA256:H91DFuEG4jpk5edzLA4aUKkpv4zv9Q339ipRmkph6Tk The key is unknown
1 file · +20 -13
M src/hooks.server.ts +20 -13
@@ -86,21 +86,28 @@ export const handle: Handle = async ({ event, resolve }) => {
8686 const origin = event.request.headers.get('origin');
8787 const host = event.request.headers.get('host');
8888
89- // Block cross-origin requests (origin header is present but doesn't match host)
90- if (origin) {
91- try {
92- const originUrl = new URL(origin);
93- if (originUrl.host !== host) {
94- log.warn('CSRF check failed - origin mismatch', {
95- origin: originUrl.host,
96- host
97- });
98- return new Response('Forbidden', { status: 403 });
99- }
100- } catch {
101- log.warn('CSRF check failed - invalid origin', { origin });
89+ if (!origin) {
90+ // Browsers always send Origin on state-changing requests.
91+ // Missing Origin means non-browser client (curl, scripts).
92+ log.warn('CSRF check failed - missing origin header', {
93+ method: event.request.method,
94+ path: event.url.pathname
95+ });
96+ return new Response('Forbidden', { status: 403 });
97+ }
98+
99+ try {
100+ const originUrl = new URL(origin);
101+ if (originUrl.host !== host) {
102+ log.warn('CSRF check failed - origin mismatch', {
103+ origin: originUrl.host,
104+ host
105+ });
102106 return new Response('Forbidden', { status: 403 });
103107 }
108+ } catch {
109+ log.warn('CSRF check failed - invalid origin', { origin });
110+ return new Response('Forbidden', { status: 403 });
104111 }
105112 }
106113
@@ -86,21 +86,28 @@ export const handle: Handle = async ({ event, resolve }) => {
86 const origin = event.request.headers.get('origin');86 const origin = event.request.headers.get('origin');
87 const host = event.request.headers.get('host');87 const host = event.request.headers.get('host');
88 88
89- // Block cross-origin requests (origin header is present but doesn't match host)89+ if (!origin) {
90- if (origin) {90+ // Browsers always send Origin on state-changing requests.
91- try {91+ // Missing Origin means non-browser client (curl, scripts).
92- const originUrl = new URL(origin);92+ log.warn('CSRF check failed - missing origin header', {
93- if (originUrl.host !== host) {93+ method: event.request.method,
94- log.warn('CSRF check failed - origin mismatch', {94+ path: event.url.pathname
95- origin: originUrl.host,95+ });
96- host96+ return new Response('Forbidden', { status: 403 });
97- });97+ }
98- return new Response('Forbidden', { status: 403 });98+
99- }99+ try {
100- } catch {100+ const originUrl = new URL(origin);
101- log.warn('CSRF check failed - invalid origin', { origin });101+ if (originUrl.host !== host) {
102+ log.warn('CSRF check failed - origin mismatch', {
103+ origin: originUrl.host,
104+ host
105+ });
102 return new Response('Forbidden', { status: 403 });106 return new Response('Forbidden', { status: 403 });
103 }107 }
108+ } catch {
109+ log.warn('CSRF check failed - invalid origin', { origin });
110+ return new Response('Forbidden', { status: 403 });
104 }111 }
105 }112 }
106 113

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 8

↑↓ moveenter openesc close