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

@@ -1,13 +1,12 @@
import "server-only";
import { createClient as createSupabaseClient } from "@supabase/supabase-js";
import type { Database } from "./types";
// Service-role client: bypasses RLS entirely. Server-only — never import this
// from a Client Component or anything bundled for the browser.
// from a Client Component or anything bundled for the browser. The
// "server-only" import makes an accidental client-side import a build error
// instead of a runtime one.
export function createAdminClient() {
if (typeof window !== "undefined") {
throw new Error("createAdminClient must never be called in the browser");
}
return createSupabaseClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!,

9
lib/supabase/query.ts Normal file
View File

@@ -0,0 +1,9 @@
// PostgREST's .or() filter syntax treats "," "(" and ")" as structural
// delimiters between conditions. A raw user-supplied search term containing
// them (e.g. from a search box or ?q= param) can break out of the intended
// column conditions and append arbitrary extra filters to the query. Strip
// them before interpolating — harmless for real name/title searches, which
// never legitimately contain them.
export function sanitizeIlikeTerm(term: string): string {
return term.replace(/[,()]/g, "");
}