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

+11 -11
View File
@@ -14,7 +14,7 @@ import { logServerError } from "@/lib/server-log";
/** Parse an UnsignedInt form value, returning null when blank/invalid/negative. */
function optUInt(formData: FormData, key: string): number | null {
const raw = String(formData.get(key) ?? "").trim();
const raw = String(formData.get(key) ?? "").normalize("NFC").trim();
if (raw === "") return null;
const n = Number(raw);
if (!Number.isFinite(n) || n < 0) return null;
@@ -30,7 +30,7 @@ function reqUInt(formData: FormData, key: string): number {
export async function createShopArticle(formData: FormData): Promise<void> {
const staff = await requireStaff();
const name = String(formData.get("name") ?? "")
const name = String(formData.get("name") ?? "").normalize("NFC")
.trim()
.slice(0, 255);
if (!name) return;
@@ -40,13 +40,13 @@ export async function createShopArticle(formData: FormData): Promise<void> {
const created = await prisma.websiteShopArticles.create({
data: {
name,
info: String(formData.get("info") ?? "")
info: String(formData.get("info") ?? "").normalize("NFC")
.trim()
.slice(0, 255),
iconUrl: String(formData.get("icon") ?? "")
iconUrl: String(formData.get("icon") ?? "").normalize("NFC")
.trim()
.slice(0, 255),
color: String(formData.get("color") ?? "")
color: String(formData.get("color") ?? "").normalize("NFC")
.trim()
.slice(0, 255),
costs: reqUInt(formData, "costs"),
@@ -55,7 +55,7 @@ export async function createShopArticle(formData: FormData): Promise<void> {
duckets: optUInt(formData, "duckets"),
diamonds: optUInt(formData, "diamonds"),
badges:
String(formData.get("badges") ?? "")
String(formData.get("badges") ?? "").normalize("NFC")
.trim()
.slice(0, 255) || null,
position: reqUInt(formData, "position"),
@@ -85,7 +85,7 @@ export async function updateShopArticle(formData: FormData): Promise<void> {
const id = formPositiveBigInt(formData, "id");
if (!id) return;
const name = String(formData.get("name") ?? "")
const name = String(formData.get("name") ?? "").normalize("NFC")
.trim()
.slice(0, 255);
if (!name) return;
@@ -95,13 +95,13 @@ export async function updateShopArticle(formData: FormData): Promise<void> {
where: { id },
data: {
name,
info: String(formData.get("info") ?? "")
info: String(formData.get("info") ?? "").normalize("NFC")
.trim()
.slice(0, 255),
iconUrl: String(formData.get("icon") ?? "")
iconUrl: String(formData.get("icon") ?? "").normalize("NFC")
.trim()
.slice(0, 255),
color: String(formData.get("color") ?? "")
color: String(formData.get("color") ?? "").normalize("NFC")
.trim()
.slice(0, 255),
costs: reqUInt(formData, "costs"),
@@ -110,7 +110,7 @@ export async function updateShopArticle(formData: FormData): Promise<void> {
duckets: optUInt(formData, "duckets"),
diamonds: optUInt(formData, "diamonds"),
badges:
String(formData.get("badges") ?? "")
String(formData.get("badges") ?? "").normalize("NFC")
.trim()
.slice(0, 255) || null,
position: reqUInt(formData, "position"),