# KI-generierten Code prüfen: Warum grüne Tests nicht reichen

> Canonical: https://vxi.me/de/posts/vibe-coding-ai-code-review-evidence-gates/
> Locale: de-de
> Published: 2026-10-25
> Updated: 2026-10-25
> Markdown: https://vxi.me/de/posts/vibe-coding-ai-code-review-evidence-gates.md

<figure class="ai-review-evidence-hero not-prose" lang="de-de">
<picture><source media="(prefers-reduced-motion: reduce) and (max-width: 640px)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-evidence-gates.mobile.still.de-de.svg" type="image/svg+xml"><source media="(prefers-reduced-motion: reduce)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-evidence-gates.still.de-de.svg" type="image/svg+xml"><source media="(max-width: 640px)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-evidence-gates.mobile.de-de.svg" type="image/svg+xml"><img src="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-evidence-gates.de-de.svg" alt="Drei verschiedene Codeversionen bestanden 8/8, 10/10 und 12/12 Top-Level-Tests. Unabhängige Reviews fanden vier Fehlerklassen; vier separate Proben wurden abgewehrt. Nur offline." width="1200" height="700" loading="eager" fetchpriority="high" decoding="async" style="display:block;width:100%;height:auto"></picture>
<figcaption style="font-size:0.8rem;line-height:1.5;opacity:.78;margin-top:.35rem">Drei Codeversionen und ihre unabhängigen Review-Ergebnisse. Grüne Tests sind keine Produktionsfreigabe.</figcaption>
</figure>

Vibe Coding beschleunigt die Codeproduktion, aber nicht automatisch die Review-Arbeit. Ein LLM kann schnell einen ordentlich wirkenden Diff liefern. Eine überzeugende Erklärung oder eine grüne Testsuite beweist jedoch nicht, dass die Implementierung produktionsreif ist.

Der Fall ist ein Build-Time-Validator für animierte Terminal-SVGs in diesem Blog-System. Er prüft eingeschränkte XML/CSS-Regeln, Manifest-Pfade und SHA-256 sowie sichtbare Demo-Bilder in acht Sprachen. Ein festes canonical SVG dient als positives Fixture. Alle vier Fehlerklassen wurden vor dem Merge in isolierten Tests gefunden; die Evidenz gilt nur für den Offline-Validator.

Die ursprüngliche Suite bestand **8/8 Tests**, doch unabhängiges Review fand zwei unsichere Akzeptanzen. Nach dem ersten Fix bestanden **10/10**, danach wurden zwei weitere Fehler entdeckt. Die finale Version bestand **12/12 Top-Level-Tests**; vier zusätzliche Angriffsproben wurden separat ausgeführt und alle abgewiesen. Drei JavaScript-Dateien bestanden node --check.

Ich fixiere zuerst Umfang und Invarianten, baue minimale Gegenbeispiele, protokolliere RED nur bei einem tatsächlich beobachteten Fehler, prüfe positive und negative Fälle am exakten Commit unabhängig und entscheide dann nach Risiko. Logiktests ersetzen weder Build noch Plattform- oder Laufzeitprüfung. Für riskante Änderungen genügt eine Evidenzkarte mit Umfang, Base/Head-Commit, Invarianten, positivem Fixture, Gegenbeispiel, echtem Befehl und Umgebung, offenen Grenzen und Ergebnis. Der Validator ist eine Build-Komponente im Blog-Repository, kein separat paketiertes universelles CLI. Review-Zeitersparnis wurde nicht gemessen.

Das öffentliche Repository enthält nur bereinigte synthetische Gegenbeispiele und ausführbare Lehrtests – weder den Produktionsvalidator dieses Blogsystems noch dessen Fixes oder interne Architektur. Die Folge 8/8 → 10/10 → 12/12 stammt aus der ursprünglichen Offline-Prüfung; die öffentlichen Tests reproduzieren nur vier Fehlermuster und bilden die Produktionsimplementierung nicht nach.

<a class="gh-repo-pill" href="https://github.com/vxime/ai-code-review-counterexamples/tree/74d9f12331ef4d015bf882a6e09d66e59434368b" target="_blank" rel="noopener noreferrer"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" width="18" height="18"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645.12.54 2.873.12 3.176.765.84 1.23 1.91.81 3.22.61 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606.21 2.896.015 3.286C20.565 22.092 24 17.592 24 12.297"/></svg><span>Bereinigte Beispiele und ausführbare Tests</span></a>

## Warum akzeptierten 8/8 bestandene Tests trotzdem ungültige Eingaben?

Eine grüne Suite zeigt nur, dass aktuelle Eingaben aktuelle Assertions erfüllen. Sie beweist nicht, dass jede ungültige Eingabe abgelehnt wird.

Das erste Gegenbeispiel setzte ein erlaubtes Bild und einen nicht genehmigten Verweis auf eine Ressource eines anderen Artikels in dieselbe Zeile. Die alte Logik fand ein erlaubtes Bild-Tag und ließ die ganze Zeile durch. Unabhängiges Review erstellte eine echte SVG unter einem anderen Slug und ergänzte den Markdown-Verweis neben dem gültigen Bild in acht Sprachversionen. Die Datei existierte bewusst, um einen Fehlalarm wegen fehlender Zieldatei auszuschließen. Die alte Implementierung meldete weiterhin Erfolg mit posts_checked=8.

~~~html
<img src="/assets/terminal-demo.svg" alt="Terminal demo" width="356" height="137" /> ![foreign](../foreign/terminal-demo.svg)
~~~

Das zweite Gegenbeispiel war ein Dateisystem-Rennen: Der geprüfte Pfad muss nicht die später geöffnete Datei sein. Ein Elternverzeichnis wurde beim Öffnen ausgetauscht; außerhalb lag eine canonical SVG mit passendem SHA-256. Nach dem Austausch gegen einen Symlink las die alte Implementierung die externe Datei und akzeptierte sie. Umgangen wurde die Herkunftsbeschränkung, nicht die Hash-Prüfung.

Der finale Linux-Pfad öffnet die vertrauenswürdige Checkout-Wurzel und löst Komponenten relativ zu bereits geöffneten Elternverzeichnis-Deskriptoren auf. Zwischenkomponenten müssen Verzeichnisse sein und nutzen O_NOFOLLOW; auch die finale Datei darf kein Symlink sein. Typ, Linkzahl und Größe werden geprüft, Device, Inode und Größe vor und nach dem Lesen verglichen, dann der Digest geprüft. Linux /proc/self/fd wird verwendet; ohne procfs wird geschlossen abgebrochen. Die Garantie setzt einen vertrauenswürdigen Checkout und verfügbares procfs voraus, nicht beliebige Angreifer mit gleicher UID oder Host-Rechten. Siehe [Linux openat2(2)](https://man7.org/linux/man-pages/man2/openat2.2.html).

## Runde zwei: Der Hash stimmt, aber der Inhalt ist unsichtbar

Nach dem ersten Fix bestanden **10/10 Tests**. Der dritte Fehler betrifft Reduced Motion: SVG-Frames starten transparent und werden durch die Animation sichtbar. Wenn die Reduced-Motion-Regel nur die Animation stoppt, ohne den letzten Frame sichtbar zu machen, bleibt das Ergebnis leer.

~~~css
.f { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  #f0 { animation: none; opacity: 1; }
  #f3 { animation: none; } /* letzter Frame bleibt transparent */
}
~~~

Der Test berechnete SHA-256, Größe, content-addressed Dateinamen und canonical manifest neu. Er prüfte den Vertrag, dass das Endergebnis sichtbar bleiben muss. Siehe [MDN zu prefers-reduced-motion](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/%40media/prefers-reduced-motion).

Der vierte Fehler kam aus Markdown. Die alte Regex zählte Bild-Tags auch innerhalb von HTML-Kommentaren. Wird das einzige Bild auskommentiert, bleibt der Tag im Quelltext, ist aber nicht sichtbar. Nach den [CommonMark-0.31.2-Regeln für HTML-Blöcke](https://spec.commonmark.org/0.31.2/#html-blocks) ist ein kommentierter Tag kein sichtbares Bild.

~~~html
<!-- <img src="/assets/demo.svg" alt="Terminal demo" /> -->
~~~

<figure class="ai-review-counterexample not-prose" lang="de-de">
<picture><source media="(prefers-reduced-motion: reduce) and (max-width: 640px)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-counterexample.mobile.still.de-de.svg" type="image/svg+xml"><source media="(prefers-reduced-motion: reduce)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-counterexample.still.de-de.svg" type="image/svg+xml"><source media="(max-width: 640px)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-counterexample.mobile.de-de.svg" type="image/svg+xml"><img src="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-counterexample.de-de.svg" alt="Nach 10/10 bestandenen Tests fand ein unabhängiges Review ein Bild-Tag im HTML-Kommentar, das fälschlich als sichtbar galt. Finale Version: 12/12 und vier separate Proben bestanden." width="1200" height="700" loading="lazy" decoding="async" style="display:block;width:100%;height:auto"></picture>
<figcaption style="font-size:0.8rem;line-height:1.5;opacity:.78;margin-top:.35rem">Grüne Tests übersehen Gegenbeispiele: Ein Bild-Tag im Kommentar ist kein sichtbares Bild.</figcaption>
</figure>

## Testergebnisse an exakte Versionen binden

| Codeversion | Hauptsuite | Unabhängiges Review danach |
| --- | --- | --- |
| Ausgangsversion | 8/8 bestanden | SEC-01 und SEC-02 unsicher akzeptiert |
| Version nach dem ersten Fix | 10/10 bestanden | SEC-03 und SEC-04 unsicher akzeptiert |
| Finale offline geprüfte Version | 12/12 Top-Level-Tests bestanden | Vier separate Angriffsproben abgewiesen |

Die finale Suite enthält Regressionstests für SEC-01 bis SEC-04. Die vier zusätzlichen Proben zählen nicht zu 12/12 und dürfen nicht zu 16/16 addiert werden. Drei JavaScript-Dateien bestanden node --check, was Syntax und nicht Verhalten prüft.

Die letzten beiden Regressionstests wurden vor dem Quellcode-Fix committed, aber die vollständige Suite lief nicht auf dem reinen Test-Commit. Daher gibt es keinen belegten vollständigen 10/12-RED-Lauf. Belegt sind die Reproduktion durch unabhängiges Review und die erfolgreichen Tests der finalen Version.

Erst nach stabiler Verhaltensevidenz folgt die Komplexitätsprüfung: Welche Invariante schützt ein Helper oder Zweig? Ist Logik doppelt? Gibt es echte Aufrufer? Kürzerer Code ist nicht automatisch einfacher; das Entfernen eines Pfadschutzes kann ein geschlossenes Rennen zurückbringen.


<!-- vxi-evidence-panel: ai-code-review-counterexamples -->

Verwandte Beiträge: [Mutation Testing](/posts/ai-generated-tests-mutation-testing/), [OpenQodex' Pre-Push-Review-Fail-Open-Grenze](/posts/openqodex-prepush-review-fail-open/) und [Diffles Review-Kommentaranker](/posts/diffle-git-diff-comment-anchoring/).

## Was fehlt zwischen bestandenen Tests und Produktionsfreigabe?

Die finale unabhängige Prüfung umfasste den Offline-Validator unter Node.js v22.16.0 auf Linux x64: 12/12 Tests bestanden, vier zusätzliche Angriffsproben abgewiesen, canonical SVG/manifest und acht Sprachbindungen bestanden, drei JavaScript-Dateien durch node --check.

Das stützt die begrenzte Aussage, dass vier bekannte falsche Akzeptanzen in dieser Umgebung und unter diesen Regeln behoben wurden. Es beweist nicht die Produktionsreife des gesamten Blogs. Native macOS-Sicherheit gegen bösartige parallele Dateisystem-Schreiber bleibt ungeprüft; vollständiger Site-Build und Laufzeitprüfung lagen außerhalb der Offline-Tests.

Eine reale Freigabe muss nach Risiko geschichtet sein: Unit-Tests prüfen Verhalten, statische Checks Muster, Build und Integration die Zusammensetzung, Plattform- und Laufzeittests die Deployment-Grenzen. Bleibt eine wichtige Grenze ungeprüft, ist ein lokaler Erfolg keine vollständige Produktionsfreigabe.

Verträge und Gegenbeispiele zeigen, welchem Verhalten man misstrauen sollte; Tests des exakten Stands und unabhängige Evidenz entscheiden, wo tiefer geprüft wird. Das ersetzt menschliches Urteil nicht, sondern macht aus „sieht gut aus“ eine überprüfbare Review-Entscheidung.

## Verification context

- Level: first-party
- Verified: 2026-10-25T00:00:00.000Z
- Environment: Node.js v22.16.0 / Linux x64
- Evidence mode: mixed
- Public repository: https://github.com/vxime/ai-code-review-counterexamples
- Evidence commit: 74d9f12331ef4d015bf882a6e09d66e59434368b
- Evidence receipt: https://vxi.me/evidence/vibe-coding-ai-code-review-evidence-gates.json
