Files
Laufzettel/docs/issues/012-platzhalter-namensregeln.md
T
Florian WildeandClaude Opus 5 b59fbb4645
Prüfung / pruefung (pull_request) Canceled after 0s
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (pull_request) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
feat(platzhalter): Aufloesung, Namensregeln und Shell-Maskierung
Aus einer Vorlage wird hier der PowerShell-Befehl, den ein Techniker kopiert und
auf einem Domaenencontroller ausfuehrt. Entsprechend dicht ist die Abdeckung:
810 Tests, und die Maskierung wird **ausgefuehrt** geprueft, nicht gelesen.

- Platzhalter aus Geraet, Lauf, Mitarbeiter und Abteilung, dazu die Variablen
  der Vorlage; ein unbekannter bleibt sichtbar stehen
- `{{ou}}` ist der Eltern-Container des DN — genau der Pfad fuer New-ADUser
- Namensregeln fuer upn, sam, mail und hostname mit Transliteration, `{v}`,
  `{nnn}` und `{kundenkuerzel}`; Kundenregeln ueberlagern die globalen feldweise
- Maskierung je Shell, Skript-Kopie je Abschnitt mit Kommentarzeilen
- `{{generiertes_passwort}}`: 20 Zeichen, bei jeder Anzeige neu, nirgends
  gespeichert; im Druck `[Passwort – nicht gespeichert]`

Der unabhaengige Review fand drei Blocker. Der schwerste war eine
Codeausfuehrung: PowerShell liest auch `“`, `”` und `„` als Begrenzer einer
Zeichenkette, die Maskierung kannte nur das ASCII-Zeichen. Ein Abteilungsname
aus Word — Word wandelt `"` von selbst um — reichte. Gefunden hat es der Review
nicht durch Lesen, sondern indem er sich PowerShell in einen Container geholt
und die Markierungsdatei hat entstehen lassen. Der Test dagegen hatte sich still
uebersprungen; er nimmt jetzt selbst das Abbild und bricht in CI ab.

Dazu: zwei verschiedene Passwoerter in einer Antwort (Anzeige und Skript wurden
getrennt gefuellt), eine Vorlagenvariable, die `{{techniker}}` und `{{kunde}}`
ueberschreiben konnte, und die tragende Annahme der Maskierung — dass der
Platzhalter in Anfuehrungszeichen steht —, die niemand geprueft hat.

Schliesst #012

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-25 15:29:43 +02:00

8.1 KiB
Raw Blame History

#012 M7 – Platzhalter, Namensregeln, Escaping

Status: erledigt Meilenstein: v0.1.0 Labels: phase-1, backend, security Geschätzt: 8 h Tatsächlich: 2 h 30 min

Ziel

Reine, framework-freie Service-Schicht mit dichter Testabdeckung. Hier entstehen die fertigen PowerShell-Befehle, die der Techniker kopiert — und hier darf nichts schiefgehen, weder beim Escaping noch beim generierten Passwort.

Aufgaben

  • Platzhalter-Auflösung {{key}} in Schritt-Titel, Hilfetext und Befehlstext
  • Eingebaut aus Gerät und Lauf: {{hostname}}, {{seriennummer}}, {{inventarnummer}}, {{ticket}}, {{datum}}, {{techniker}}; im Modus DIENSTLEISTUNG zusätzlich {{kunde}} und {{kundenkuerzel}}, im Modus INTERN leer
  • Eingebaut aus dem Mitarbeiter: {{mitarbeiter}} (Alias {{benutzer}}), {{vorname}}, {{nachname}}, {{upn}}, {{sam}}, {{mail}}, {{abteilung}}, {{titel}}, {{vorgesetzter}}, {{vorgesetzter_sam}}, {{dn}}, {{ou}}
  • Aus der gewählten Abteilung: {{ou}} falls nicht vom Mitarbeiter, {{gruppen}} kommagetrennt
  • {{ou}} eines synchronisierten Mitarbeiters ist der Eltern-Container seines dn (alles nach dem ersten Komma)
  • Dazu die in der Vorlage definierten Variablen; unbekannte Platzhalter bleiben sichtbar stehen
  • {{generiertes_passwort}}: 20 Zeichen aus Groß, Klein, Ziffern und Sonderzeichen ohne verwechselbare Zeichen, je Anzeige neu erzeugt, nie gespeichert; Hinweis „Passwort sofort in den Passwortmanager übernehmen“; in der Druckansicht [Passwort – nicht gespeichert]
  • Namensregeln (Einstellung.namensregeln global, im Modus DIENSTLEISTUNG zusätzlich Kunde.namensregeln mit Fallback): upn, sam (höchstens 20 Zeichen), mail, hostname
  • Transliteration (ä→ae, ö→oe, ü→ue, ß→ss, Akzente entfernt), Kleinschreibung, Leerzeichen entfernt, {nnn}-Zähler, {v} für den ersten Buchstaben des Vornamens, {kundenkuerzel}
  • Shell-Escaping je Shell: PowerShell (doppelte Anführungszeichen verdoppeln, Backtick maskieren), Bash, Cmd
  • HTML-Escaping ersetzter Werte; Markdown im Hilfetext sicher gerendert, kein rohes HTML
  • „Alle Befehle als Skript kopieren“ je Abschnitt: alle BEFEHL-Schritte in Reihenfolge, getrennt durch Leerzeilen, je mit # Schritt: <Titel> davor, Zeilenumbruch passend zur Shell

Akzeptanzkriterien

  • Test: „Jörg Müller-Lüdenscheid“ ergibt joerg.mueller-luedenscheid@firma.de und jmuellerluedensch (17 Zeichen — siehe unten, die Klammer im ursprünglichen Text war falsch)
  • Test: {{generiertes_passwort}} erscheint im angezeigten Befehl, steht aber nicht in Schrittergebnis, Kommentar, Protokoll oder der Druckansicht; zwei Aufrufe liefern verschiedene Passwörter
  • Test: ein Wert mit " und ` wird für PowerShell korrekt maskiert und bricht den Befehl nicht
  • Test: unbekannte Platzhalter bleiben als {{key}} stehen
  • Test: {{ou}} wird aus CN=Maria Huber,OU=Buchhaltung,OU=Benutzer,DC=firma,DC=local zu OU=Buchhaltung,OU=Benutzer,DC=firma,DC=local
  • Test: Skript-Kopie liefert die Befehle in Abschnittsreihenfolge mit Kommentarzeilen
  • Test: im Modus INTERN sind {{kunde}} und {{kundenkuerzel}} leer
  • Test: ein Wert mit <script> landet HTML-escaped in der Anzeige

Beim Bauen aufgefallen

  • PowerShell liest auch typografische Anführungszeichen als Begrenzer. “, ” und „ beenden eine "…"-Zeichenkette genauso wie ", ‘, ’, ‚ und ‛ eine '…'-Zeichenkette. Die Maskierung kannte nur das ASCII-Zeichen. Ein Abteilungsname, den jemand aus Word kopiert — Word wandelt " von selbst in “/” —, führte damit fremden Code auf dem Domänencontroller aus. Der Review hat es nicht gelesen, sondern gemessen: er hat sich PowerShell 7 in einem Container geholt und die Markierungsdatei entstehen lassen. Behoben und mit demselben Aufbau gegengeprüft: alle Werte kommen unverändert an, nichts wird ausgeführt.
  • Der pwsh-Test hat sich still übersprungen — dieselbe Falle, die tests/helpers/db.ts für die Datenbank längst zugemacht hat: ein stilles Überspringen sieht genauso grün aus wie ein bestandener Lauf. Er nimmt jetzt das Container-Abbild, wenn kein pwsh da ist, und bricht in CI ab, statt zu schweigen.
  • Zwei verschiedene Passwörter in einer Antwort. befehleDesLaufs hat jeden Schritt zweimal gefüllt — einmal für die Anzeige, einmal für das Skript —, und weil das Passwort bei jedem Füllen neu entsteht, standen auf dem Bildschirm und in der Kopie verschiedene. Der Techniker legt das Konto mit dem einen an und nennt dem Benutzer das andere; weil nichts gespeichert wird, ist es danach unwiederbringlich.
  • Eine Vorlagenvariable konnte jeden eingebauten Platzhalter überschreiben. Eine Variable techniker ließ einen Befehl mit fremdem Namen unterschreiben, eine Variable kunde holte im Modus Intern die Kundenebene zurück, die es dort ausdrücklich nicht gibt. Gesperrt sind jetzt genau fünf Namen; alles andere bleibt erlaubt, weil eine Onboarding-Vorlage nach upn, sam und mail fragen können muss — die Person steht ja noch nicht im Verzeichnis.
  • Die tragende Annahme der Maskierung hat niemand geprüft. Sie gilt nur, wenn der Platzhalter in der Vorlage in Anführungszeichen steht; Write-Host {{vorname}} lässt sich nicht sicher füllen. Das stand als Kommentar da und wurde nirgends eingelöst — der Review hat mit $(touch …) eine Datei entstehen lassen. Jetzt wird es geprüft und der Schritt abgewiesen.
  • Ich bin selbst in die Falle getappt, die ich in #010 repariert habe: $' ist in String.replaceAll ein Rückverweis auf alles hinter dem Treffer. Aus erste\nzweite wurde erste'zweite\n''zweite — ein Befehl, der nicht mehr zugeht. Gefunden hat es mein eigener Test, der gegen echtes Bash läuft.
  • Meine erste HTML-Maskierung war doppelt gemoppelt. React maskiert Textknoten selbst; eine zweite Maskierung hätte „Forschung & Entwicklung“ sichtbar gemacht. Der Beweis für das Akzeptanzkriterium steht jetzt am gerenderten HTML statt in einer eigenen Funktion.
  • {{ou}} bei einem maskierten Backslash im DN. CN=X\\,OU=Y ist ein literaler Backslash im Wert, das Komma ist der Trenner — die Vorausschau hielt es für maskiert, lieferte nichts, und der Aufrufer fiel still auf die Ziel-OU der Abteilung zurück. Das Konto wäre in der falschen OU gelandet.
  • Hostnamen, die Windows ablehnt: ein Nachname „Con“ reichte, damit die Regel CON vorschlägt — das ist die MS-DOS-Konsole. Reservierte Gerätenamen und rein numerische Namen liefern jetzt keinen Vorschlag.
  • ! ist aus dem Passwort geflogen, und ein Passwort fängt nie mit = + - @ an: bei eingeschalteter verzögerter Ersetzung frisst cmd.exe das ! still, und die vier anderen liest eine Tabellenkalkulation am Zellenanfang als Formel.

Eine Entscheidung, die den Auftragstext korrigiert

Das Akzeptanzkriterium nennt jmuellerluedensch und setzt in Klammern „auf 20 Zeichen gekürzt“. Der Name hat 17 Zeichen, und beides zusammen geht nur mit zwei Regeln, die so jetzt im Code stehen: der Bindestrich fällt im sam weg, und eine Regel ohne eigenen {nnn} hält drei Stellen für die spätere Unterscheidung frei (20 − 3 = 17). Der Vorrat wird immer zurückgehalten, auch solange der Name frei ist — sonst hieße derselbe Mensch jmuellerluedenscheid, solange er der Erste ist, und jmuellerluedensch2, sobald ein Zweiter dazukommt. Ein einmal notierter Anmeldename muss auch morgen noch aus der Regel herausfallen.

Offen geblieben

  • Cmd ist nur begründet, nicht gemessen. Es gibt hier kein Windows. Die Ablehnung von ", CR, LF und Null-Byte und die Bewertung von % und ! stehen als Begründung im Code — mit dem ausdrücklichen Hinweis, was daran Annahme ist.
  • Kein Aufrufer. Dieser Meilenstein hat keine Oberfläche; die Funktionen werden von #013 (Abarbeitung) und #014 (Geräteseite) gerufen. Der Hinweis „Passwort sofort in den Passwortmanager übernehmen“ und die Druckansicht [Passwort – nicht gespeichert] sind damit vorbereitet, aber noch nirgends zu sehen.