koa-classic-server 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,593 @@
1
+ # DEBUG REPORT - koa-classic-server
2
+
3
+ **Data Analisi:** 2025-11-17
4
+ **Versione Analizzata:** 1.1.0
5
+ **Branch:** claude/featuring-koa-smart-server-016TZsdaPURgHLiQHmCFJFsk
6
+
7
+ ---
8
+
9
+ ## Sommario Esecutivo
10
+
11
+ Sono stati identificati **8 problemi** di cui:
12
+ - 🔴 **CRITICI:** 2 (sicurezza)
13
+ - 🟠 **ALTA PRIORITÀ:** 3 (funzionalità core)
14
+ - 🟡 **MEDIA PRIORITÀ:** 2 (robustezza)
15
+ - 🔵 **BASSA PRIORITÀ:** 1 (qualità codice)
16
+
17
+ ---
18
+
19
+ ## 🔴 PROBLEMI CRITICI
20
+
21
+ ### 1. Path Traversal Vulnerability (CRITICO - SICUREZZA)
22
+
23
+ **Location:** `index.cjs:104`
24
+
25
+ **Codice:**
26
+ ```javascript
27
+ toOpen = rootDir + decodeURIComponent(pageHrefOutPrefix.pathname);
28
+ ```
29
+
30
+ **Problema:**
31
+ Non c'è validazione del path per prevenire attacchi di path traversal. Un attaccante può accedere a file al di fuori di `rootDir`.
32
+
33
+ **Attacco Esempio:**
34
+ ```
35
+ GET /../../../etc/passwd
36
+ GET /../config/database.yml
37
+ GET /../.env
38
+ ```
39
+
40
+ **Impatto:**
41
+ - Accesso non autorizzato a file sensibili del server
42
+ - Potenziale lettura di credenziali, configurazioni, chiavi private
43
+ - Violazione della sicurezza del sistema
44
+
45
+ **Severità:** 🔴 CRITICA
46
+
47
+ **Fix Raccomandato:**
48
+ ```javascript
49
+ const path = require('path');
50
+
51
+ // Normalizza e verifica che il path risultante sia dentro rootDir
52
+ const requestedPath = path.normalize(decodeURIComponent(pageHrefOutPrefix.pathname));
53
+ const fullPath = path.join(rootDir, requestedPath);
54
+
55
+ // Verifica che il path finale sia dentro rootDir
56
+ if (!fullPath.startsWith(path.resolve(rootDir))) {
57
+ ctx.status = 403;
58
+ ctx.body = 'Forbidden';
59
+ return;
60
+ }
61
+
62
+ toOpen = fullPath;
63
+ ```
64
+
65
+ **Riferimenti:**
66
+ - OWASP: Path Traversal
67
+ - CWE-22: Improper Limitation of a Pathname to a Restricted Directory
68
+
69
+ ---
70
+
71
+ ### 2. Mancanza di Gestione Errori Template Rendering (CRITICO - DISPONIBILITÀ)
72
+
73
+ **Location:** `index.cjs:167`
74
+
75
+ **Codice:**
76
+ ```javascript
77
+ await options.template.render(ctx, next, toOpen);
78
+ ```
79
+
80
+ **Problema:**
81
+ Nessun try-catch attorno alla chiamata `template.render`. Se il rendering fallisce, l'errore non gestito può crashare l'applicazione.
82
+
83
+ **Impatto:**
84
+ - Crash del server su errore di rendering
85
+ - Denial of Service potenziale
86
+ - Esperienza utente degradata
87
+
88
+ **Severità:** 🔴 CRITICA
89
+
90
+ **Fix Raccomandato:**
91
+ ```javascript
92
+ if (options.template.ext.includes(fileExt)) {
93
+ try {
94
+ await options.template.render(ctx, next, toOpen);
95
+ return;
96
+ } catch (error) {
97
+ console.error('Template rendering error:', error);
98
+ ctx.status = 500;
99
+ ctx.body = 'Internal Server Error - Template Rendering Failed';
100
+ return;
101
+ }
102
+ }
103
+ ```
104
+
105
+ ---
106
+
107
+ ## 🟠 PROBLEMI ALTA PRIORITÀ
108
+
109
+ ### 3. Status Code 404 Non Settato (ALTA - STANDARD HTTP)
110
+
111
+ **Location:**
112
+ - `index.cjs:110` (file non esiste)
113
+ - `index.cjs:128` (directory listing disabilitato)
114
+
115
+ **Codice:**
116
+ ```javascript
117
+ // Linea 110
118
+ if (!fs.existsSync(toOpen)) {
119
+ ctx.body = requestedUrlNotFound();
120
+ // Manca: ctx.status = 404;
121
+ return;
122
+ }
123
+
124
+ // Linea 128
125
+ } else {
126
+ // allora non devo mostrare il contenuto della directory
127
+ ctx.body = requestedUrlNotFound();
128
+ // Manca: ctx.status = 404;
129
+ }
130
+ ```
131
+
132
+ **Problema:**
133
+ Quando una risorsa non viene trovata, viene restituito un body HTML con "Not Found", ma lo status HTTP rimane 200 (OK) invece di 404 (Not Found).
134
+
135
+ **Impatto:**
136
+ - Violazione standard HTTP
137
+ - Cache proxy potrebbero cachare errori come successi
138
+ - SEO negativo (motori di ricerca indicizzano pagine 404 come valide)
139
+ - Client non possono distinguere successo da errore basandosi sullo status code
140
+
141
+ **Verifica:**
142
+ ```bash
143
+ curl -I http://localhost:3000/file-che-non-esiste.txt
144
+ # Atteso: HTTP/1.1 404 Not Found
145
+ # Attuale: HTTP/1.1 200 OK
146
+ ```
147
+
148
+ **Severità:** 🟠 ALTA
149
+
150
+ **Fix Raccomandato:**
151
+ ```javascript
152
+ // Linea 110
153
+ if (!fs.existsSync(toOpen)) {
154
+ ctx.status = 404;
155
+ ctx.body = requestedUrlNotFound();
156
+ return;
157
+ }
158
+
159
+ // Linea 128
160
+ } else {
161
+ ctx.status = 404;
162
+ ctx.body = requestedUrlNotFound();
163
+ }
164
+ ```
165
+
166
+ ---
167
+
168
+ ### 4. Race Condition nella Lettura File (ALTA - AFFIDABILITÀ)
169
+
170
+ **Location:** `index.cjs:107-172`
171
+
172
+ **Codice:**
173
+ ```javascript
174
+ if (!fs.existsSync(toOpen)) {
175
+ // ...
176
+ }
177
+ // ... altre operazioni ...
178
+ const src = fs.createReadStream(toOpen); // Linea 172
179
+ ```
180
+
181
+ **Problema:**
182
+ C'è un gap temporale (TOCTOU - Time-of-check to Time-of-use) tra:
183
+ 1. Controllo esistenza file (`existsSync`)
184
+ 2. Lettura file (`createReadStream`)
185
+
186
+ Se il file viene cancellato tra questi due momenti, `createReadStream` lancia un errore non gestito.
187
+
188
+ **Impatto:**
189
+ - Possibile crash del server
190
+ - Errore non gestito raggiunge l'utente
191
+ - Log inquinati da stack trace
192
+
193
+ **Severità:** 🟠 ALTA
194
+
195
+ **Fix Raccomandato:**
196
+ ```javascript
197
+ async function loadFile(toOpen) {
198
+ // Template rendering logic...
199
+
200
+ try {
201
+ // Verifica esistenza prima di stream
202
+ await fs.promises.access(toOpen, fs.constants.R_OK);
203
+
204
+ let mimeType = mime.lookup(toOpen);
205
+ const src = fs.createReadStream(toOpen);
206
+
207
+ // Gestisci errori stream
208
+ src.on('error', (err) => {
209
+ console.error('Stream error:', err);
210
+ if (!ctx.headerSent) {
211
+ ctx.status = 500;
212
+ ctx.body = 'Error reading file';
213
+ }
214
+ });
215
+
216
+ ctx.response.set("content-type", mimeType);
217
+ ctx.response.set("content-disposition",
218
+ `inline; filename=${pageHrefOutPrefix.pathname.substring(1)}`);
219
+ ctx.body = src;
220
+ } catch (error) {
221
+ console.error('File access error:', error);
222
+ ctx.status = 404;
223
+ ctx.body = requestedUrlNotFound();
224
+ }
225
+ }
226
+ ```
227
+
228
+ ---
229
+
230
+ ### 5. Estrazione Estensione File Fragile (ALTA - ROBUSTEZZA)
231
+
232
+ **Location:** `index.cjs:163-164`
233
+
234
+ **Codice:**
235
+ ```javascript
236
+ const a_path = toOpen.split(".");
237
+ const fileExt = a_path[a_path.length - 1];
238
+ ```
239
+
240
+ **Problema:**
241
+ Metodo fragile per estrarre l'estensione:
242
+ - File senza estensione: `README` → estensione = "README" (errato)
243
+ - File con più punti: `archive.tar.gz` → estensione = "gz" (potrebbe essere errato)
244
+ - File nascosti Unix: `.gitignore` → estensione = "gitignore" (errato)
245
+ - Path con punti: `/folder.backup/file` → estensione = "backup/file" (errato)
246
+
247
+ **Impatto:**
248
+ - Template rendering potrebbe attivarsi su file sbagliati
249
+ - File nascosti potrebbero essere processati erroneamente
250
+
251
+ **Severità:** 🟠 ALTA
252
+
253
+ **Fix Raccomandato:**
254
+ ```javascript
255
+ const path = require('path');
256
+
257
+ async function loadFile(toOpen) {
258
+ if (options.template.ext.length > 0) {
259
+ // Usa path.extname che gestisce correttamente tutti i casi
260
+ const fileExt = path.extname(toOpen).slice(1); // .slice(1) rimuove il punto
261
+
262
+ if (fileExt && options.template.ext.includes(fileExt)) {
263
+ try {
264
+ await options.template.render(ctx, next, toOpen);
265
+ return;
266
+ } catch (error) {
267
+ console.error('Template rendering error:', error);
268
+ ctx.status = 500;
269
+ ctx.body = 'Internal Server Error';
270
+ return;
271
+ }
272
+ }
273
+ }
274
+ // ... resto del codice
275
+ }
276
+ ```
277
+
278
+ ---
279
+
280
+ ## 🟡 PROBLEMI MEDIA PRIORITÀ
281
+
282
+ ### 6. Mancanza Gestione Errori fs.readdirSync (MEDIA - ROBUSTEZZA)
283
+
284
+ **Location:** `index.cjs:183`
285
+
286
+ **Codice:**
287
+ ```javascript
288
+ function show_dir(toOpen) {
289
+ dir = fs.readdirSync(toOpen, { withFileTypes: true }); // possibile error error.code == "ENOENT" ???
290
+ ```
291
+
292
+ **Problema:**
293
+ Il commento stesso riconosce il problema: `readdirSync` può lanciare errori (permessi insufficienti, directory cancellata, etc.) ma non c'è gestione.
294
+
295
+ **Errori Possibili:**
296
+ - `ENOENT`: Directory non esiste più
297
+ - `EACCES`: Permessi insufficienti
298
+ - `ENOTDIR`: Path non è una directory
299
+
300
+ **Impatto:**
301
+ - Crash su errori filesystem
302
+ - Messaggi di errore criptici all'utente
303
+
304
+ **Severità:** 🟡 MEDIA
305
+
306
+ **Fix Raccomandato:**
307
+ ```javascript
308
+ function show_dir(toOpen) {
309
+ let dir;
310
+ try {
311
+ dir = fs.readdirSync(toOpen, { withFileTypes: true });
312
+ } catch (error) {
313
+ console.error('Directory read error:', error);
314
+ return `
315
+ <!DOCTYPE html>
316
+ <html>
317
+ <head><title>Error</title></head>
318
+ <body>
319
+ <h1>Error Reading Directory</h1>
320
+ <p>Unable to access directory contents.</p>
321
+ </body>
322
+ </html>
323
+ `;
324
+ }
325
+
326
+ // ... resto della funzione
327
+ }
328
+ ```
329
+
330
+ ---
331
+
332
+ ### 7. Gestione Inconsistente Content-Disposition (MEDIA - QUALITÀ)
333
+
334
+ **Location:** `index.cjs:174-177`
335
+
336
+ **Codice:**
337
+ ```javascript
338
+ ctx.response.set(
339
+ "content-disposition",
340
+ `inline; filename=${pageHrefOutPrefix.pathname.substring(1)}`
341
+ );
342
+ ```
343
+
344
+ **Problema:**
345
+ Il filename in Content-Disposition non è quotato e non è sanitizzato:
346
+ - Caratteri speciali nel filename potrebbero causare problemi
347
+ - Spazi e caratteri non-ASCII non sono gestiti
348
+ - Secondo RFC 6266, il filename dovrebbe essere quotato se contiene caratteri speciali
349
+
350
+ **Impatto:**
351
+ - Download file con nomi strani potrebbero fallire
352
+ - Alcuni browser potrebbero interpretare male il filename
353
+
354
+ **Severità:** 🟡 MEDIA
355
+
356
+ **Fix Raccomandato:**
357
+ ```javascript
358
+ const path = require('path');
359
+
360
+ // Estrai solo il basename, non l'intero path
361
+ const filename = path.basename(pageHrefOutPrefix.pathname);
362
+
363
+ // Quota il filename per sicurezza
364
+ ctx.response.set(
365
+ "content-disposition",
366
+ `inline; filename="${filename.replace(/"/g, '\\"')}"`
367
+ );
368
+
369
+ // O ancora meglio, usa una libreria come content-disposition:
370
+ // const contentDisposition = require('content-disposition');
371
+ // ctx.response.set("content-disposition", contentDisposition(filename, { type: 'inline' }));
372
+ ```
373
+
374
+ ---
375
+
376
+ ## 🔵 PROBLEMI BASSA PRIORITÀ
377
+
378
+ ### 8. Uso di Array() Invece di [] (BASSA - STILE)
379
+
380
+ **Location:** Multiple locations
381
+
382
+ **Codice:**
383
+ ```javascript
384
+ options.method = Array.isArray( options.method ) ? options.method : Array('GET');
385
+ options.urlsReserved = Array.isArray( options.urlsReserved ) ? options.urlsReserved : Array();
386
+ options.template.ext = ( Array.isArray(options.template.ext) ) ? options.template.ext : Array();
387
+ ```
388
+
389
+ **Problema:**
390
+ `Array('GET')` crea un array con un elemento, ma è meno idiomatico di `['GET']`. Inoltre `Array()` è meno leggibile di `[]`.
391
+
392
+ **Impatto:**
393
+ - Nessun impatto funzionale
394
+ - Ridotta leggibilità codice
395
+
396
+ **Severità:** 🔵 BASSA
397
+
398
+ **Fix Raccomandato:**
399
+ ```javascript
400
+ options.method = Array.isArray(options.method) ? options.method : ['GET'];
401
+ options.urlsReserved = Array.isArray(options.urlsReserved) ? options.urlsReserved : [];
402
+ options.template.ext = Array.isArray(options.template.ext) ? options.template.ext : [];
403
+ ```
404
+
405
+ ---
406
+
407
+ ## Test dei Problemi
408
+
409
+ ### Test Path Traversal (Problema #1)
410
+
411
+ ```javascript
412
+ // test-path-traversal.js
413
+ const supertest = require('supertest');
414
+ const Koa = require('koa');
415
+ const koaClassicServer = require('./index.cjs');
416
+
417
+ const app = new Koa();
418
+ app.use(koaClassicServer(__dirname + '/public'));
419
+ const server = app.listen();
420
+
421
+ // Test attacco path traversal
422
+ supertest(server)
423
+ .get('/../package.json') // Tenta di accedere fuori da public/
424
+ .end((err, res) => {
425
+ console.log('Status:', res.status);
426
+ console.log('Body contains package.json?', res.text.includes('"name"'));
427
+ // Se vedi il contenuto di package.json, la vulnerabilità è confermata
428
+ server.close();
429
+ });
430
+ ```
431
+
432
+ ### Test Status Code 404 (Problema #3)
433
+
434
+ ```javascript
435
+ // test-404-status.js
436
+ const supertest = require('supertest');
437
+ const Koa = require('koa');
438
+ const koaClassicServer = require('./index.cjs');
439
+
440
+ const app = new Koa();
441
+ app.use(koaClassicServer(__dirname + '/public'));
442
+ const server = app.listen();
443
+
444
+ supertest(server)
445
+ .get('/file-che-non-esiste.txt')
446
+ .end((err, res) => {
447
+ console.log('Status Code:', res.status);
448
+ console.log('Expected: 404, Got:', res.status);
449
+ console.log('BUG CONFIRMED:', res.status === 200); // true = bug presente
450
+ server.close();
451
+ });
452
+ ```
453
+
454
+ ### Test Template Error (Problema #2)
455
+
456
+ ```javascript
457
+ // test-template-error.js
458
+ const Koa = require('koa');
459
+ const koaClassicServer = require('./index.cjs');
460
+
461
+ const app = new Koa();
462
+
463
+ // Template render che lancia errore
464
+ const brokenRender = async (ctx, next, filePath) => {
465
+ throw new Error('Simulated template error');
466
+ };
467
+
468
+ app.use(koaClassicServer(__dirname + '/public', {
469
+ template: {
470
+ render: brokenRender,
471
+ ext: ['html']
472
+ }
473
+ }));
474
+
475
+ const server = app.listen(3000);
476
+
477
+ // Accedi a un file .html
478
+ // Il server crasherà con errore non gestito
479
+ ```
480
+
481
+ ---
482
+
483
+ ## Priorità di Fix
484
+
485
+ ### Immediate (Prima del Deploy)
486
+ 1. **Path Traversal** (Problema #1) - CRITICO
487
+ 2. **Status Code 404** (Problema #3) - ALTA
488
+
489
+ ### Breve Termine (Prossimo Release)
490
+ 3. **Template Error Handling** (Problema #2) - CRITICO
491
+ 4. **Race Condition File** (Problema #4) - ALTA
492
+ 5. **Estrazione Estensione** (Problema #5) - ALTA
493
+
494
+ ### Medio Termine (Miglioramenti)
495
+ 6. **fs.readdirSync Error** (Problema #6) - MEDIA
496
+ 7. **Content-Disposition** (Problema #7) - MEDIA
497
+
498
+ ### Opzionale (Refactoring)
499
+ 8. **Array() vs []** (Problema #8) - BASSA
500
+
501
+ ---
502
+
503
+ ## Miglioramenti Generali Raccomandati
504
+
505
+ ### 1. Aggiungere Validazione Input
506
+ ```javascript
507
+ // All'inizio della funzione principale
508
+ if (!rootDir || typeof rootDir !== 'string') {
509
+ throw new TypeError('rootDir must be a non-empty string');
510
+ }
511
+
512
+ if (!path.isAbsolute(rootDir)) {
513
+ throw new Error('rootDir must be an absolute path');
514
+ }
515
+ ```
516
+
517
+ ### 2. Logging Strutturato
518
+ ```javascript
519
+ // Opzione per logging
520
+ options.logger = options.logger || console;
521
+
522
+ // Usa nel codice
523
+ options.logger.error('File not found:', toOpen);
524
+ options.logger.warn('Template rendering failed:', error);
525
+ ```
526
+
527
+ ### 3. Timeout per Template Rendering
528
+ ```javascript
529
+ // Previeni template rendering infiniti
530
+ const renderWithTimeout = (renderFn, timeout = 5000) => {
531
+ return Promise.race([
532
+ renderFn(),
533
+ new Promise((_, reject) =>
534
+ setTimeout(() => reject(new Error('Render timeout')), timeout)
535
+ )
536
+ ]);
537
+ };
538
+ ```
539
+
540
+ ### 4. Sanitizzazione HTML in Directory Listing
541
+ ```javascript
542
+ // Previeni XSS in nomi file
543
+ function escapeHtml(unsafe) {
544
+ return unsafe
545
+ .replace(/&/g, "&amp;")
546
+ .replace(/</g, "&lt;")
547
+ .replace(/>/g, "&gt;")
548
+ .replace(/"/g, "&quot;")
549
+ .replace(/'/g, "&#039;");
550
+ }
551
+
552
+ // Usa quando mostri nomi file
553
+ s_dir += ` <a href="${itemUri}">${escapeHtml(s_name)}</a>`;
554
+ ```
555
+
556
+ ---
557
+
558
+ ## Metriche Codice
559
+
560
+ ### Complessità Ciclomatica
561
+ - **Funzione principale:** ~25 (Alta - da ridurre)
562
+ - **show_dir:** ~15 (Media)
563
+ - **loadFile:** ~5 (Bassa)
564
+
565
+ ### Raccomandazione
566
+ Spezzare la funzione principale in sottofunzioni più piccole:
567
+ - `validateRequest()`
568
+ - `checkReservedUrls()`
569
+ - `resolveFilePath()`
570
+ - `handleResource()`
571
+
572
+ ---
573
+
574
+ ## Conclusioni
575
+
576
+ Il progetto koa-classic-server presenta **2 vulnerabilità critiche di sicurezza** che devono essere risolte immediatamente prima di qualsiasi deploy in produzione:
577
+
578
+ 1. **Path Traversal** - permette accesso a file arbitrari
579
+ 2. **Template Error Unhandled** - può causare crash del server
580
+
581
+ Inoltre, ci sono **3 problemi di alta priorità** che impattano conformità agli standard HTTP e affidabilità:
582
+
583
+ 3. Status code 404 mancante
584
+ 4. Race condition nella lettura file
585
+ 5. Estrazione estensione fragile
586
+
587
+ **Raccomandazione:** Implementare i fix per problemi #1, #2, #3 prima del prossimo release.
588
+
589
+ ---
590
+
591
+ **Report compilato da:** Claude Code Analysis
592
+ **Branch analizzata:** claude/featuring-koa-smart-server-016TZsdaPURgHLiQHmCFJFsk
593
+ **Commit:** f8693a0