docs(lessons): security-E2E must drive every collector default path, not just the aggregate hub

Captures the two redaction leaks from the remote-diagnostics build: a hoster-
returned opaque token surviving because value-scrub only covers stored config
secrets, and the get_config_redacted/get_queue_state default paths skipping
pattern-scrub entirely. The discriminator the first E2E missed, plus the rule:
exercise each collector with its DEFAULT args and seed fixtures with a NON-config
secret so you test pattern-scrub, not value-scrub by accident.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Administrator 2026-06-19 17:49:14 +02:00
parent b423357a2c
commit 20b53f9587

View File

@ -108,3 +108,11 @@
**Symptom:** Single-Instance-Lock (G) über 3 Turns hinweg in Prosa „dem User vorgelegt"; User hat 0× darauf reagiert (stattdessen Hunt re-run). Drohte ein 4. Mal als vage Sorge im Summary aufzutauchen. **Symptom:** Single-Instance-Lock (G) über 3 Turns hinweg in Prosa „dem User vorgelegt"; User hat 0× darauf reagiert (stattdessen Hunt re-run). Drohte ein 4. Mal als vage Sorge im Summary aufzutauchen.
**Regel:** Re-Bestätigung dass ein Bug ECHT ist ≠ Zustimmung zu einer Verhaltensänderung. Die offene Frage bei user-facing Changes ist nicht „ist es real" sondern „will der User dieses Verhalten" (hier: startet er je absichtlich 2 Instanzen?). Eine vierte Prosa-Erwähnung ist „ambient worry", keine Entscheidung. **Regel:** Re-Bestätigung dass ein Bug ECHT ist ≠ Zustimmung zu einer Verhaltensänderung. Die offene Frage bei user-facing Changes ist nicht „ist es real" sondern „will der User dieses Verhalten" (hier: startet er je absichtlich 2 Instanzen?). Eine vierte Prosa-Erwähnung ist „ambient worry", keine Entscheidung.
**Wie anwenden:** Nach dem Release EINE AskUserQuestion feuern (user-facing Change + ggf. weitere geparkte Punkte als Multi-Select gebündelt) ODER in EINER Zeile sagen „geparkt bis dein Wort" und aufhören es zu wiederholen. Nie denselben user-facing Flag 3+ Mal als Sorge raisen. **Wie anwenden:** Nach dem Release EINE AskUserQuestion feuern (user-facing Change + ggf. weitere geparkte Punkte als Multi-Select gebündelt) ODER in EINER Zeile sagen „geparkt bis dein Wort" und aufhören es zu wiederholen. Nie denselben user-facing Flag 3+ Mal als Sorge raisen.
## 2026-06-19 — Security-E2E muss JEDEN Collector-Default-Pfad treiben, nicht nur den Aggregat-Hub (Remote-Diagnostics)
**Kontext:** Read-only Remote-Diagnose gebaut (Agent im App-Prozess + lokales MCP-Gateway, connect-by-code). Redaktion ist die EINZIGE Garantie, dass kein Secret die Box verlässt. Drei Scrub-Ebenen: (1) `sanitizeConfig` redactet CRED_KEY-gekeyte Felder strukturell, (2) `valueScrub` ersetzt bekannte Config-Secret-WERTE per split/join, (3) `redactLogText` pattern-scrubt Secret-SHAPES (Bearer/token=/cookie:/discord-webhook/?key=) in Freitext.
**Bug 1 (E2E-Gate gefangen):** Ein Hoster-zurückgegebenes `token=<opaque>` in einem History-Error-String überlebte — es ist KEIN gespeichertes Config-Credential, also greift value-scrub nicht, und das line-48-Pattern kannte nur `access_token`, nicht bare `token`. Fix: token-Familie (`token`/`auth_token`/`refresh_token`/`session_token` + standalone `Bearer <opaque>`) ins Pattern.
**Bug 2 (Advisor gefangen, NACH grünem E2E):** `get_config_redacted` section:'all' und `get_queue_state {includeJobs:true}` (DEFAULT-Pfad!) liefen nur durch value-scrub, nie pattern-scrub → derselbe opaque-Token-Leak. `redactLogText` direkt über kompaktes JSON laufen zu lassen geht NICHT (die `cookie:`/`[^\n]*`-Patterns fressen über Feldgrenzen → JSON kaputt). Fix: `_deepRedact` = per-String-Leaf-Walk, der redactLogText auf jedes Leaf einzeln anwendet (JSON-safe, da pro Leaf begrenzt). history aus get_config gedroppt (hat eigenen Collector).
**Warum der erste E2E es verfehlte (der Diskriminator):** `server_health` ruft `getQueueState({includeJobs:false})` → kein Job-Error je serialisiert; UND das Fixture nutzte `apiKey=<SECRET>` als Queue-Error → value-scrub fing es eh, auch ohne pattern-scrub. ZWEI Zufälle versteckten den Leak. Der direkte `get_queue_state{includeJobs:true}`-Pfad mit NICHT-Config-Token war nie getrieben.
**Regel:** Bei einem Redaktions-Gate (a) jeden Collector EINZELN mit seinen DEFAULT-Args treiben, nicht nur den Aggregat-Hub, der bequeme Flags setzt; (b) Fixtures MÜSSEN ein NICHT-Config-Secret enthalten (opaque Token, der nur als Shape erkennbar ist), sonst testet man value-scrub und glaubt, pattern-scrub zu testen; (c) ein grüner E2E heißt nicht „dicht" — Advisor/Review über jeden Pfad laufen lassen, der einen Freitext-Fehlerstring serialisiert.
**Wie anwenden:** Denylist-Redaktion kann einen bare opaque String OHNE `key=`/`Bearer`/URL-Kontext NICHT scrubben — das ist inhärent, kein Defekt. Commit-/Release-Notes ehrlich halten („common secret shapes pattern-scrubbed", nicht „kein Secret verlässt je die Box"). Default-Bind 127.0.0.1 erzwingen (`_safeDiagBindAddress`), 0.0.0.0-Option erst wenn wss/TLS existiert — nie plaintext ws:// auf allen Interfaces.