Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5.4 KiB
API-21 – Architekturtest gegen Fehlertext-Leck in HTTP-Antworten – Prüfprotokoll
Basis: integration/gesamtprojekt (Head 2c09253) + Merge von feature/qa-15-phase2c-sicherheitsbehebung (API-20/AUD-08/IAM-23) als Vorbedingung.
Geprüft auf Entwicklungshost 204, Wegwerf-Cluster 17/coredev, seriell über die gemeinsame Sperre. archivdms unberührt.
Umsetzung
internal/archtest(Core) undarchive/internal/archtest(Archive) — je ein eigenständiges Paket, da beide Module getrenntego.mod-Dateien haben und keine gemeinsame Bibliothek existiert (dasselbe Muster wie beiinternal/apierror/internal/httpharden).Scan(root, exempt)durchläuft den Quellbaum pergo/parser/go/ast(keine Typprüfung, Namenskonventionwfürhttp.ResponseWriter— dieselbe Konvention, die im ganzen Code bereits durchgängig verwendet wird, u.a. inapierror.Writeselbst).- Regel 1 (
err-in-response): erkenntw.Write(...),fmt.Fprintf(w, ...),fmt.Fprint(w, ...),fmt.Fprintln(w, ...)undjson.NewEncoder(w).Encode(...), wenn ein Argument rekursiv einenX.Error()-Aufruf enthält. - Regel 2 (
raw-listen-and-serve): erkennthttp.ListenAndServe(...)(Paketfunktion, nichts.ListenAndServe()auf einemhttpharden-erzeugten*http.Server). - Ausnahmen: ein Kommentar
// archtest:allow <Begründung>direkt über der Fundzeile (oder als Trailing-Kommentar auf derselben Zeile) unterdrückt den Fund. Aktuell 0 Einträge in Core und Archive — keine Ausnahme nötig. - Modulgrenzen: Cores Scan schließt
archive/,dms/,mail/aus (eigene Module, DMS hat mit INT-01/INT-15 bereits ein eigenes Muster). Innerhalb jedes Moduls sindinternal/apierrorundinternal/httphardenausgenommen — dort ist das Schreiben des Fehlertexts bzw. der roheListenAndServe-Aufruf die Implementierung der Regel selbst.
Echter Fund, behoben
internal/statuspage/statuspage.go:268 (writeJSONResult) schrieb err.Error() direkt in eine 500-JSON-Antwort — vor API-20/API-21 unentdeckt, weil dieser Fund nicht in der ursprünglichen 116-Stellen-Liste von F-14 war (vermutlich weil erst nach dieser Erhebung entstanden oder übersehen). Zur Laufzeit war das Leck bereits durch apierror.Normalize (API-20, am Rand von httpharden.ListenAndServe) abgefangen — der Statuspage-Dienst läuft nachweislich darüber (cmd/statuspage-devserver/main.go:53). Trotzdem verlangt AC1 den sanktionierten Aufrufpfad auch am Ursprung. Behoben: apierror.Write mit allgemeiner Meldung, der Originalfehler geht ins Log (log.Printf("statuspage: %v", err)). Kein bestehender Test deckte den Fehlerzweig von OverviewHandler/HistoryHandler ab, daher keine Testanpassung nötig.
Prüfungen
Prüfung 1 — absichtlich eingefügter Verstoß bringt den Test zum Scheitern, danach entfernt:
- Zunächst am echten Fund demonstriert: vor der Behebung von
statuspage.goschlugTestNoErrorTextLeaksIntoHTTPResponsesmit exakter Fundstelle (internal/statuspage/statuspage.go:268) fehl, danach bestanden. Das ist der schärfste Nachweis — ein realer, bis dahin unentdeckter Verstoß. - Zusätzlich als dauerhafter, automatisierter Beleg:
TestScanDetectsInjectedViolations(Core und Archive je eigene Kopie) schreibt eine Testfixtur mit 3err-in-response-Verstößen (Write,Fprintf,Encode), 1raw-listen-and-serve-Verstoß und 1 durcharchtest:allowbegründet ausgenommenem Verstoß in ein temporäres Verzeichnis und prüft die exakte Fundzahl inklusive korrekt wirkender Ausnahme. Läuft bei jedemgo test ./...mit — kein manueller Schritt, keine Rückbau-Notwendigkeit.
Prüfung 2 — bestehender Code besteht den Test ohne Änderung außer dokumentierten Ausnahmen:
- Nach Behebung des einen echten Funds:
go build ./...,go vet ./...undgo test -count=1 ./...in Core und Archive vollständig grün, 0 Ausnahmen nötig.
Prüfung 3 — Ende-zu-Ende-Smoke bleibt grün:
scripts/e2e-core-dms.sh:TestCoreE2E_TokenJWKSAndPolicy10/10 Teilfälle bestanden (50,7 s), inklusive der RBAC-10-Modulaktivierungs- und Mandantenbindungsfälle.
QA und Entscheidungen
- Entscheidung — Erkennungsmethode ohne Typprüfung: Ein voller
go/analysis-Typchecker wäre präziser, aber deutlich aufwändiger und würdegolang.org/x/tools/go/analysisals neue Abhängigkeit einführen. Die Namenskonvention (wfürhttp.ResponseWriter) ist im gesamten Code durchgängig eingehalten (Stichprobe über alle Handler in Core/Archive/DMS ergab keine Abweichung), das Risiko falscher Negative ist damit gering und über Codereview auffangbar. - Entscheidung — Scope nur Core und Archive: Wie im Ticket festgelegt (DMS hat eigenes Muster aus INT-01/INT-15, Mail war von API-20 nicht betroffen).
- Entscheidung — kein Rückbau-Schritt für die Nachweise:
TestScanDetectsInjectedViolationsbleibt dauerhaft im Baum (bessere, wiederholbare Absicherung als ein einmaliger manueller Verstoß); die Testfixtur lebt nur int.TempDir(), berührt also nie den echten Quellbaum. - 0 blockierende Iterationen — der einzige Fund war klein und eindeutig behebbar, keine Rückfrage nötig.
Offen
- Keine Typprüfung → theoretische falsche Negative bei unüblicher Variablenbenennung (siehe oben, als Risiko akzeptiert und dokumentiert statt durch einen Typchecker geschlossen).
- Mail-Modul hat weiterhin keinen eigenen Architekturtest (außerhalb des Tickets, siehe API-20-Protokoll: der eine Mail-Fund war eine nicht eingebundene Testfixtur ohne Leck).