Files
ApiCore/ApiCore-Backend/src/common/file-signature.ts
T
Nick SzaunigandClaude Sonnet 5 b7ff92abd3 fix: SSO-MFA-Umgehung, Datei-Magic-Bytes und oeffentliches Swagger geschlossen
Weitere vom Sicherheitsaudit gemeldete Luecken behoben, die bisher als
niedrige Prioritaet offen gelassen wurden:

- SSO-Login (Entra ID) prüft jetzt lokale MFA genauso wie der
  Passwort-Login: ist mfaEnabled gesetzt, liefert /auth/sso/complete
  denselben mfaRequired-Codepfad wie /auth/login, bevor Tokens
  ausgestellt werden. Frontend (Login-Seite, AuthContext) zeigt in dem
  Fall den bestehenden Code-Eingabe-Schritt statt die Anmeldung
  stillschweigend abzuschliessen.
- Avatar- und Dokument-Uploads pruefen jetzt die tatsaechlichen
  Datei-Bytes (Magic Numbers fuer PNG/JPEG/WEBP/PDF) statt nur dem
  client-angegebenen MIME-Typ/data:-URL-Praefix zu vertrauen.
- Swagger-UI (/api/v1/docs) ist jetzt standardmaessig deaktiviert
  (ENABLE_SWAGGER=true erforderlich), da sie zuvor das komplette
  API-Schema ungeschuetzt oeffentlich preisgab.

npm-audit-Warnungen (deepmerge-ts/mysql2 unter prisma) bewusst nicht
gefixt: kein stabiles Prisma-Release loest sie bislang auf, nur eine
Release-Candidate-Hauptversion - das Risiko eines Downgrades/RC-Upgrades
der Datenbankschicht uebersteigt den Nutzen fuer eine Kette, die zur
Laufzeit ohnehin nicht erreichbar ist (nur Prisma-CLI, nur MySQL-Treiber,
dieses Projekt nutzt Postgres).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-02 09:09:58 +02:00

36 lines
1.3 KiB
TypeScript

const SIGNATURE_CHECKS: Record<string, (buffer: Buffer) => boolean> = {
'image/png': (buffer) =>
buffer.length >= 8 &&
buffer
.subarray(0, 8)
.equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])),
'image/jpeg': (buffer) =>
buffer.length >= 3 &&
buffer[0] === 0xff &&
buffer[1] === 0xd8 &&
buffer[2] === 0xff,
'image/webp': (buffer) =>
buffer.length >= 12 &&
buffer.subarray(0, 4).toString('ascii') === 'RIFF' &&
buffer.subarray(8, 12).toString('ascii') === 'WEBP',
'application/pdf': (buffer) =>
buffer.length >= 5 && buffer.subarray(0, 5).toString('ascii') === '%PDF-',
};
/**
* Verifies a file's actual bytes match its declared MIME type instead of
* trusting the client-supplied value alone (a multipart Content-Type header
* or a data: URL prefix are both attacker-controlled). Used at every upload
* path that persists the bytes to disk under a server-chosen name/extension,
* so a mislabeled file can't be dropped onto the filesystem as a type it
* isn't. Types with no signature defined here are not checked (returns
* true) - callers still gate on their own MIME allowlist first.
*/
export function matchesFileSignature(
buffer: Buffer,
declaredMimeType: string,
): boolean {
const check = SIGNATURE_CHECKS[declaredMimeType];
return check ? check(buffer) : true;
}