fix(quick-261009-p0m): Schutz-Kopfzeilen in der Weboberfläche, X-Powered-By abgeschaltet

- next.config.ts: nosniff, Referrer-Policy, X-Frame-Options SAMEORIGIN, CSP nur frame-ancestors 'self', Permissions-Policy (Kamera, Mikrofon, Standort, Zahlung, USB), COOP same-origin-allow-popups, poweredByHeader aus
- API: X-Powered-By (Express) in configureHttp abgeschaltet
- Tests: next-config.test.ts, http-setup.spec.ts
- Sicherheitsprotokoll (Zeilen der Außenprüfung), Entwicklungsanleitung, CHANGELOG

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-09 20:29:07 +02:00
parent 2a8a7d4270
commit fceec19ded
8 changed files with 113 additions and 8 deletions
+10 -1
View File
@@ -14,6 +14,9 @@ function recordingApp() {
enableCors: vi.fn(() => {
calls.push('cors');
}),
disable: vi.fn((name: string) => {
calls.push(`disable:${name}`);
}),
} as unknown as HttpApp;
return { app, calls };
}
@@ -38,6 +41,12 @@ describe('configureHttp (Review WR-01)', () => {
it('Anfragelog und Cookies kommen vor allem anderen', () => {
const { app, calls } = recordingApp();
configureHttp(app, 'x');
expect(calls.slice(0, 2)).toEqual(['use', 'use']);
expect(calls.filter((c) => !c.startsWith('disable:')).slice(0, 2)).toEqual(['use', 'use']);
});
it('schaltet die Kopfzeile X-Powered-By ab', () => {
const { app } = recordingApp();
configureHttp(app, 'x');
expect(app.disable).toHaveBeenCalledWith('x-powered-by');
});
});
+9 -2
View File
@@ -1,4 +1,5 @@
import { type INestApplication, ValidationPipe } from '@nestjs/common';
import { ValidationPipe } from '@nestjs/common';
import type { NestExpressApplication } from '@nestjs/platform-express';
import cookieParser from 'cookie-parser';
import {
CERT_BUILD_ROUTE,
@@ -8,7 +9,10 @@ import {
import { requestLogMiddleware } from './common/request-log';
/** Der Teil der Nest-Anwendung, den die HTTP-Einrichtung braucht (so laesst sich die Reihenfolge testen). */
export type HttpApp = Pick<INestApplication, 'use' | 'useGlobalPipes' | 'enableCors'>;
export type HttpApp = Pick<
NestExpressApplication,
'use' | 'useGlobalPipes' | 'enableCors' | 'disable'
>;
/**
* Gemeinsame HTTP-Einrichtung der API (aus main.ts, damit die Reihenfolge testbar ist).
@@ -21,6 +25,9 @@ export type HttpApp = Pick<INestApplication, 'use' | 'useGlobalPipes' | 'enableC
* Er steht trotzdem vor Nests globalem JSON-Leser, der erst beim Start (listen) eingebaut wird.
*/
export function configureHttp(app: HttpApp, corsOrigin: string): void {
// Kein "X-Powered-By: Express" (quick-261009-p0m): verrät dem Besucher nur das Framework
app.disable('x-powered-by');
// Eine Zeile je Anfrage im Docker-Log (Pfad, Status, Dauer, Benutzer)
app.use(requestLogMiddleware);
+2 -1
View File
@@ -1,11 +1,12 @@
import { ConfigService } from '@nestjs/config';
import { NestFactory } from '@nestjs/core';
import type { NestExpressApplication } from '@nestjs/platform-express';
import { AppModule } from './app.module';
import { formatAppVersionLine } from './health/app-version';
import { configureHttp } from './http-setup';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const app = await NestFactory.create<NestExpressApplication>(AppModule);
const configService = app.get(ConfigService);
configureHttp(app, configService.get<string>('CORS_ORIGIN', 'http://localhost:3000'));