Reports/Export builder (CSV/XLSX), plus a security fix pass

Adds the Berichte export pipeline (/api/export/{report,events,employees})
with shared CSV/XLSX writers in lib/export.ts and lib/reports-data.ts.

Security pass alongside it: sanitize .or() search terms against PostgREST
filter injection, sanitize spreadsheet cells against CSV/Excel formula
injection, stop leaking raw DB error messages to clients, harden the
service-role client with server-only, add baseline security headers, and
bump the vulnerable nested postcss via an override.
This commit is contained in:
2026-07-15 20:34:27 +02:00
parent 901c5c426e
commit f96773da0f
21 changed files with 2323 additions and 279 deletions

View File

@@ -5,6 +5,7 @@ import { Avatar } from "@/components/ui/Avatar";
import { StatusChip } from "@/components/ui/StatusChip";
import { fmtDate } from "@/lib/format";
import { breadcrumbFor, loadOrgMaps } from "@/lib/org";
import { sanitizeIlikeTerm } from "@/lib/supabase/query";
import { createClient } from "@/lib/supabase/server";
import type { EmploymentStatus } from "@/lib/supabase/types";
@@ -49,7 +50,8 @@ export default async function EmployeesPage({ searchParams }: EmployeesPageProps
if (/^\d+$/.test(q)) {
query = query.eq("personnel_number", Number(q));
} else {
query = query.or(`first_name.ilike.%${q}%,last_name.ilike.%${q}%,job_title.ilike.%${q}%`);
const term = sanitizeIlikeTerm(q);
query = query.or(`first_name.ilike.%${term}%,last_name.ilike.%${term}%,job_title.ilike.%${term}%`);
}
}
if (params.division) query = query.eq("division_id", params.division);