Anhang: Bewertungen halten den Stand zu einem Stichtag fest

Die Kapitel 1 bis 12 beschreiben die Architektur, wie sie heute gilt. Dieser Anhang hält fest, was Bewertungen an einem Stichtag gefunden haben. Drei Arten gibt es: die ATAM-Bewertung prüft die Architekturansätze gegen den Utility Tree, das Security-Review prüft den ganzen Bestand nach OWASP Top 10, und das Harness-Audit prüft, ob die Prüfschichten des Harness-Rads wirklich laufen. Ein Bericht ist wie ein Protokoll: Er bleibt, wie er am Stichtag war. Was sich danach ändert, steht in den Kapiteln und im nächsten Bericht.

Jedes Quartal kommt ein datierter Abschnitt dazu

Die drei Bewertungen laufen jedes Quartal gemeinsam, das nächste Mal im Dezember 2026 (ADR-030). Jede Runde bekommt einen eigenen Abschnitt am Ende dieses Anhangs; ältere Runden bleiben unverändert stehen, damit die Geschichte lesbar bleibt. Jeder Bericht liegt in einer eigenen Datei mit Datum im Namen: _atam-JJJJ-MM-TT.adoc, _security-JJJJ-MM-TT.adoc und _harness-audit-JJJJ-MM-TT.adoc. Die Befund-IDs laufen über alle Runden fort (AA, SP, TP, AR und NR für ATAM, S für Security); ein neuer Befund bekommt die nächste freie ID, ein erledigter bleibt mit Datum stehen. Die Übersichtsseite zeigt das Datum des jüngsten ATAM-Berichts und des jüngsten Audits. Ein neuer ATAM-Bericht verlangt im KI-Review den Abschnitt „Architektur (ATAM)“, die anderen beiden nicht.

Die Ergebnisse wandern in die Kapitel 8, 10 und 11

Ein Bericht erklärt, woher ein Befund kommt; gepflegt wird der Befund in den Kapiteln. Neue Bedrohungen (T) und Maßnahmen (M) stehen in 8.1 und 8.2 und verlinken den Bericht. Neue oder anders benotete Qualitätsszenarien ändern den Utility Tree in Kapitel 10. Risiken ®, Risikothemen (RT) und technische Schulden (TD) stehen in Kapitel 11, der Stand der Prüfschichten im Harness-Rad in 8.16.

September 2026: Die erste Runde legt die Baseline

Die erste Runde fand am 24. und 25.09.2026 statt. Sie setzt die IDs, an denen sich alle späteren Runden messen.

Harness-Audit vom 24.09.2026: Jede vorhandene Schicht hat jetzt einen prüfbaren Beleg

Das Audit prüft, ob jede Schicht, die das Harness-Rad (8.16) als „vorhanden“ zählt, wirklich läuft. Am 24.09.2026 hat Claude Code es zum ersten Mal durchgeführt. Seitdem nennt jede vorhandene Schicht in scripts/harness-belege.js maschinenprüfbare Belege, und test/build/harness-rad.test.js bricht, wenn einer fehlt. Repo-Einstellungen sieht kein Test: Secret Scanning mit Push Protection, Dependabot-Warnungen und -Updates, CodeQL Default Setup und die Branch Protection auf main (Belege github:). Sie hat das Audit über die GitHub-API bestätigt.

Die Befunde betrafen den Text, nicht die Schichten, und sind behoben (Commit c48e4c5). Zahlen, die mit jedem PR veralten, etwa die Anzahl der Unit-Tests und Properties, sind aus 8.16 entfernt. Falsch beschrieben waren drei Details: JSON-Schemas werden nicht validiert, doku.yml läuft nur bei PRs mit Doku-Änderungen, und der Umfang des Link-Prüfers stimmte nicht. Seit dem Audit beschreibt 8.16, wie der Stand geprüft wird.

Ergebnisse der ATAM-Bewertung vom 25.09.2026: Das größte Risiko ist ein Kern-Fehler, den jede App sofort erbt

Die Architecture Tradeoff Analysis Method (ATAM) prüft, ob die Architekturansätze die wichtigsten Qualitätsszenarien tragen. Grundlage sind der Utility Tree in Kapitel 10, die Ansätze aus Kapitel 4 und die ADRs aus Kapitel 9. Die Bewertung hat Claude Code am 25.09.2026 aus Doku und Code abgeleitet; der Product Owner hat die Wichtigkeit im Utility Tree bestätigt und die Ladezeit (QS-26) als Nicht-Risiko eingestuft. Die Befunde tragen feste IDs, damit spätere Bewertungen sie fortschreiben können: SP für Sensitivity Points, TP für Tradeoff Points, AR für Risiken und NR für Non-Risks.

Neun Architekturansätze tragen die Szenarien
ID Ansatz ADR Szenarien

AA-1

Statische Site ohne Backend; alles läuft im Build oder im Browser

ADR-001

QS-1, QS-13, QS-26

AA-2

Monorepo, Kern einmal unter /kern/, App-Konfiguration per Dependency Inversion

ADR-012, ADR-013

QS-6, QS-9, QS-25

AA-3

Der Build als Prüfstand: Regelverstöße sind Build-Fehler

ADR-008, ADR-017, ADR-023

QS-2, QS-5, QS-10

AA-4

Deterministischer Zufall aus der Aufgabennummer, llms.txt als Vertrag

ADR-004, ADR-007

QS-3, QS-4, QS-5

AA-5

Zwei-Klick-Video mit lokalem Platzhalter

ADR-005, ADR-019

QS-1

AA-6

Eigene Parser ohne eval, exakte Brüche, eine Rundungsregel, Terme an Prüfstellen

ADR-006, ADR-009

QS-6, QS-7, QS-8, QS-19

AA-7

Eine Zeichenfunktion für Build (Mini-DOM) und Browser

ADR-016

QS-13

AA-8

Inhalts-Hash an jeder Referenz, Basis-URL an einer Stelle

ADR-014, ADR-015

QS-20, QS-21, QS-23

AA-9

Harness: Pflicht-Checks, Property-Tests, Browser-Tests, KI-Review

ADR-026, ADR-027, ADR-029

QS-27, QS-28, alle

Sechs Stellen reagieren empfindlich, an sechs Stellen ziehen Ziele gegeneinander
ID Befund Szenario ADR Risiko

SP-1

zahlantwort.js, termantwort.js und bruch.js entscheiden in jeder App über richtig und falsch. Die Korrektheit aller Apps hängt an wenigen hundert Zeilen Kern.

QS-6, QS-7, QS-19

ADR-013, ADR-006

R-004

SP-2

Termprüfung: 8 Prüfstellen, mindestens 5 gültig, Toleranz 1e-9. Weniger Stellen lassen falsche Terme durch, eine engere Toleranz weist richtige ab.

QS-19

ADR-009

R-012

SP-3

Die Reihenfolge, in der ein Generator Zufallszahlen zieht. Ein neuer zufall()-Aufruf vor einem alten ändert jede Aufgabe jeder Nummer.

QS-3

ADR-004

R-003

SP-4

pruefeExterneRessourcen und pruefeExterneImporte erkennen fremde Hosts an Mustern im Quelltext. Eine URL, die JavaScript erst zur Laufzeit zusammensetzt, sehen sie nicht; e2e/extern.spec.js prüft nur das Laden der Seite, nicht die Übung danach.

QS-1, QS-2

ADR-008, ADR-026

R-031

SP-5

Der Term-Auswerter begrenzt Exponenten auf 64 und kennt nur seine eigenen Tokens. Jede neue Funktion im Parser erweitert die Eingabefläche.

QS-8

ADR-006

R-025

SP-6

axe-core prüfte je App nur eine Kompetenzseite (eineJeApp in e2e/seiten.js). Was eine andere Seite anders machte, etwa ein Baum oder ein Termfeld, prüfte nur der Mensch. Erledigt 25.09.2026: axe prüft jede Kompetenzseite (kompetenzSeiten, R-030).

QS-27

ADR-026

R-030

TP-1

Ein Kern für alle Apps: Eine Korrektur erreicht jede App mit einem Commit (QZ-4, QS-25), ein Fehler ebenso (QZ-3, QS-6, QS-19).

QS-25, QS-6

ADR-012, ADR-013

R-004, R-025

TP-2

Stabile Aufgabennummern (QZ-2) gegen bessere Generatoren (QZ-3, QZ-4). Der Product Owner hat einen Einfrier-Test am 23.09.2026 verworfen.

QS-3

ADR-004

R-003

TP-3

Eigene Parser ohne eval und ohne Bibliothek (QZ-1, QS-8) gegen die Korrektheit eines selbstgebauten Parsers (QZ-3, QS-19).

QS-8, QS-19

ADR-006, ADR-009

R-012, R-025

TP-4

Zwei-Klick-Video (QZ-1, QS-1) gegen einen Klick mehr für das Kind (QZ-5).

QS-1

ADR-005, ADR-019

R-009

TP-5

Pflicht-Review je Kopf-Commit (QZ-3) gegen Tempo beim Bauen neuer Apps (QZ-4): Jeder neue Commit braucht ein neues Review.

QS-28

ADR-027

R-026

TP-6

Statisches Bild im Build (QZ-5, QS-13) gegen eine Zeichenfunktion mit zwei Laufzeiten (QZ-4).

QS-13

ADR-016

R-019

AR-1

Ein Fehler im Kern-Prüfer meldet in allen Apps zugleich eine richtige Antwort als falsch oder eine falsche als richtig. Den zweiten Fall bemerkt niemand.

QS-6, QS-7, QS-19

ADR-013

R-004, R-025

AR-2

Ein falscher Term, der an allen Prüfstellen zufällig mit dem richtigen übereinstimmt, gilt als richtig.

QS-19

ADR-009

R-012

AR-3

Eine fremde Anfrage, die erst nach einer Eingabe entsteht (außer dem Video), fängt kein automatischer Test.

QS-1

ADR-001, ADR-026

R-031

AR-4

Eine Generator-Änderung lässt alte Tutor-Links auf eine andere Aufgabe zeigen.

QS-3

ADR-004

R-003

AR-5

Der Tutor hängt an claude.ai; ändert sich ?q= oder das Laden von tutor.md, reißt die Verbindung.

QS-3, QS-4

ADR-004

R-002

AR-6

Barrierefreiheit prüfte die Automatik nur stichprobenartig; eine neue Seitenform konnte WCAG verletzen, ohne dass ein Check rot wurde. Gemildert 25.09.2026: axe prüft jede Seite; der erste Lauf fand einen Verstoß im Pfadbaum (R-030).

QS-27

ADR-026

R-030

AR-7

Autor und Reviewer posten über dasselbe Konto, oft mit demselben Modell; blinde Flecken teilen beide.

alle

ADR-027

R-026

NR-1

Ohne Backend gibt es keinen Server zum Angreifen und keine personenbezogenen Daten zum Verlieren.

QS-1

ADR-001

–

NR-2

Eine externe Ressource in der Ausgabe bricht den Build; beim Laden prüft e2e/extern.spec.js jede App-Seite im Pflicht-Check browser.

QS-2

ADR-008, ADR-026

–

NR-3

Brüche rechnen exakt; gerundet wird erst beim Vergleich und nur nach den getippten Stellen. Property-Tests decken die Regel ab.

QS-6, QS-7

ADR-006

–

NR-4

Kein eval: der Parser ist eigen, ESLint verbietet eval im Pflicht-Check, Exponenten über 64 sind ungültig.

QS-8

ADR-006, ADR-023

–

NR-5

Die Ladezeit ist kein Risiko: statische Seiten ohne Framework, Web-Fonts und Tracking. Der Product Owner schätzt sie am 25.09.2026 als weit unter 2 Sekunden ein.

QS-26

ADR-001

–

NR-6

Inhalts-Hash und eine Basis-URL: Nach einem Deployment mischen sich keine Modulversionen, ein Umzug ist eine Zeile.

QS-20, QS-21, QS-23

ADR-014, ADR-015

–

NR-7

Die CI-Läufe dauern 13 bis 92 Sekunden bei einem Ziel unter 5 Minuten; Platz für weitere Prüfungen ist da.

QS-28

ADR-026

–

Die Befunde bündeln sich zu drei Risikothemen

Die Befunde fassen drei Risikothemen zusammen: RT-1 „Ein Fehler trifft alle Apps zugleich“, RT-2 „Der Vertrag mit dem Tutor bewegt sich“ und RT-3 „Die Prüfer sehen nur Stichproben“. Den laufenden Stand der Themen und die daraus entstandenen Risiken R-030 und R-031 führt Kapitel 11.

Security-Review vom 25.09.2026: Kein XSS-Pfad, aber Lücken in der Tutor-Allowlist und in der CI-Lieferkette

Das KI-Review jedes PRs prüft OWASP nur am Diff. Am 25.09.2026 hat Claude Code erstmals den ganzen Bestand geprüft: jeden URL-Parameter und jeden localStorage-Wert bis zu jeder DOM-Senke in src/kern/js/ und src/*/js/, die vendorte talkitover.js, die Nunjucks-Vorlagen, den Tutor-Vertrag (T-015), die Workflows unter .github/workflows/ und den Datenschutz. Payloads liefen gegen die gebaute Site im Browser (Playwright). Grundlage ist die OWASP Top 10 (2021), angepasst an eine statische Site mit CI-Lieferkette. Der Product Owner hat S-008 bis S-010 am 25.09.2026 bewusst akzeptiert.

Das Ergebnis: Kein Weg führt von außen zu fremdem Markup oder Skript. Jeder Parameter läuft durch eine Allowlist oder ein Muster und landet nur über textContent, setAttribute oder value im DOM; innerHTML steht nur in TalkItOver, dort mit festen Zeichenketten. Schwach waren zwei Prüfungen, die Angreifer nicht von außen erreichen, sondern nur über einen gemergten PR: die Link-Allowlist für Tutor-Dateien und die Pins der GitHub Actions. Beide sind in diesem Review behoben.

ID OWASP Ort Befund und Nachweis Schwere Stand

S-001

A03

src/kern/js/testablauf.js:32, src/kern/js/aufgabenlink.js:67

modus in MODI und Nachschlagetabellen finden auch geerbte Schlüssel. /prozent/test.html?modus=toString schrieb „function toString() …“ in den Titel, /prozent/grundbegriffe.html?gesucht=constructor&nr=1 warf „VORLAGEN[…][gesucht] is not a function“. Kein Markup möglich (T-016).

niedrig

Behoben: Object.hasOwn, leseVorgaben verwirft Namen aus Object.prototype (M-25)

S-002

A08

.github/workflows/*.yml

16 uses: an verschiebbaren Tags (@v4, @v3), auch im Deploy-Pfad von pages.yml. Wer ein Tag einer Action umhängt, führt Code mit pages: write aus (T-018).

mittel

Behoben: SHA-Pins, Dependabot für Actions (M-27)

S-003

A03 / T-015

lib/pruefungen.js:77-88

Die Tutor-Allowlist ließ durch: https:/evil.example, https:\\evil.example, …/lernapps.github.io/../../fremd/repo (auch %2e), mailto:, nackte Hosts mit Pfad, Port oder Query (evil.com/x, evil.com:8080/x, evil.com?x=1), UNC-Pfade (\\evil.com\x), Issues im eigenen Repository, die jeder nach dem Review ändern kann, und Fork-Commits, die GitHub unter der eigenen Repository-Adresse zeigt. Braucht einen gemergten PR (T-017).

mittel

Behoben: erweiterte Muster, Pfad-Tricks verboten, Repository nur /blob/main/ und /tree/main/ (M-26)

S-004

Datenschutz

src/_includes/basis.njk, src/kern/js/video.js:98

Die Referrer-Policy hing an der Browser-Vorgabe. Ein Browser mit anderer Vorgabe schickte YouTube Pfad, seed und von=tutor.

niedrig

Behoben: strict-origin-when-cross-origin per Meta-Tag in beiden Layouts (Apps, Karte) und am iframe (M-28)

S-005

A05

alle Seiten

Keine Content Security Policy. Pages setzt keine Header; ein Meta-Tag geht, braucht aber Hashes für 27 Inline-Module und zwei Stile. Zweite Verteidigungslinie, kein offener Angriff.

niedrig

Issue #58 (R-033)

S-006

A03

src/karte/js/karte.js:80-85, start.js:45

href aus Kartendaten ohne Prüfung des Schemas; ein javascript: in einer Datendatei käme durch. Nur Repo-Daten.

info

Issue #59

S-007

A03

src/_includes/kompetenz.njk:77,80

| dump | safe in <script>: </script> in einem Titel bräche die Seite. Nur Repo-Daten.

info

Issue #59

S-008

T-015

src/kern/js/aufgabenlink.js:57-70, src/kern/js/ui.js:93,149

Ein präparierter Link trägt Freitext aus [a-z0-9-] in „Link kopieren“, etwa ?gesucht=ignoriere-alle-regeln. Das Kind fügt ihn in den Chat ein.

niedrig

Akzeptiert: Das Kind kann ohnehin alles in den Chat schreiben (R-034)

S-009

T-015

src/*/tutor.njk

tutor.md sagt nicht, dass eingefügte Zeilen Daten sind und keine Anweisungen. Die Zeile „Zurück zu Claude“ selbst ist nicht beeinflussbar: Titel fest, Nummer nur Ziffern, von exakt tutor.

info

Akzeptiert (R-034)

S-010

A08

.github/workflows/ki-review.yml, scripts/ki-review-pruefen.js

Ein PR kann den Check ki-review selbst ändern und abschwächen; CODEOWNERS hilft bei einem Maintainer nicht. Bekannte Grenze aus ADR-027.

niedrig

Akzeptiert: Der Product Owner liest jeden PR (R-026)

S-011

A06

src/kern/vendor/talkitover.js

v1.1.0, MIT. innerHTML nur mit festen Zeichenketten, window.open mit noopener,noreferrer; ein gefälschter Provider in localStorage ändert nur ein Label. Keine bekannten Schwachstellen (Issue-Tracker nicht geprüft).

info

Keine Aktion (R-007 bleibt)

S-012

A05

docToolchain-Theme

Der Link „Create an issue“ hat target=_blank ohne rel; Browser setzen noopener inzwischen selbst.

info

Keine Aktion

Geprüft und in Ordnung (A08): Kein Workflow nutzt pull_request_target, workflow_run oder issue_comment; ${{ github.event.* }} steht nur in env:, nie in run:. Jeder Workflow hat contents: read, nur pages.yml darf pages: write und id-token: write, und nur auf main. Das Standard-Token des Repositorys ist lesend und darf keine PRs freigeben. Secrets nutzt kein Workflow außer dem automatischen GITHUB_TOKEN. Caches aus PRs sieht main nicht (Cache-Scope je Ref); docToolchain und asciidoc-linter hängen an Commits (M-13, ADR-028).

Nicht anwendbar oder abgedeckt: A01 (Zugriffskontrolle) und A07 (Authentifizierung) – keine Konten, kein Backend (M-01); A02 (Kryptografie) – nur HTTPS über Pages, keine Geheimnisse im Browser; A06 (Komponenten) – npm audit, Dependency Review und Dependabot (M-20, M-24); A09 (Logging) – die Apps messen bewusst nichts (8.4), Änderungen zeigt die Git-Historie (M-08); A10 (SSRF) – kein Server, die Karte lädt nur ihre eigene data.json.

Datenschutz: localStorage hält nur Stufen je Kompetenz, den letzten Test (Nummer, Modus, Datum, Stufen), den Video-Merker und den TalkItOver-Provider, keine Namen und keinen Freitext. Nach einer Eingabe geht keine Anfrage an fremde Hosts; YouTube lädt erst nach dem Klick. Der Deploy liefert nur _site/ und die Doku, ohne Quelltext-Maps. R-031 bleibt offen, weil der Build fremde Hosts nur an Mustern erkennt.