Skip to content

Commit 7e7428f

Browse files
committed
fix: validate payloads upfront, guard against array inputs clearing flags
1 parent 202718d commit 7e7428f

2 files changed

Lines changed: 30 additions & 15 deletions

File tree

apps/webapp/app/routes/admin.api.orgs.$orgId.feature-flags.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
6969

7070
let featureFlags: typeof Prisma.JsonNull | Record<string, unknown>;
7171

72-
if (body === null || (typeof body === "object" && Object.keys(body).length === 0)) {
72+
if (body === null || (typeof body === "object" && !Array.isArray(body) && Object.keys(body).length === 0)) {
7373
featureFlags = Prisma.JsonNull;
7474
} else {
7575
const validationResult = validatePartialFeatureFlags(body as Record<string, unknown>);

apps/webapp/app/routes/admin.feature-flags.tsx

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,28 +58,43 @@ export const action = async ({ request }: ActionFunctionArgs) => {
5858
}
5959

6060
const body = await request.json();
61-
const { flags: newFlags } = body as { flags: Record<string, unknown> };
6261

62+
if (
63+
typeof body !== "object" ||
64+
body === null ||
65+
Array.isArray(body) ||
66+
typeof body.flags !== "object" ||
67+
body.flags === null ||
68+
Array.isArray(body.flags)
69+
) {
70+
return json({ error: "Invalid payload" }, { status: 400 });
71+
}
72+
73+
const newFlags = body.flags as Record<string, unknown>;
74+
const validationResult = validatePartialFeatureFlags(newFlags);
75+
if (!validationResult.success) {
76+
return json(
77+
{ error: "Invalid feature flags", details: validationResult.error.issues },
78+
{ status: 400 }
79+
);
80+
}
81+
82+
const validatedFlags = validationResult.data as Record<string, unknown>;
6383
const controlTypes = getAllFlagControlTypes();
6484
const catalogKeys = Object.keys(controlTypes);
6585

66-
// Split keys into upserts and deletes
6786
const keysToDelete: string[] = [];
6887
const upsertOps: ReturnType<typeof prisma.featureFlag.upsert>[] = [];
6988

7089
for (const key of catalogKeys) {
71-
if (key in newFlags) {
72-
const result = validatePartialFeatureFlags({ [key]: newFlags[key] });
73-
if (result.success) {
74-
const validatedValue = (result.data as Record<string, unknown>)[key];
75-
upsertOps.push(
76-
prisma.featureFlag.upsert({
77-
where: { key },
78-
create: { key, value: validatedValue as any },
79-
update: { value: validatedValue as any },
80-
})
81-
);
82-
}
90+
if (key in validatedFlags) {
91+
upsertOps.push(
92+
prisma.featureFlag.upsert({
93+
where: { key },
94+
create: { key, value: validatedFlags[key] as any },
95+
update: { value: validatedFlags[key] as any },
96+
})
97+
);
8398
} else {
8499
keysToDelete.push(key);
85100
}

0 commit comments

Comments
 (0)