detached d7a3f1f

fix(security): validate text preferences with Zod schema before saving

commit
d7a3f1f5afe9e8dde4cb4a99be76867858756b98
author
Kit · · 2026-02-16 11:03 UTC
parent
5794674
signature
SSH UnverifiedSHA256:H91DFuEG4jpk5edzLA4aUKkpv4zv9Q339ipRmkph6Tk The key is unknown
1 file · +16 -9
M src/routes/api/user/text-preferences/+server.ts +16 -9
@@ -3,6 +3,7 @@ import type { RequestHandler } from './$types';
33 import convex from '$lib/db/convex.server';
44 import { DEFAULT_USER_PREFERENCES, type UserTextPreferences } from '$lib/types/text-formatting';
55 import { validateSessionToken } from '$lib/api/session.server';
6+import { sanitizeStyleData } from '$lib/validation/text-formatting';
67
78 const isKitHttpError = (err: unknown): err is { status: number; body: unknown } => {
89 return typeof err === 'object' && err !== null && 'status' in err && 'body' in err;
@@ -73,21 +74,27 @@ export const POST: RequestHandler = async ({ cookies, request }) => {
7374
7475 // Parse request body
7576 const body = await request.json();
76- const { defaultStyle, allowFormatting, maxMessageLength } = body as UserTextPreferences;
77-
78- // Validate required fields
79- if (
80- !defaultStyle ||
81- typeof allowFormatting !== 'boolean' ||
82- typeof maxMessageLength !== 'number'
83- ) {
77+ const { allowFormatting, maxMessageLength } = body as {
78+ allowFormatting: unknown;
79+ maxMessageLength: unknown;
80+ defaultStyle: unknown;
81+ };
82+
83+ // Validate types
84+ if (typeof allowFormatting !== 'boolean' || typeof maxMessageLength !== 'number') {
8485 throw error(400, 'Invalid preferences data');
8586 }
8687
88+ // Validate and sanitize style data through existing Zod schema
89+ const validatedStyle = sanitizeStyleData(body.defaultStyle);
90+ if (!validatedStyle) {
91+ throw error(400, 'Invalid style data');
92+ }
93+
8794 // Save preferences via Convex
8895 const result = await convex.textPreferences.save(
8996 user.id,
90- defaultStyle,
97+ validatedStyle,
9198 allowFormatting,
9299 maxMessageLength
93100 );
@@ -3,6 +3,7 @@ import type { RequestHandler } from './$types';
3 import convex from '$lib/db/convex.server';3 import convex from '$lib/db/convex.server';
4 import { DEFAULT_USER_PREFERENCES, type UserTextPreferences } from '$lib/types/text-formatting';4 import { DEFAULT_USER_PREFERENCES, type UserTextPreferences } from '$lib/types/text-formatting';
5 import { validateSessionToken } from '$lib/api/session.server';5 import { validateSessionToken } from '$lib/api/session.server';
6+import { sanitizeStyleData } from '$lib/validation/text-formatting';
6 7
7 const isKitHttpError = (err: unknown): err is { status: number; body: unknown } => {8 const isKitHttpError = (err: unknown): err is { status: number; body: unknown } => {
8 return typeof err === 'object' && err !== null && 'status' in err && 'body' in err;9 return typeof err === 'object' && err !== null && 'status' in err && 'body' in err;
@@ -73,21 +74,27 @@ export const POST: RequestHandler = async ({ cookies, request }) => {
73 74
74 // Parse request body75 // Parse request body
75 const body = await request.json();76 const body = await request.json();
76- const { defaultStyle, allowFormatting, maxMessageLength } = body as UserTextPreferences;77+ const { allowFormatting, maxMessageLength } = body as {
77-78+ allowFormatting: unknown;
78- // Validate required fields79+ maxMessageLength: unknown;
79- if (80+ defaultStyle: unknown;
80- !defaultStyle ||81+ };
81- typeof allowFormatting !== 'boolean' ||82+
82- typeof maxMessageLength !== 'number'83+ // Validate types
83- ) {84+ if (typeof allowFormatting !== 'boolean' || typeof maxMessageLength !== 'number') {
84 throw error(400, 'Invalid preferences data');85 throw error(400, 'Invalid preferences data');
85 }86 }
86 87
88+ // Validate and sanitize style data through existing Zod schema
89+ const validatedStyle = sanitizeStyleData(body.defaultStyle);
90+ if (!validatedStyle) {
91+ throw error(400, 'Invalid style data');
92+ }
93+
87 // Save preferences via Convex94 // Save preferences via Convex
88 const result = await convex.textPreferences.save(95 const result = await convex.textPreferences.save(
89 user.id,96 user.id,
90- defaultStyle,97+ validatedStyle,
91 allowFormatting,98 allowFormatting,
92 maxMessageLength99 maxMessageLength
93 );100 );

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