fix(security): Bildoptimierer abschalten

/_next/image war ohne Anmeldung erreichbar und antwortete mit 200. Die
Middleware nimmt den Pfad aus, weil dort sonst nur Rechenzeit verbraucht
wuerde - gedacht war das fuer ausgelieferte Dateien, nicht fuer einen
Endpunkt, der Bilder verarbeitet.

Er reicht jedes Bild durch sharp und damit durch libvips, fuer das es
regelmaessig Schwachstellen gibt; derzeit sind vier offen (CVE-2026-33327,
-33328, -35590, -35591). Ausnutzbar waere das hier nur mit einer Datei, die
schon auf dem Server liegt - hochladen kann man nichts. Aber Nebula benutzt
next/image ohnehin nirgends: eine Angriffsflaeche ohne Gegenwert.

Der Optimierer ist deshalb abgeschaltet, und der Pfad ist nicht mehr von der
Zugangsschranke ausgenommen. Ein Test haelt fest, dass er ohne Anmeldung
nicht mehr mit 200 antwortet.

Die Advisories zu postcss und sharp bleiben in npm audit stehen: Next pinnt
beide fest, auch in 16.2.12 - ein Versionssprung wuerde daran nichts
aendern. postcss laeuft nur zur Bauzeit ueber eigenes CSS.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-27 00:14:10 +02:00
co-authored by Claude Opus 5
parent 27f51b6be8
commit df94646371
3 changed files with 32 additions and 1 deletions
+13
View File
@@ -532,6 +532,19 @@ test("Projektvorlagen: aus Projekt sichern und anwenden", async ({ request, play
expect((await request.delete(`/api/templates/${vorlage.id}`)).ok()).toBeTruthy(); expect((await request.delete(`/api/templates/${vorlage.id}`)).ok()).toBeTruthy();
}); });
test("Der Bildoptimierer ist zu", async ({ playwright }) => {
// /_next/image reicht jedes Bild durch sharp und damit durch libvips, fuer
// das es regelmaessig Schwachstellen gibt. Nebula benutzt next/image
// nirgends – der Endpunkt darf ohne Anmeldung nicht antworten.
const anonym = await playwright.request.newContext({
baseURL: test.info().project.use.baseURL,
maxRedirects: 0,
});
const antwort = await anonym.get("/_next/image?url=%2Ficon-192.png&w=64&q=75");
expect(antwort.status(), "kein 200 ohne Anmeldung").not.toBe(200);
await anonym.dispose();
});
test("Export enthaelt keine Geheimnisse, Import beachtet die Strategie", async ({ request }) => { test("Export enthaelt keine Geheimnisse, Import beachtet die Strategie", async ({ request }) => {
await login(request, ADMIN); await login(request, ADMIN);
+15
View File
@@ -9,6 +9,21 @@ const nextConfig: NextConfig = {
poweredByHeader: false, poweredByHeader: false,
// Prisma darf nicht gebundlet werden, sonst findet der Client seine Engine nicht. // Prisma darf nicht gebundlet werden, sonst findet der Client seine Engine nicht.
serverExternalPackages: ["@prisma/client", "prisma"], serverExternalPackages: ["@prisma/client", "prisma"],
images: {
/**
* Der Bildoptimierer ist abgeschaltet.
*
* Nebula benutzt next/image nirgends – die paar Symbole sind SVG und
* PNG in fester Groesse. Der Endpunkt /_next/image waere aber trotzdem
* da, und zwar ohne Anmeldung: die Middleware nimmt ihn aus, weil dort
* sonst nur Rechenzeit verbraucht wuerde.
*
* Er reicht jedes Bild durch sharp und damit durch libvips, fuer das es
* regelmaessig Schwachstellen gibt (aktuell CVE-2026-33327 und drei
* weitere). Eine Angriffsflaeche ohne Gegenwert – also weg damit.
*/
unoptimized: true,
},
}; };
export default nextConfig; export default nextConfig;
+4 -1
View File
@@ -147,7 +147,10 @@ export const config = {
* weder CSRF-Pruefung noch Nonce und wuerden nur Rechenzeit kosten. * weder CSRF-Pruefung noch Nonce und wuerden nur Rechenzeit kosten.
*/ */
{ {
source: "/((?!_next/static|_next/image|favicon.ico|icon.svg).*)", // _next/image ist bewusst *nicht* ausgenommen: der Bildoptimierer ist
// abgeschaltet (siehe next.config.ts), und was von ihm uebrig bleibt,
// soll nicht ohne Sitzung erreichbar sein.
source: "/((?!_next/static|favicon.ico|icon.svg).*)",
missing: [ missing: [
{ type: "header", key: "next-router-prefetch" }, { type: "header", key: "next-router-prefetch" },
{ type: "header", key: "purpose", value: "prefetch" }, { type: "header", key: "purpose", value: "prefetch" },