From e21e478fd1c13dc2d21dbb4ada3613ce650c68b8 Mon Sep 17 00:00:00 2001 From: Harlekin Date: Thu, 3 Sep 2026 23:52:53 +0200 Subject: [PATCH] Vorlagen-JSON abweisen statt still verwerfen, Platzhalter ohne Prototypkette MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Durchgesehen, was mit Text passiert, der nicht als Text ankommt. Die Grundlage war schon in Ordnung: genau ein dangerouslySetInnerHTML mit einer Konstanten, kein eval, kein $queryRawUnsafe, keine Weiterleitung aus einem Adressparameter, Dateien mit nosniff und sandbox-CSP als Anhang statt inline. Drei Ränder waren offen. **Vorlagen.** Das zod-Schema hatte einen festen Satz Feldarten und speicherte das Geprüfte, verwarf unbekannte Schlüssel aber stillschweigend. Mit strictObject fällt ein `typ` statt `type` auf, statt eine Vorlage zu hinterlassen, die nicht tut, was dasteht. **Platzhalter lasen über die Prototypkette.** Beim Durchprobieren gefunden: `constructor` ist ein gültiger Feldschlüssel, die Schlüsselregel erlaubt Kleinbuchstaben. Ein Baustein mit {{constructor}} bekam den Quelltext der geerbten Funktion in den Text geschrieben - kein ausgeführter Code, aber Innenleben in einem Dokument, das weitergereicht wird. Jetzt Object.hasOwn. **Farbmarken aus der Datenbank** landen als Name einer CSS-Variablen in einem style-Attribut. Heute setzt das nur der Seed, und eine zweite Deklaration liesse sich darüber ohnehin nicht einschleusen - aber der Kommentar sagt „im Adminbereich pflegbar", und was aus der Datenbank in ein Stilattribut geht, wird geprüft. Dreizehn Prüfungen, die es mit echten Versuchen probieren. Bösartiger Text bleibt dabei ausdrücklich erlaubt: eine Feldbeschriftung darf heissen und sieht dann genau so aus. Verboten ist, dass daraus ein Element wird. Nebenbei: eine Vorlagenänderung stand im Zugriffsprotokoll als roles.update. --- web/app/(app)/admin/vorlagen/actions.ts | 2 +- web/components/StampMark.tsx | 26 +++++ web/components/StatusTags.tsx | 4 +- web/lib/audit/write.ts | 1 + web/lib/i18n/admin.ts | 2 + web/lib/templates/fields.ts | 8 +- web/lib/templates/schema.ts | 11 +- web/tests/einschleusen.test.ts | 149 ++++++++++++++++++++++++ 8 files changed, 198 insertions(+), 5 deletions(-) create mode 100644 web/tests/einschleusen.test.ts diff --git a/web/app/(app)/admin/vorlagen/actions.ts b/web/app/(app)/admin/vorlagen/actions.ts index c3c43c8..6197d1b 100644 --- a/web/app/(app)/admin/vorlagen/actions.ts +++ b/web/app/(app)/admin/vorlagen/actions.ts @@ -67,7 +67,7 @@ export async function saveTemplate(input: unknown): Promise { await writeAudit(tx, { actorId: user.id, - action: 'roles.update', + action: 'template.update', targetType: 'DocumentTemplate', targetId: `${key}:${version}`, clearanceAtTime: user.permissions.clearanceLevel, diff --git a/web/components/StampMark.tsx b/web/components/StampMark.tsx index 185f34b..b9d99ae 100644 --- a/web/components/StampMark.tsx +++ b/web/components/StampMark.tsx @@ -11,6 +11,32 @@ export function stampColor(level: number): string { return STAMP_COLOR[level] ?? STAMP_COLOR[0]! } +/** + * Die Farbmarken, die aus der Datenbank kommen duerfen. + * + * `ClearanceLevelConfig.colorToken` und `PersonnelStatusTag.colorToken` sind + * Daten und im Adminbereich pflegbar. Sie landen als Name einer CSS-Variablen + * in einem `style`-Attribut, und was aus der Datenbank in ein Stilattribut + * geht, wird geprueft — nicht weil es heute jemand setzen koennte, sondern + * weil es morgen jemand koennen soll. Ein unbekannter Wert faellt auf die + * neutrale Marke zurueck, statt eine Variable zu erfinden, die es nicht gibt. + */ +export const COLOR_TOKENS = [ + 'stamp-0', + 'stamp-1', + 'stamp-2', + 'stamp-3', + 'stamp-4', + 'stamp-5', +] as const + +export type ColorToken = (typeof COLOR_TOKENS)[number] + +export function colorVariable(token: string | null | undefined): string { + const erlaubt = (COLOR_TOKENS as readonly string[]).includes(token ?? '') + return `var(--color-${erlaubt ? token : 'stamp-0'})` +} + /** * Stempelmarke oben rechts am Objekt. Der Stempelcharakter kommt aus dem * schmalen Schnitt, der Rahmenlinie und der Farbe, nicht aus Grossbuchstaben. diff --git a/web/components/StatusTags.tsx b/web/components/StatusTags.tsx index 205b710..e0ca1ce 100644 --- a/web/components/StatusTags.tsx +++ b/web/components/StatusTags.tsx @@ -1,3 +1,5 @@ +import { colorVariable } from '@/components/StampMark' + /** * Status einer Person als Etiketten. * @@ -20,7 +22,7 @@ export function StatusTags({