Add NFC normalization to all FormData inputs across 41 server actions
Local Build and Deploy / deploy (push) Successful in 59s

All user-supplied string values from FormData now go through
String.prototype.normalize('NFC') to prevent Unicode homoglyph
attacks and canonicalization bypasses. NFC is idempotent for
already-normalized strings, so this is a pure security improvement
with zero behavioral change for legitimate users.
This commit is contained in:
openhands committed 2026-07-13 12:21:37 +02:00
1 parent e2fc7ea1a4
commit e5ae51bff7
41 files changed
+152 -152

No files matched your search

+6 -6
View File
@@ -30,24 +30,24 @@ export async function saveTheme(formData: FormData): Promise<void> {
for (const mode of ["light", "dark"] as const) {
for (const key of THEME_COLOR_KEYS) {
const dbKey = settingKey(key, mode);
const raw = String(formData.get(dbKey) ?? "").trim();
const raw = String(formData.get(dbKey) ?? "").normalize("NFC").trim();
if (raw && COLOR_RE.test(raw)) await writeSetting(dbKey, raw);
}
}
const radius = String(formData.get("border_radius") ?? "").trim();
const radius = String(formData.get("border_radius") ?? "").normalize("NFC").trim();
if (/^\d{1,3}$/.test(radius)) await writeSetting("border_radius", radius);
// Typography
const font = String(formData.get("font_family") ?? "").trim();
const font = String(formData.get("font_family") ?? "").normalize("NFC").trim();
if (font in FONTS) await writeSetting("font_family", font);
for (const key of HEADING_KEYS) {
const v = String(formData.get(key) ?? "").trim();
const v = String(formData.get(key) ?? "").normalize("NFC").trim();
if (/^\d{1,3}$/.test(v)) await writeSetting(key, v);
}
// Raw custom CSS (staff-trusted; length-capped, ThemeVars injects it as-is).
if (formData.has("custom_css")) {
const cssRaw = String(formData.get("custom_css") ?? "").slice(0, CUSTOM_CSS_MAX);
const cssRaw = String(formData.get("custom_css") ?? "").normalize("NFC").slice(0, CUSTOM_CSS_MAX);
await writeSetting("custom_css", cssRaw);
}
@@ -66,7 +66,7 @@ export async function saveTheme(formData: FormData): Promise<void> {
export async function applyPreset(formData: FormData): Promise<void> {
const staff = await requireStaff();
const name = String(formData.get("preset") ?? "");
const name = String(formData.get("preset") ?? "").normalize("NFC");
// eslint-disable-next-line security/detect-object-injection -- guarded by null check below
const preset = PRESETS[name];
if (!preset) redirect("/admin/theme");