Pentest 55.2 + 55.3 HIGH + 55.4 + 53.3: Notes/Document-Auth/Race/Generate
55.3 HIGH (Contract-Documents ohne Auth abrufbar):
- /uploads/contract-documents/*.pdf war HTTP 200 ohne Token, weil
nginx die Datei direkt ausliefert und Backend nur /api/uploads/*
schützte.
- Defense-in-Depth: app.get('/uploads/*') jetzt ebenfalls mit
authenticate + downloadFile (Ownership-Check) abgesichert.
Falls nginx fehlkonfiguriert sein sollte, fängt das Backend.
55.2 MEDIUM (notes ungestrippt + unlimitiert):
- Neuer sanitizeNotes-Helper: stripHtml + CRLF→LF + Control-Chars
raus + Cap 2000 Zeichen. Eingesetzt für ContractDocument.notes
in allen 3 Schreibpfaden (contract.controller, saveAttachment-
AsContractDocument, saveEmailAsContractDocument).
- documentType zusätzlich stripHtml.
55.4 LOW (Race: 5x Lieferbestätigung → 5 Dokumente):
- Neuer In-Memory-Lock per (contractId, documentType) in
contractStatusScheduler.service. withContractDocumentLock führt
Recent-Duplicate-Check (10s-Window) + Write atomar aus.
- In cachedEmail-Pfaden: fs.writeFileSync ist jetzt INNERHALB des
Locks → kein verwaister Datei-Müll bei Race-Reject.
53.3 (Prisma-Client veraltet bei ungebauten Images):
- docker-entrypoint.sh: `prisma generate` am Container-Start
hinzugefügt. Kostet ~5–10 s, regeneriert den Client gegen das
aktuelle Schema falls jemand ein Stale-Image hochgezogen hat.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -253,6 +253,17 @@ app.get('/api/uploads/*', authenticate as any, (req, res, next) => {
|
||||
return (downloadFile as any)(req, res, next);
|
||||
});
|
||||
|
||||
// Pentest 55.3 (HIGH, 2026-06-01): /uploads/contract-documents/*.pdf
|
||||
// kam ungeschützt durch, weil der nginx-Reverse-Proxy die Dateien
|
||||
// direkt aus dem Filesystem auslieferte und der Backend-Auth-Check
|
||||
// nur bei /api/uploads/* griff. Defense-in-Depth: dieselbe Route auch
|
||||
// ohne /api-Präfix freischalten – damit der Backend-Owner-Check immer
|
||||
// läuft, egal wie nginx konfiguriert ist.
|
||||
app.get('/uploads/*', authenticate as any, (req, res, next) => {
|
||||
req.query.path = req.originalUrl.split('?')[0];
|
||||
return (downloadFile as any)(req, res, next);
|
||||
});
|
||||
|
||||
// Cache-Control für alle API-Responses: `no-store` verhindert, dass Shared
|
||||
// Caches (CDN/Reverse-Proxy/Browser-History) JSON mit sensiblen Daten
|
||||
// vorhalten. Statische Frontend-Assets unter /assets/* sind weiter cacheable
|
||||
|
||||
Reference in New Issue
Block a user