Anlass: Der Editor soll öffentlich werden (Reddit-Post, Gastzugang in #428). Der Server ist selbst gehostet (stein2), deshalb darf es keine Lücke geben. Dieses Issue hält den gemessenen Ist-Stand fest und plant die Härtung.
Beim Anlegen schon behoben
tmp/ war von außen lesbar. Darin lagen 79 Kopien echter Projektdateien aus Wegwerf-Bauskripten, Dateien 0644, an https://nockal.com/feuerwerk/tmp/… HTTP 200. Damit war der Schutz aus #85 (Projekte 0640) umgangen.
Seit 15.10., 20:02, ist tmp/ auf 0750 gesetzt, extern kommt 403.
Das nginx-Log reicht bis 11.10. zurück. Seither gab es dort nur eigene Zugriffe. Was davor war, ist nicht mehr feststellbar.
Befunde (gemessen am 15.10.)
Der Web-Root ist der ganze Workspace. nginx liefert alles aus, was nicht eigens gesperrt ist. Gesperrt sind nur .git und Dateien mit 0640. Lesbar sind unter anderem logs/ (328 Build- und Check-Logs), node_modules/, wiki/, tests/, tools/, screenshot-archive/, benchmark/ und einzelne Dateien in rpgmz/. Der Quellcode ist ohnehin öffentlich, Logs und Fremd-Assets sind es nicht.
Jede .php-Datei unter dem Root läuft per HTTP (location ~ \.php$).
Betroffen sind tools/make-user.php, tools/migrate-file-owners.php, tools/video-worker.php, tools/ci/router.php, tests/php/*.test.php und node_modules/flatted/php/flatted.php.
Keines davon prüft, ob es auf der Kommandozeile läuft. Heute scheitern sie an $argv, weil FPM register_argc_argv=0 hat (per Sonde gemessen).
Ist der Schalter einmal an, liest PHP $argv aus dem Query-String. Dann legt make-user.php Konten samt Rolle an. Der Schutz ist also Zufall.
display_errors=1 unter FPM: Fehler gehen samt Serverpfaden an den Browser. Dazu expose_php=1 (X-Powered-By: PHP/8.5.11).
Das Session-Cookie hat kein Secure.Auth.php:119 setzt es nur bei $_SERVER['HTTPS'], und hinter der Brandmauer kommt bei nginx nur HTTP an. An https://nockal.com gemessen: HttpOnly; SameSite=Lax, ohne Secure.
Der Login hat keine Bremse. In api/ gibt es keine Begrenzung der Fehlversuche. Passwörter selbst sind sauber (password_hash, Mindestlänge 8), Session-Token 256 Bit.
Keine Security-Header, auch nicht von außen: kein CSP, kein X-Content-Type-Options, kein frame-ancestors, kein HSTS.
Die Web-Container mounten den ganzen Ordner /app/feuerwehr/Data/AiWorkspaces. Eine Lücke in PHP reicht damit über dieses Projekt hinaus.
Was noch geprüft werden muss
API: jede Route gegen anonym, user, admin und ein fremdes Konto, als PHP-Test im CI-Gate. Dazu Pfad-Traversal in allen Dateinamen (Texturen, Fonts, Sounds, Videos, Pixel, Plugins) und die Grenzen des Routers.
Uploads: Was wird je Art angenommen, und mit welchem Content-Type geht es wieder raus? Bilder werden schon gesnifft. Für Fonts, Sounds und Videos fehlt die Prüfung, ob sich zum Beispiel HTML einschmuggeln lässt.
Video-Worker: ffmpeg verarbeitet fremde Dateien. Prüfen, ob der Container ohne Netz läuft, CPU, RAM und Laufzeit begrenzt sind und er nur seinen eigenen Ordner sieht.
Ressourcen: nginx nimmt Bodies bis 2 GB, PHP hat 4 GB memory_limit. Ohne Quoten je Konto (Platz, Projekte, Snapshots, Videos) kann ein Konto die Platte füllen.
CSRF:SameSite=Lax zählt alle Subdomains von nockal.com als dieselbe Seite. Prüfen, ob die API für schreibende Routen Content-Type: application/json verlangt.
XSS: Der Editor zeichnet im Canvas, aber play.html, Projekt- und Asset-Namen und die öffentliche Release-Route gehören geprüft.
Abhängigkeiten:npm audit, dazu die Versionen des PHP- und nginx-Images.
Plugins: bleiben bei #115. Solange es keinen Upload gibt (#116), legt nur der Server-Admin welche ab.
Vorschlag zur Umsetzung
Web-Root trennen: nginx bekommt nur noch, was ausgeliefert wird: dist/, api/index.php, die öffentlichen Teile von data/ und public/. Alles andere ist dann nicht mehr erreichbar, die Befunde 1 und 2 sind damit erledigt. Das betrifft die gemeinsame nginx.conf.
PHP:display_errors und expose_php aus. Alle Skripte unter tools/ brechen ab, wenn PHP_SAPI !== 'cli'. Das Cookie bekommt immer Secure.
Login-Bremse (429 nach zu vielen Fehlversuchen) und Quoten.
Header in nginx.
Rechte-Matrix als PHP-Test, damit eine neue Route nicht unbemerkt offen bleibt.
Zum Schluss ein Scan von außen (z. B. OWASP ZAP Baseline) gegen die Live-Seite.
Fertig, wenn
Von außen sind nur noch index.html, play.html, dist/, public/, die API und die öffentlichen Dateien in data/ erreichbar. Gemessen wird das mit einer Liste von Pfaden, die 403/404 liefern müssen.
Keine .php außer api/index.php läuft per HTTP.
Cookie mit Secure, die Header gesetzt, display_errors aus.
Die Rechte-Matrix ist ein PHP-Test im CI-Gate.
Der Scan von außen meldet nichts Hohes oder Mittleres.
Rückfragen
Web-Root trennen: Darf ich dafür die gemeinsame nginx.conf anfassen (mit Backup)? Oder lieber ein eigenes Auslieferungsverzeichnis, das npm run build füllt?
Login-Bremse: Hinter der Brandmauer sieht nginx jede Anfrage als 172.16.0.1. Für eine Sperre je IP braucht es X-Forwarded-For von der Brandmauer. Ob sie den Header setzt, muss ich erst messen. Reicht sonst eine Sperre je Konto?
Quoten: Welche Grenzen sind dir recht, etwa Platz je Konto?
Scan von außen: Darf ich ZAP gegen die Live-Seite laufen lassen?
Anlass: Der Editor soll öffentlich werden (Reddit-Post, Gastzugang in #428). Der Server ist selbst gehostet (stein2), deshalb darf es keine Lücke geben. Dieses Issue hält den gemessenen Ist-Stand fest und plant die Härtung.
## Beim Anlegen schon behoben
- **`tmp/` war von außen lesbar.** Darin lagen 79 Kopien echter Projektdateien aus Wegwerf-Bauskripten, Dateien 0644, an `https://nockal.com/feuerwerk/tmp/…` HTTP 200. Damit war der Schutz aus #85 (Projekte 0640) umgangen.
- Seit 15.10., 20:02, ist `tmp/` auf 0750 gesetzt, extern kommt 403.
- Das nginx-Log reicht bis 11.10. zurück. Seither gab es dort nur eigene Zugriffe. Was davor war, ist nicht mehr feststellbar.
## Befunde (gemessen am 15.10.)
1. **Der Web-Root ist der ganze Workspace.** nginx liefert alles aus, was nicht eigens gesperrt ist. Gesperrt sind nur `.git` und Dateien mit 0640. Lesbar sind unter anderem `logs/` (328 Build- und Check-Logs), `node_modules/`, `wiki/`, `tests/`, `tools/`, `screenshot-archive/`, `benchmark/` und einzelne Dateien in `rpgmz/`. Der Quellcode ist ohnehin öffentlich, Logs und Fremd-Assets sind es nicht.
2. **Jede `.php`-Datei unter dem Root läuft per HTTP** (`location ~ \.php$`).
- Betroffen sind `tools/make-user.php`, `tools/migrate-file-owners.php`, `tools/video-worker.php`, `tools/ci/router.php`, `tests/php/*.test.php` und `node_modules/flatted/php/flatted.php`.
- Keines davon prüft, ob es auf der Kommandozeile läuft. Heute scheitern sie an `$argv`, weil FPM `register_argc_argv=0` hat (per Sonde gemessen).
- Ist der Schalter einmal an, liest PHP `$argv` aus dem Query-String. Dann legt `make-user.php` Konten samt Rolle an. Der Schutz ist also Zufall.
3. **`display_errors=1`** unter FPM: Fehler gehen samt Serverpfaden an den Browser. Dazu `expose_php=1` (`X-Powered-By: PHP/8.5.11`).
4. **Das Session-Cookie hat kein `Secure`.** `Auth.php:119` setzt es nur bei `$_SERVER['HTTPS']`, und hinter der Brandmauer kommt bei nginx nur HTTP an. An `https://nockal.com` gemessen: `HttpOnly; SameSite=Lax`, ohne `Secure`.
5. **Der Login hat keine Bremse.** In `api/` gibt es keine Begrenzung der Fehlversuche. Passwörter selbst sind sauber (`password_hash`, Mindestlänge 8), Session-Token 256 Bit.
6. **Keine Security-Header**, auch nicht von außen: kein CSP, kein `X-Content-Type-Options`, kein `frame-ancestors`, kein HSTS.
7. **Die Web-Container mounten den ganzen Ordner `/app/feuerwehr/Data/AiWorkspaces`.** Eine Lücke in PHP reicht damit über dieses Projekt hinaus.
## Was noch geprüft werden muss
- **API:** jede Route gegen anonym, `user`, `admin` und ein fremdes Konto, als PHP-Test im CI-Gate. Dazu Pfad-Traversal in allen Dateinamen (Texturen, Fonts, Sounds, Videos, Pixel, Plugins) und die Grenzen des Routers.
- **Uploads:** Was wird je Art angenommen, und mit welchem Content-Type geht es wieder raus? Bilder werden schon gesnifft. Für Fonts, Sounds und Videos fehlt die Prüfung, ob sich zum Beispiel HTML einschmuggeln lässt.
- **Video-Worker:** ffmpeg verarbeitet fremde Dateien. Prüfen, ob der Container ohne Netz läuft, CPU, RAM und Laufzeit begrenzt sind und er nur seinen eigenen Ordner sieht.
- **Ressourcen:** nginx nimmt Bodies bis 2 GB, PHP hat 4 GB `memory_limit`. Ohne Quoten je Konto (Platz, Projekte, Snapshots, Videos) kann ein Konto die Platte füllen.
- **CSRF:** `SameSite=Lax` zählt alle Subdomains von `nockal.com` als dieselbe Seite. Prüfen, ob die API für schreibende Routen `Content-Type: application/json` verlangt.
- **XSS:** Der Editor zeichnet im Canvas, aber `play.html`, Projekt- und Asset-Namen und die öffentliche Release-Route gehören geprüft.
- **Abhängigkeiten:** `npm audit`, dazu die Versionen des PHP- und nginx-Images.
- **Plugins:** bleiben bei #115. Solange es keinen Upload gibt (#116), legt nur der Server-Admin welche ab.
## Vorschlag zur Umsetzung
1. **Web-Root trennen:** nginx bekommt nur noch, was ausgeliefert wird: `dist/`, `api/index.php`, die öffentlichen Teile von `data/` und `public/`. Alles andere ist dann nicht mehr erreichbar, die Befunde 1 und 2 sind damit erledigt. Das betrifft die gemeinsame nginx.conf.
2. **PHP:** `display_errors` und `expose_php` aus. Alle Skripte unter `tools/` brechen ab, wenn `PHP_SAPI !== 'cli'`. Das Cookie bekommt immer `Secure`.
3. **Login-Bremse** (429 nach zu vielen Fehlversuchen) und **Quoten**.
4. **Header** in nginx.
5. **Rechte-Matrix** als PHP-Test, damit eine neue Route nicht unbemerkt offen bleibt.
6. Zum Schluss ein **Scan von außen** (z. B. OWASP ZAP Baseline) gegen die Live-Seite.
## Fertig, wenn
- Von außen sind nur noch `index.html`, `play.html`, `dist/`, `public/`, die API und die öffentlichen Dateien in `data/` erreichbar. Gemessen wird das mit einer Liste von Pfaden, die 403/404 liefern müssen.
- Keine `.php` außer `api/index.php` läuft per HTTP.
- Cookie mit `Secure`, die Header gesetzt, `display_errors` aus.
- Die Rechte-Matrix ist ein PHP-Test im CI-Gate.
- Der Scan von außen meldet nichts Hohes oder Mittleres.
## Rückfragen
1. **Web-Root trennen:** Darf ich dafür die gemeinsame nginx.conf anfassen (mit Backup)? Oder lieber ein eigenes Auslieferungsverzeichnis, das `npm run build` füllt?
2. **Login-Bremse:** Hinter der Brandmauer sieht nginx jede Anfrage als `172.16.0.1`. Für eine Sperre je IP braucht es `X-Forwarded-For` von der Brandmauer. Ob sie den Header setzt, muss ich erst messen. Reicht sonst eine Sperre je Konto?
3. **Quoten:** Welche Grenzen sind dir recht, etwa Platz je Konto?
4. **Scan von außen:** Darf ich ZAP gegen die Live-Seite laufen lassen?
meleki
added the new label 2026-10-15 22:03:54 +02:00
passt, web root trennen - nginx.conf darf angefasst werden
x-forwarded-for sollte gesetzt sein, bei 5fehlschlägen ip adresse für 15 minuten sperren
platz pro konto klingt gut
kenne ZAP zwar nicht, aber kannst auf die live seite laufen lassen - klingt vernünftig
1. passt, web root trennen - nginx.conf darf angefasst werden
2. x-forwarded-for sollte gesetzt sein, bei 5fehlschlägen ip adresse für 15 minuten sperren
3. platz pro konto klingt gut
4. kenne ZAP zwar nicht, aber kannst auf die live seite laufen lassen - klingt vernünftig
Danke, damit ist alles entschieden. Zusammengefasst, so baue ich es:
Web-Root trennen in der gemeinsamen nginx.conf (Backup vorher): unter /feuerwerk/ nur noch index.html, play.html, dist/, public/, api/index.php und die öffentlichen Ordner in data/. Alles andere 404.
Login-Bremse: 5 Fehlversuche, dann ist die IP 15 Minuten gesperrt (429). Die IP kommt aus X-Forwarded-For, aber nur, wenn die Anfrage von der Brandmauer kommt. Das messe ich vorher, sonst könnte sich ein Angreifer selbst eine neue IP hineinschreiben.
Quote: 1 GB je Konto, Admin ohne Grenze.
ZAP Baseline zum Schluss gegen die Live-Seite.
Dazu wie im Vorschlag: display_errors/expose_php aus, CLI-Sperre in allen Skripten, Cookie mit Secure, Security-Header, Rechte-Matrix als PHP-Test.
Aus #428 kommt eine Abhängigkeit dazu: Die Demo-Materialien (Texturen, Sounds, Videos) müssen nach der Trennung weiter ohne Login erreichbar sein. Sie liegen in data/textures|sounds|videos/, die stehen ohnehin auf der Liste der erlaubten Pfade.
Danke, damit ist alles entschieden. Zusammengefasst, so baue ich es:
1. **Web-Root trennen** in der gemeinsamen nginx.conf (Backup vorher): unter `/feuerwerk/` nur noch `index.html`, `play.html`, `dist/`, `public/`, `api/index.php` und die öffentlichen Ordner in `data/`. Alles andere 404.
2. **Login-Bremse:** 5 Fehlversuche, dann ist die IP 15 Minuten gesperrt (429). Die IP kommt aus `X-Forwarded-For`, aber nur, wenn die Anfrage von der Brandmauer kommt. Das messe ich vorher, sonst könnte sich ein Angreifer selbst eine neue IP hineinschreiben.
3. **Quote:** 1 GB je Konto, Admin ohne Grenze.
4. **ZAP Baseline** zum Schluss gegen die Live-Seite.
Dazu wie im Vorschlag: `display_errors`/`expose_php` aus, CLI-Sperre in allen Skripten, Cookie mit `Secure`, Security-Header, Rechte-Matrix als PHP-Test.
Aus #428 kommt eine Abhängigkeit dazu: Die Demo-Materialien (Texturen, Sounds, Videos) müssen nach der Trennung weiter ohne Login erreichbar sein. Sie liegen in `data/textures|sounds|videos/`, die stehen ohnehin auf der Liste der erlaubten Pfade.
Web-Root getrennt: nginx gibt unter /feuerwerk/ nur noch eine Whitelist aus: die beiden Seiten, dist/, public/brand/, die Uploads nach Art und Endung und api/index.php. Alles andere ist 404. Die gemeinsame nginx.conf ist gesichert unter /root/config-backup-20261015/.
PHP:display_errors ist aus, X-Powered-By weg. Jedes Skript außerhalb von api/ bricht ab, wenn es nicht auf der Kommandozeile läuft. HTTP leitet auf HTTPS weiter, die API weist HTTP ab.
Cookie mit Secure, HttpOnly, SameSite=Lax.
Header: CSP ohne Inline-Code und ohne eval, nosniff, frame-ancestors, HSTS, Referrer-, Permissions-Policy, COOP, CORP. Die gleiche CSP steht als <meta> in den gebauten Seiten, und der e2e-Harness meldet jeden CSP-Verstoß als FAIL.
Login-Bremse: 5 Fehlversuche je IP in 15 Minuten, danach 429 bis 15 Minuten nach dem letzten. Die IP ist der letzte Eintrag in X-Forwarded-For, den hängt die Brandmauer an. Was ein Client davor schreibt, zählt nicht.
Quote: 1 GB je Konto (Uploads, Projekte, Snapshots), 413 nur bei Wachstum, Admins frei.
CSRF: Schreibende Anfragen mit Sec-Fetch-Site: cross-site oder same-site bekommen 403.
XSS:showFailure in src/page.ts setzt die Meldung als Text, nicht mehr als HTML.
Video-Worker: Checkout nur lesbar, schreiben nur in data/videos, read_only, keine Capabilities, 2 GB, 256 Prozesse. Eine Umwandlung danach gemessen: fertig in 0,7 s.
Rechte-Matrixtests/php/rights.test.php: fragt jede Route als abgemeldet, Besitzer, Fremder und Admin. Eine Route, die im Kopf von api/index.php steht und in der Matrix fehlt, macht den Test rot.
Fertig-Kriterien
Kriterium
Beleg
Nur Seiten, dist/, public/, API und öffentliche data/-Dateien erreichbar
Pfadliste von außen: 10 ausgeliefert, 26 mit 404
Keine .php außer api/index.php per HTTP
am 16.10. nachgemessen: tools/make-user.php, tests/php/rights.test.php, node_modules/flatted/php/flatted.php → 404
ZAP Baseline gegen die Live-Seite: unter /feuerwerk/ nichts High oder Medium
Gegenproben: Ohne Cross-Site-Sperre, ohne Quote, ohne Bremse, ohne Admin-Prüfung bei users oder mit einer undokumentierten Route wird rights.test.php jedes Mal rot. Eine CSP ohne wasm-unsafe-eval macht e2e 41 mit der CSP-Zeile des Harness rot.
ce1f25f behebt den roten Gate-Lauf 705: PHP 8.2 im CI-Image hat beim Aufräumen alter Fehlversuche eine zwischengespeicherte Dateizeit gelesen.
Funde nebenbei
tmp/ war von außen lesbar, mit 79 Kopien von Projektdateien. Am 15.10. um 20:02 mit chmod 750 geschlossen, inzwischen liegt es auch außerhalb der Whitelist. Das nginx-Log reicht bis 11.10. zurück und zeigt nur eigene Zugriffe.
Port 8080 ist auf 0.0.0.0 gebunden. Aus dem LAN kommt man damit an der Brandmauer vorbei zu nginx. Nicht angefasst, weil der Web-Stack nicht zu diesem Projekt gehört.
npm audit: im Ausgelieferten nichts. Die Dev-Werkzeuge (vitest, brace-expansion, source-map-js) haben Advisories, hier nicht angefasst.
CI: Die Suites 707/708 zu cfd6d7a/ce1f25f sind heute um 06:46 abgebrochen worden (Status cancelled, alles bis dahin PASS, nicht rot). Die erste volle Suite unter der neuen CSP ist damit 709 zu 925a39a (#428): e2e ALL PASSED, 2101 Checks, Smokes 24/19/21, Gate 710 grün.
Erledigt mit `cfd6d7a` und `ce1f25f`.
## Umsetzung
- **Web-Root getrennt:** nginx gibt unter `/feuerwerk/` nur noch eine Whitelist aus: die beiden Seiten, `dist/`, `public/brand/`, die Uploads nach Art und Endung und `api/index.php`. Alles andere ist 404. Die gemeinsame `nginx.conf` ist gesichert unter `/root/config-backup-20261015/`.
- **PHP:** `display_errors` ist aus, `X-Powered-By` weg. Jedes Skript außerhalb von `api/` bricht ab, wenn es nicht auf der Kommandozeile läuft. HTTP leitet auf HTTPS weiter, die API weist HTTP ab.
- **Cookie** mit `Secure`, `HttpOnly`, `SameSite=Lax`.
- **Header:** CSP ohne Inline-Code und ohne eval, `nosniff`, `frame-ancestors`, HSTS, Referrer-, Permissions-Policy, COOP, CORP. Die gleiche CSP steht als `<meta>` in den gebauten Seiten, und der e2e-Harness meldet jeden CSP-Verstoß als FAIL.
- **Login-Bremse:** 5 Fehlversuche je IP in 15 Minuten, danach 429 bis 15 Minuten nach dem letzten. Die IP ist der letzte Eintrag in `X-Forwarded-For`, den hängt die Brandmauer an. Was ein Client davor schreibt, zählt nicht.
- **Quote:** 1 GB je Konto (Uploads, Projekte, Snapshots), 413 nur bei Wachstum, Admins frei.
- **CSRF:** Schreibende Anfragen mit `Sec-Fetch-Site: cross-site` oder `same-site` bekommen 403.
- **XSS:** `showFailure` in `src/page.ts` setzt die Meldung als Text, nicht mehr als HTML.
- **Video-Worker:** Checkout nur lesbar, schreiben nur in `data/videos`, `read_only`, keine Capabilities, 2 GB, 256 Prozesse. Eine Umwandlung danach gemessen: fertig in 0,7 s.
- **Rechte-Matrix** `tests/php/rights.test.php`: fragt jede Route als abgemeldet, Besitzer, Fremder und Admin. Eine Route, die im Kopf von `api/index.php` steht und in der Matrix fehlt, macht den Test rot.
## Fertig-Kriterien
| Kriterium | Beleg |
|---|---|
| Nur Seiten, `dist/`, `public/`, API und öffentliche `data/`-Dateien erreichbar | Pfadliste von außen: 10 ausgeliefert, 26 mit 404 |
| Keine `.php` außer `api/index.php` per HTTP | am 16.10. nachgemessen: `tools/make-user.php`, `tests/php/rights.test.php`, `node_modules/flatted/php/flatted.php` → 404 |
| Cookie `Secure`, Header gesetzt, `display_errors` aus | an `https://nockal.com/feuerwerk/` gemessen |
| Rechte-Matrix im CI-Gate | Gate 706 grün, `rights.test.php` 60 Checks (mit #428 jetzt 80) |
| Scan von außen ohne Hoch/Mittel | ZAP Baseline gegen die Live-Seite: unter `/feuerwerk/` nichts High oder Medium |
Gegenproben: Ohne Cross-Site-Sperre, ohne Quote, ohne Bremse, ohne Admin-Prüfung bei `users` oder mit einer undokumentierten Route wird `rights.test.php` jedes Mal rot. Eine CSP ohne `wasm-unsafe-eval` macht e2e 41 mit der CSP-Zeile des Harness rot.
`ce1f25f` behebt den roten Gate-Lauf 705: PHP 8.2 im CI-Image hat beim Aufräumen alter Fehlversuche eine zwischengespeicherte Dateizeit gelesen.
## Funde nebenbei
- **`tmp/` war von außen lesbar**, mit 79 Kopien von Projektdateien. Am 15.10. um 20:02 mit `chmod 750` geschlossen, inzwischen liegt es auch außerhalb der Whitelist. Das nginx-Log reicht bis 11.10. zurück und zeigt nur eigene Zugriffe.
- **Port 8080 ist auf `0.0.0.0` gebunden.** Aus dem LAN kommt man damit an der Brandmauer vorbei zu nginx. Nicht angefasst, weil der Web-Stack nicht zu diesem Projekt gehört.
- **`npm audit`:** im Ausgelieferten nichts. Die Dev-Werkzeuge (vitest, brace-expansion, source-map-js) haben Advisories, hier nicht angefasst.
**CI:** Die Suites 707/708 zu `cfd6d7a`/`ce1f25f` sind heute um 06:46 abgebrochen worden (Status `cancelled`, alles bis dahin PASS, nicht rot). Die erste volle Suite unter der neuen CSP ist damit 709 zu `925a39a` (#428): e2e **ALL PASSED, 2101 Checks**, Smokes 24/19/21, Gate 710 grün.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Anlass: Der Editor soll öffentlich werden (Reddit-Post, Gastzugang in #428). Der Server ist selbst gehostet (stein2), deshalb darf es keine Lücke geben. Dieses Issue hält den gemessenen Ist-Stand fest und plant die Härtung.
Beim Anlegen schon behoben
tmp/war von außen lesbar. Darin lagen 79 Kopien echter Projektdateien aus Wegwerf-Bauskripten, Dateien 0644, anhttps://nockal.com/feuerwerk/tmp/…HTTP 200. Damit war der Schutz aus #85 (Projekte 0640) umgangen.tmp/auf 0750 gesetzt, extern kommt 403.Befunde (gemessen am 15.10.)
.gitund Dateien mit 0640. Lesbar sind unter anderemlogs/(328 Build- und Check-Logs),node_modules/,wiki/,tests/,tools/,screenshot-archive/,benchmark/und einzelne Dateien inrpgmz/. Der Quellcode ist ohnehin öffentlich, Logs und Fremd-Assets sind es nicht..php-Datei unter dem Root läuft per HTTP (location ~ \.php$).tools/make-user.php,tools/migrate-file-owners.php,tools/video-worker.php,tools/ci/router.php,tests/php/*.test.phpundnode_modules/flatted/php/flatted.php.$argv, weil FPMregister_argc_argv=0hat (per Sonde gemessen).$argvaus dem Query-String. Dann legtmake-user.phpKonten samt Rolle an. Der Schutz ist also Zufall.display_errors=1unter FPM: Fehler gehen samt Serverpfaden an den Browser. Dazuexpose_php=1(X-Powered-By: PHP/8.5.11).Secure.Auth.php:119setzt es nur bei$_SERVER['HTTPS'], und hinter der Brandmauer kommt bei nginx nur HTTP an. Anhttps://nockal.comgemessen:HttpOnly; SameSite=Lax, ohneSecure.api/gibt es keine Begrenzung der Fehlversuche. Passwörter selbst sind sauber (password_hash, Mindestlänge 8), Session-Token 256 Bit.X-Content-Type-Options, keinframe-ancestors, kein HSTS./app/feuerwehr/Data/AiWorkspaces. Eine Lücke in PHP reicht damit über dieses Projekt hinaus.Was noch geprüft werden muss
user,adminund ein fremdes Konto, als PHP-Test im CI-Gate. Dazu Pfad-Traversal in allen Dateinamen (Texturen, Fonts, Sounds, Videos, Pixel, Plugins) und die Grenzen des Routers.memory_limit. Ohne Quoten je Konto (Platz, Projekte, Snapshots, Videos) kann ein Konto die Platte füllen.SameSite=Laxzählt alle Subdomains vonnockal.comals dieselbe Seite. Prüfen, ob die API für schreibende RoutenContent-Type: application/jsonverlangt.play.html, Projekt- und Asset-Namen und die öffentliche Release-Route gehören geprüft.npm audit, dazu die Versionen des PHP- und nginx-Images.Vorschlag zur Umsetzung
dist/,api/index.php, die öffentlichen Teile vondata/undpublic/. Alles andere ist dann nicht mehr erreichbar, die Befunde 1 und 2 sind damit erledigt. Das betrifft die gemeinsame nginx.conf.display_errorsundexpose_phpaus. Alle Skripte untertools/brechen ab, wennPHP_SAPI !== 'cli'. Das Cookie bekommt immerSecure.Fertig, wenn
index.html,play.html,dist/,public/, die API und die öffentlichen Dateien indata/erreichbar. Gemessen wird das mit einer Liste von Pfaden, die 403/404 liefern müssen..phpaußerapi/index.phpläuft per HTTP.Secure, die Header gesetzt,display_errorsaus.Rückfragen
npm run buildfüllt?172.16.0.1. Für eine Sperre je IP braucht esX-Forwarded-Forvon der Brandmauer. Ob sie den Header setzt, muss ich erst messen. Reicht sonst eine Sperre je Konto?Danke, damit ist alles entschieden. Zusammengefasst, so baue ich es:
/feuerwerk/nur nochindex.html,play.html,dist/,public/,api/index.phpund die öffentlichen Ordner indata/. Alles andere 404.X-Forwarded-For, aber nur, wenn die Anfrage von der Brandmauer kommt. Das messe ich vorher, sonst könnte sich ein Angreifer selbst eine neue IP hineinschreiben.Dazu wie im Vorschlag:
display_errors/expose_phpaus, CLI-Sperre in allen Skripten, Cookie mitSecure, Security-Header, Rechte-Matrix als PHP-Test.Aus #428 kommt eine Abhängigkeit dazu: Die Demo-Materialien (Texturen, Sounds, Videos) müssen nach der Trennung weiter ohne Login erreichbar sein. Sie liegen in
data/textures|sounds|videos/, die stehen ohnehin auf der Liste der erlaubten Pfade.Erledigt mit
cfd6d7aundce1f25f.Umsetzung
/feuerwerk/nur noch eine Whitelist aus: die beiden Seiten,dist/,public/brand/, die Uploads nach Art und Endung undapi/index.php. Alles andere ist 404. Die gemeinsamenginx.confist gesichert unter/root/config-backup-20261015/.display_errorsist aus,X-Powered-Byweg. Jedes Skript außerhalb vonapi/bricht ab, wenn es nicht auf der Kommandozeile läuft. HTTP leitet auf HTTPS weiter, die API weist HTTP ab.Secure,HttpOnly,SameSite=Lax.nosniff,frame-ancestors, HSTS, Referrer-, Permissions-Policy, COOP, CORP. Die gleiche CSP steht als<meta>in den gebauten Seiten, und der e2e-Harness meldet jeden CSP-Verstoß als FAIL.X-Forwarded-For, den hängt die Brandmauer an. Was ein Client davor schreibt, zählt nicht.Sec-Fetch-Site: cross-siteodersame-sitebekommen 403.showFailureinsrc/page.tssetzt die Meldung als Text, nicht mehr als HTML.data/videos,read_only, keine Capabilities, 2 GB, 256 Prozesse. Eine Umwandlung danach gemessen: fertig in 0,7 s.tests/php/rights.test.php: fragt jede Route als abgemeldet, Besitzer, Fremder und Admin. Eine Route, die im Kopf vonapi/index.phpsteht und in der Matrix fehlt, macht den Test rot.Fertig-Kriterien
dist/,public/, API und öffentlichedata/-Dateien erreichbar.phpaußerapi/index.phpper HTTPtools/make-user.php,tests/php/rights.test.php,node_modules/flatted/php/flatted.php→ 404Secure, Header gesetzt,display_errorsaushttps://nockal.com/feuerwerk/gemessenrights.test.php60 Checks (mit #428 jetzt 80)/feuerwerk/nichts High oder MediumGegenproben: Ohne Cross-Site-Sperre, ohne Quote, ohne Bremse, ohne Admin-Prüfung bei
usersoder mit einer undokumentierten Route wirdrights.test.phpjedes Mal rot. Eine CSP ohnewasm-unsafe-evalmacht e2e 41 mit der CSP-Zeile des Harness rot.ce1f25fbehebt den roten Gate-Lauf 705: PHP 8.2 im CI-Image hat beim Aufräumen alter Fehlversuche eine zwischengespeicherte Dateizeit gelesen.Funde nebenbei
tmp/war von außen lesbar, mit 79 Kopien von Projektdateien. Am 15.10. um 20:02 mitchmod 750geschlossen, inzwischen liegt es auch außerhalb der Whitelist. Das nginx-Log reicht bis 11.10. zurück und zeigt nur eigene Zugriffe.0.0.0.0gebunden. Aus dem LAN kommt man damit an der Brandmauer vorbei zu nginx. Nicht angefasst, weil der Web-Stack nicht zu diesem Projekt gehört.npm audit: im Ausgelieferten nichts. Die Dev-Werkzeuge (vitest, brace-expansion, source-map-js) haben Advisories, hier nicht angefasst.CI: Die Suites 707/708 zu
cfd6d7a/ce1f25fsind heute um 06:46 abgebrochen worden (Statuscancelled, alles bis dahin PASS, nicht rot). Die erste volle Suite unter der neuen CSP ist damit 709 zu925a39a(#428): e2e ALL PASSED, 2101 Checks, Smokes 24/19/21, Gate 710 grün.