fix(18): dot-only Manifest-Dateinamen ("."/"..") in getPackage() ablehnen (CR-01)

Die Zeichen-Whitelist /^[A-Za-z0-9._-]+$/ liess Namen wie ".." durch, weil
Punkt und Bindestrich erlaubte Zeichen sind -- path.join(desktopDistDir, '..')
loest aber in den Elternordner auf und unterlaeuft genau die Verteidigung in
der Tiefe (T-18-02), die diese Zeile laut Kommentar herstellen soll. Jetzt
werden "." und ".." explizit abgelehnt UND der aufgeloeste Pfad zusaetzlich
gegen desktopDistDir geprueft (haelt auch kuenftige Varianten ab, falls die
Whitelist anderswo wiederverwendet wird). Neue Testfaelle fuer manifest-Eintraege
namens "..", "." und "../manifest.json".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-16 17:39:29 +02:00
parent a8964f1a23
commit 0d5c80fbbf
2 changed files with 40 additions and 3 deletions
@@ -161,6 +161,30 @@ describe('DesktopService/DesktopController — HTTP-Durchstich (Phase 18)', () =
expect(res.status).toBe(404);
});
it('Test 7a (dot-only Name im Manifest, CR-01): ".." endet mit 404', async () => {
writeManifest({
linux: { name: '..', size: 123, sha256: 'a'.repeat(64) },
});
const res = await fetch(`${baseUrl}/desktop/download/linux`);
expect(res.status).toBe(404);
});
it('Test 7b (dot-only Name im Manifest, CR-01): "." endet mit 404', async () => {
writeManifest({
linux: { name: '.', size: 123, sha256: 'a'.repeat(64) },
});
const res = await fetch(`${baseUrl}/desktop/download/linux`);
expect(res.status).toBe(404);
});
it('Test 7c (Name mit Traversal-Segment ausserhalb desktopDistDir, CR-01): "../manifest.json" endet mit 404', async () => {
writeManifest({
linux: { name: '../manifest.json', size: 123, sha256: 'a'.repeat(64) },
});
const res = await fetch(`${baseUrl}/desktop/download/linux`);
expect(res.status).toBe(404);
});
it('Test 9 (WR-03, ungueltiger Eintrag im Manifest): name fehlt -- getLatest wirft NotFoundException statt "undefined" als Datei zu suchen', () => {
// Bewusst am `writeManifest()`-Helper vorbei direkt geschrieben -- dessen
// Parametertyp verlangt `name`, hier soll aber genau dessen Fehlen