koa-classic-server 3.0.0-alpha.0 → 3.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,421 @@
1
+ # Security Improvement for V3
2
+
3
+ Analisi di sicurezza del progetto `koa-classic-server` v3.0.0-alpha.0, con roadmap degli interventi da effettuare.
4
+
5
+ ---
6
+
7
+ ## Indice
8
+
9
+ ### Punti di Forza (da mantenere e documentare)
10
+ - [ ] [PS-1] Path Traversal — protezione multi-layer
11
+ - [ ] [PS-2] Hidden Files/Directories — controllo esplicito
12
+ - [ ] [PS-3] XSS Prevention — escaping nel directory listing
13
+ - [ ] [PS-4] Security Headers — CSP, X-Frame-Options, Referrer-Policy
14
+ - [ ] [PS-5] Dipendenze — superficie d'attacco minima
15
+
16
+ ### Miglioramenti Prioritari
17
+ - [x] [M-1] Timeout configurabile sul template rendering *(Medio)*
18
+ - [x] [M-2] Cache staleness su filesystem NFS/distribuiti *(Medio)*
19
+ - [x] [M-3] Documentare il rischio DNS Rebinding *(Basso)*
20
+ - [x] [M-4] Documentare i limiti dei security headers sui file statici *(Basso)*
21
+
22
+ ### Nice-to-Have
23
+ - [x] [N-1] Logger iniettabile dall'esterno
24
+ - [x] [N-2] Protezione contro directory listing con molti file (DoS)
25
+
26
+ ---
27
+
28
+ ## Punti di Forza
29
+
30
+ ### [PS-1] Path Traversal
31
+
32
+ La protezione è implementata a più livelli in `index.cjs` (righe 884–910):
33
+
34
+ 1. **Null byte guard** — rifiuta con `400 Bad Request` qualsiasi path contenente `\0`
35
+ 2. **Normalizzazione** — `path.normalize()` applicata prima di qualsiasi `path.join()`
36
+ 3. **Boundary check** — il path risolto deve iniziare con `rootDir`; altrimenti risponde `403 Forbidden`
37
+ 4. **URL-encoded variants** — gestite automaticamente dal layer di parsing di Koa (es. `%2e%2e%2f`)
38
+
39
+ ```js
40
+ if (requestedPath.includes('\0')) {
41
+ ctx.status = 400;
42
+ ctx.body = 'Bad Request';
43
+ return;
44
+ }
45
+ const normalizedPath = path.normalize(requestedPath);
46
+ const fullPath = path.join(normalizedRootDir, normalizedPath);
47
+ if (!fullPath.startsWith(normalizedRootDir)) {
48
+ ctx.status = 403;
49
+ ctx.body = 'Forbidden';
50
+ return;
51
+ }
52
+ ```
53
+
54
+ Test di copertura: `__tests__/security.test.js` verifica `/../package.json`, `/%2e%2e%2f`, `/../../../etc/hosts`.
55
+
56
+ ---
57
+
58
+ ### [PS-2] Hidden Files/Directories
59
+
60
+ Introdotta in v3.0.0, l'opzione `hidden` permette un controllo granulare sulla visibilità di file e directory (righe ~537–550, ~760–795 in `index.cjs`):
61
+
62
+ - **Default `'visible'`** per dot-files e dot-directory (allineato alla *design philosophy* — vedi `CLAUDE.md`)
63
+ - **Blacklist assoluta** per pattern come `.git`, `.svn` (prevale su whitelist e default)
64
+ - **Whitelist** per `.well-known` (sempre visibile; utile per ACME / Let's Encrypt)
65
+ - **Pattern `alwaysHide`** — supporta glob e `RegExp` per match path-aware
66
+
67
+ Priority logic: `blacklist > whitelist > alwaysHide > default`.
68
+
69
+ > **Cambio rispetto al ciclo v3-alpha:** una prima implementazione di PS-2 aveva impostato `dotFiles.default: 'hidden'` come hardening-by-default + un warning runtime se omesso. Quella scelta è stata revertita alla finale v3.0.0 perché violava il principio "HTTP file server first" (`GET /.env` ritornava 404 anche se il file esisteva — sorpresa per l'operatore). PS-2 ora fornisce *meccanismi* di hardening; la *policy* (cosa nascondere) è scelta esplicita dell'operatore, documentata nella **Security Checklist** di `README.md` e `DOCUMENTATION.md`.
70
+
71
+ Test di copertura: `__tests__/hidden-option.test.js` — copre sia il default `'visible'` (system behavior) sia il path opt-in `default: 'hidden'`.
72
+
73
+ ---
74
+
75
+ ### [PS-3] XSS Prevention
76
+
77
+ Tutti i nomi file nel directory listing passano per `escapeHtml()` (righe 119–125):
78
+
79
+ ```js
80
+ const _HTML_ESCAPE_MAP = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' };
81
+ function escapeHtml(unsafe) {
82
+ if (typeof unsafe !== 'string') return unsafe;
83
+ return unsafe.replace(_HTML_ESCAPE_RE, c => _HTML_ESCAPE_MAP[c]);
84
+ }
85
+ ```
86
+
87
+ L'header `Content-Disposition` usa encoding RFC 5987 (percent-encoding UTF-8) con fallback ASCII quoted-string.
88
+
89
+ ---
90
+
91
+ ### [PS-4] Security Headers
92
+
93
+ Applicati alle pagine generate dal middleware (directory listing, pagine di errore):
94
+
95
+ | Header | Valore |
96
+ |---|---|
97
+ | `Content-Security-Policy` | `default-src 'none'; style-src '<hash>'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'` |
98
+ | `X-Content-Type-Options` | `nosniff` |
99
+ | `X-Frame-Options` | `DENY` |
100
+ | `Referrer-Policy` | `no-referrer` |
101
+ | `Permissions-Policy` | `camera=(), microphone=(), geolocation=(), payment=()` |
102
+
103
+ Il CSP usa un hash SHA-256 del CSS inline invece di `'unsafe-inline'`.
104
+
105
+ Test di copertura: `__tests__/security-headers.test.js`.
106
+
107
+ ---
108
+
109
+ ### [PS-5] Dipendenze — Superficie d'Attacco Minima
110
+
111
+ | Pacchetto | Tipo | Note |
112
+ |---|---|---|
113
+ | `mime-types ^3.0.2` | Runtime | Unica dipendenza runtime |
114
+ | `koa ^2.16.4 \|\| >=3.1.2` | Peer | Sicurezza dipende dalla versione scelta dall'utente |
115
+ | `jest`, `supertest`, `eslint`, `ejs`, `autocannon`, `inquirer` | Dev only | Non impattano il bundle di produzione |
116
+
117
+ Superficie d'attacco della supply chain: **molto ridotta**.
118
+
119
+ ---
120
+
121
+ ## Miglioramenti Prioritari
122
+
123
+ ### [M-1] Timeout configurabile sul template rendering *(Priorità: Media)*
124
+
125
+ **Problema**
126
+
127
+ Il callback `template.render` viene eseguito senza alcun timeout. Se la funzione esegue operazioni async lente (query DB, fetch remoti), la connessione rimane aperta indefinitamente, esponendo il server a un potenziale DoS per esaurimento di connessioni.
128
+
129
+ **Soluzione proposta**
130
+
131
+ Aggiungere un'opzione `template.renderTimeout` (default: `5000` ms) e wrappare la chiamata:
132
+
133
+ ```js
134
+ const TIMEOUT_MS = options.template?.renderTimeout ?? 5000;
135
+
136
+ const renderWithTimeout = Promise.race([
137
+ options.template.render(ctx, next, filePath, rawBuffer),
138
+ new Promise((_, reject) =>
139
+ setTimeout(() => reject(new Error('Template render timeout')), TIMEOUT_MS)
140
+ )
141
+ ]);
142
+
143
+ try {
144
+ await renderWithTimeout;
145
+ } catch (err) {
146
+ ctx.status = 500;
147
+ ctx.body = 'Internal Server Error';
148
+ }
149
+ ```
150
+
151
+ **Impatto:** basso (aggiunta opzionale, backward-compatible).
152
+
153
+ ---
154
+
155
+ ### [M-2] Cache staleness su filesystem NFS/distribuiti *(Priorità: Media)*
156
+
157
+ **Problema**
158
+
159
+ La validazione della cache server-side si basa esclusivamente su `mtime + size` (righe 1055–1058). Su filesystem NFS o distribuiti (es. container con volumi montati), `mtime` può non aggiornarsi immediatamente dopo una modifica, portando il middleware a servire contenuti obsoleti.
160
+
161
+ **Soluzione proposta**
162
+
163
+ 1. Aggiungere un'opzione `serverCache.maxAge` (in ms) per l'invalidazione time-based come secondo layer di controllo.
164
+ 2. Documentare chiaramente la limitazione nella documentazione e nei JSDoc.
165
+
166
+ ```js
167
+ serverCache: {
168
+ rawFile: {
169
+ enabled: false,
170
+ maxSize: 52428800,
171
+ maxFileSize: 1048576,
172
+ maxAge: 0 // 0 = disabilitato, altrimenti ms
173
+ }
174
+ }
175
+ ```
176
+
177
+ **Impatto:** medio (richiede modifica alla logica LFU cache).
178
+
179
+ ---
180
+
181
+ ### [M-3] Documentare il rischio DNS Rebinding *(Priorità: Bassa)*
182
+
183
+ **Problema**
184
+
185
+ Il middleware non valida l'header `Host`. In scenari intranet/localhost senza reverse proxy davanti, un attaccante può eseguire attacchi di DNS rebinding per accedere al server come se fosse un'origine fidata.
186
+
187
+ **Soluzione proposta**
188
+
189
+ Non è necessario implementarlo nel middleware (responsabilità del reverse proxy o di Koa stesso), ma va documentato come prerequisito di deployment:
190
+
191
+ > **Nota di sicurezza:** questo middleware non valida l'header `Host`. In produzione, è necessario configurare un reverse proxy (nginx, Caddy) che accetti solo gli hostname attesi, oppure usare [`koa-host-header-safe`](https://github.com/search?q=koa+host+header) o simili.
192
+
193
+ **Stato V3:** documentato in `docs/DOCUMENTATION.md` → *Best Practices → Sicurezza → DNS Rebinding*, con esempio nginx e middleware Koa di allowlist su `ctx.host`.
194
+
195
+ ---
196
+
197
+ ### [M-4] Documentare i limiti dei security headers sui file statici *(Priorità: Bassa)*
198
+
199
+ **Problema**
200
+
201
+ I security headers (CSP, X-Frame-Options, ecc.) vengono aggiunti **solo** alle pagine generate dal middleware (directory listing, errori 404/500). I file statici serviti direttamente (HTML, JS, CSS dell'utente) non ricevono alcun header di sicurezza aggiuntivo.
202
+
203
+ Questo comportamento è by-design ma può generare false aspettative negli utenti che si aspettano una protezione automatica su tutti i file.
204
+
205
+ **Soluzione proposta**
206
+
207
+ Aggiungere nella documentazione una sezione dedicata che spieghi:
208
+ - Quali pagine ricevono i security headers
209
+ - Come aggiungere headers custom sui file statici tramite Koa middleware separato
210
+ - Esempio con `ctx.set()` a monte di `koa-classic-server`
211
+
212
+ **Stato V3:** documentato in `docs/DOCUMENTATION.md` → *Best Practices → Sicurezza → Limiti dei Security Headers sui file statici*, con tabella degli header impostati automaticamente, esempio di middleware Koa upstream con CSP/HSTS/Referrer-Policy, e note operative su CSP report-only e COOP/COEP.
213
+
214
+ ---
215
+
216
+ ## Nice-to-Have
217
+
218
+ ### [N-1] Logger iniettabile dall'esterno
219
+
220
+ **Problema**
221
+
222
+ Gli errori interni (stream error, file access error) vengono scritti direttamente su `console.error`. In produzione, con sistemi di log aggregati, questo può esporre stack trace o percorsi file in output non controllati.
223
+
224
+ **Soluzione proposta**
225
+
226
+ Aggiungere un'opzione `logger` che accetti un oggetto compatibile con l'interfaccia standard (`{ error, warn, info }`):
227
+
228
+ ```js
229
+ koaClassicServer(rootDir, {
230
+ logger: pinoInstance // o winston, console, ecc.
231
+ })
232
+ ```
233
+
234
+ Default: `console` (backward-compatible).
235
+
236
+ ---
237
+
238
+ ### [N-2] Protezione contro directory listing con molti file
239
+
240
+ **Problema**
241
+
242
+ Il directory listing processa le entry in batch da 64 elementi con `Promise.all()`. Con directory contenenti decine di migliaia di file, la generazione della risposta può occupare molta memoria e CPU, contribuendo a un DoS indiretto.
243
+
244
+ **Soluzione proposta**
245
+
246
+ 1. Aggiungere un'opzione `dirListing.maxEntries` (default: es. `10000`) che tronca il listing e mostra un avviso.
247
+ 2. Aggiungere paginazione opzionale al directory listing.
248
+
249
+ ---
250
+
251
+ ## Riepilogo
252
+
253
+ | ID | Descrizione | Priorità | Stato |
254
+ |---|---|---|---|
255
+ | PS-1 | Path Traversal multi-layer | — | Implementato |
256
+ | PS-2 | Hidden Files/Directories | — | Implementato |
257
+ | PS-3 | XSS Prevention nel listing | — | Implementato |
258
+ | PS-4 | Security Headers CSP/HSTS | — | Implementato |
259
+ | PS-5 | Dipendenze minimali | — | Implementato |
260
+ | M-1 | Timeout template rendering | Media | Implementato |
261
+ | M-2 | Cache staleness NFS | Media | Implementato |
262
+ | M-3 | Documentare DNS Rebinding | Bassa | Documentato |
263
+ | M-4 | Documentare limiti security headers | Bassa | Documentato |
264
+ | N-1 | Logger iniettabile | Nice-to-have | Implementato |
265
+ | N-2 | Protezione DoS directory listing | Nice-to-have | Implementato (con caveat — vedi sotto) |
266
+
267
+ ---
268
+
269
+ ## Future Work — v3.1
270
+
271
+ ### [F-1] Opt-in streaming read per directory adversarial *(rimandato a v3.1)*
272
+
273
+ **Contesto**
274
+
275
+ La prima implementazione di N-2 (v3.0.0-alpha.0) usava `fs.promises.opendir()` con async iterator per limitare la lettura a `dirListing.maxEntries` entry e bounded la RAM indipendentemente dalla dimensione su disco. I benchmark hanno mostrato una regressione di latenza di 3-4× sui listing rispetto a v2 (es. dir 10k file: 90 ms → 405 ms), dovuta all'overhead di una `Promise` per ogni entry nell'async iterator.
276
+
277
+ Prima del rilascio è stato applicato un fix (vedi commit di v3.0.0-alpha.0): la lettura è tornata a usare `fs.promises.readdir({ withFileTypes: true })` seguita da `slice(0, dirListing.maxEntries)`. Recupera le performance v2, ma **rinuncia alla garanzia "RAM bounded regardless of disk size"**: una directory con milioni di file alloca milioni di Dirent prima dello slicing.
278
+
279
+ **Decisione v3.0**
280
+
281
+ Per il caso d'uso primario (servire asset statici controllati dall'operatore) la nuova implementazione è ottimale. Il caso edge — directory scrivibile da utenti non fidati, attaccante che crea milioni di file — non è la modalità d'uso dichiarata del middleware e va affrontata a livello applicativo / OS.
282
+
283
+ **Proposta per v3.1**
284
+
285
+ Aggiungere un'opzione `dirListing.readMode` (`'fast' | 'bounded'`, default `'fast'`):
286
+
287
+ ```javascript
288
+ app.use(koaClassicServer(rootDir, {
289
+ dirListing: {
290
+ maxEntries: 1000,
291
+ readMode: 'bounded', // opendir() streaming, RAM bounded a O(maxEntries)
292
+ }
293
+ }));
294
+ ```
295
+
296
+ - `'fast'` (default) — comportamento v3.0: readdir + slice, performance v2-class
297
+ - `'bounded'` — opendir async iterator: lettura interrotta a `dirListing.maxEntries`, RAM bounded indipendentemente dalla dimensione su disco, latenza più alta sui listing
298
+
299
+ Trade-off documentato chiaramente nella user-facing doc. Da valutare prima del freeze 3.1:
300
+ - Test simmetrici nelle due modalità
301
+ - Validazione factory (`dirListing.readMode` ∈ `{'fast', 'bounded'}`)
302
+ - Aggiornamento README + DOCUMENTATION.md con esempio di scelta
303
+
304
+ Il caso d'uso target di `'bounded'`: hosting multi-tenant con directory scrivibili da utenti (es. `/uploads`), backup server, log shipper con cartelle spool.
305
+
306
+ **Rinviato perché**
307
+
308
+ - Non è regressione rispetto a v2 (v2 aveva esattamente questo profilo memoria)
309
+ - Caso d'uso adversarial-directory minoritario nel target del middleware
310
+ - Aggiungere un'opzione API non banale richiede design review e test approfonditi, meglio non aggiungerla in fretta nel rush release di 3.0
311
+
312
+ ---
313
+
314
+ ### [F-1bis] Brainstorm — hybrid automatico fast/bounded *(da decidere in v3.1)*
315
+
316
+ In sede di discussione dei default v3.0 è emersa la richiesta di valutare un **terzo modo**: una scelta automatica fra `fast` e `bounded` basata sulla dimensione effettiva della directory, in modo che 99% degli operatori non debbano configurare nulla. Documento qui i risultati del brainstorm così la decisione finale per v3.1 ha già contesto.
317
+
318
+ #### Vincolo tecnico: chicken-and-egg
319
+
320
+ Per scegliere il path PRIMA di leggere serve un modo *economico* di stimare il numero di entry. `readdir()` (fast) lo scopri solo *dopo* aver pagato l'allocazione completa — quindi il danno è già fatto. `opendir()` (bounded) lo scopri streamando — ma se stai streamando hai già pagato l'overhead per-entry, non c'è più nulla da salvare.
321
+
322
+ L'hybrid puramente automatico richiede una di queste premesse:
323
+
324
+ 1. **Size hint dal FS** (`fs.stat().size` su directory)
325
+ 2. **Probe-and-commit** (leggi le prime N entry, poi decidi)
326
+ 3. **Cache di metadati da richieste precedenti**
327
+
328
+ Tutte e tre hanno problemi.
329
+
330
+ #### Approccio A — `fs.stat()` size hint
331
+
332
+ Su ext4/xfs/tmpfs la `size` di una directory è grossolanamente correlata col numero di entry (~24-64 byte per entry). Una stat() extra costa ~0.1 ms.
333
+
334
+ ```javascript
335
+ const dirStat = await fs.promises.stat(toOpen);
336
+ if (dirStat.size > THRESHOLD_BYTES) {
337
+ return streamingRead(toOpen, maxEntries); // bounded
338
+ } else {
339
+ return fastRead(toOpen, maxEntries); // readdir + slice
340
+ }
341
+ ```
342
+
343
+ **Pro:** decisione *prima* dell'allocazione, costo ridotto, semplice.
344
+ **Contro decisivo:**
345
+ - **FS-dependent**: NFS / remote FS / FUSE / overlayfs spesso ritornano `size: 0` o valori non significativi per le directory.
346
+ - **False negative**: dir con `size` piccola ma molte entry (nomi corti, hash table compatta) → bypassa il safe path → OOM possibile.
347
+ - **False positive**: dir con `size` grande ma poche entry (nomi molto lunghi) → safe path inutile → 3-4× più lento del necessario.
348
+ - Soglia magica filesystem-dependent.
349
+
350
+ Funziona ~85% dei casi su FS POSIX locali, ma "85%" su un comportamento di sicurezza non è abbastanza.
351
+
352
+ #### Approccio B — Probe-and-commit
353
+
354
+ Apri sempre con `opendir()`. Leggi le prime N entry (es. 5000) via streaming, poi decidi:
355
+
356
+ ```javascript
357
+ const handle = await fs.opendir(toOpen);
358
+ const buffer = [];
359
+ for await (const entry of handle) {
360
+ buffer.push(entry);
361
+ if (buffer.length >= PROBE_LIMIT) break;
362
+ }
363
+ // dir piccola → buffer è già il risultato finale
364
+ // dir grande → continua streaming (slow) o butta e ricomincia con readdir (work duplicato)
365
+ ```
366
+
367
+ **Pro:** niente heuristica FS-dependent, decisione basata su dato reale.
368
+ **Contro decisivo:**
369
+ - Paghi sempre l'overhead opendir per le prime N entry (N=5000 → ~155 ms anche su dir di 100 file).
370
+ - Per dir piccole è una regressione netta.
371
+ - Se sopra threshold devi scegliere fra continuare streaming (lento) o re-leggere via readdir (lavoro duplicato).
372
+
373
+ Non hybridizza davvero: regressione per i casi piccoli.
374
+
375
+ #### Approccio C — Cache di metadati
376
+
377
+ Ricorda il numero di entry dalla richiesta precedente alla stessa directory.
378
+
379
+ **Contro:**
380
+ - Prima richiesta sempre slow.
381
+ - Stale cache su FS che cambiano.
382
+ - Multi-process unsafe.
383
+ - Memory overhead.
384
+
385
+ Scartato in fase di brainstorm.
386
+
387
+ #### Cosa fanno nginx e Apache
388
+
389
+ Ricerca rapida sul comportamento di altri server statici:
390
+
391
+ | Server | Modulo | Strategia | Note |
392
+ |---|---|---|---|
393
+ | **nginx** | `autoindex` | `opendir()` + iterazione sempre | Performance "decente, non eccellente". Documentazione raccomanda di disabilitarlo in produzione. |
394
+ | **Apache HTTPd** | `mod_autoindex` | `opendir()` + iterazione sempre | Idem. Considerato feature di dev/admin, non hot path. |
395
+ | **lighttpd** | `mod_dirlisting` | `opendir()` + iterazione | Stesso pattern. |
396
+ | **caddy** | listing module | `os.ReadDir()` (Go), che è l'equivalente di readdir+slice | Niente hybrid. |
397
+
398
+ **Pattern industria**: nessuno fa hybrid automatico. La scelta universale è **`opendir()` sempre** (accetta la lentezza come prezzo della sicurezza) oppure **`readdir()` sempre** (caddy, accetta la potenziale RAM exhaustion).
399
+
400
+ La motivazione consensuale: *autoindex non è un hot path. Chi serve milioni di file con autoindex acceso o ha una specifica esigenza (lo configurerà) o sta abusando della feature (servirà disabilitarlo).*
401
+
402
+ #### Conclusione del brainstorm
403
+
404
+ L'hybrid automatico ha tre realizzazioni teoriche, **nessuna soddisfacente**:
405
+
406
+ | Approccio | Pro | Contro decisivo |
407
+ |---|---|---|
408
+ | A — stat() heuristic | trasparente | FS-dependent, soglia magica, false-negative pericolosi |
409
+ | B — probe-and-commit | non heuristica | overhead sempre, lavoro duplicato |
410
+ | C — cache | trasparente per richieste successive | prima sempre slow, stale, unsafe |
411
+
412
+ Le opzioni *robuste* restano due:
413
+
414
+ 1. **`readMode: 'fast' | 'bounded'` esplicito** (la proposta originale [F-1]) — operatore sceglie consapevolmente
415
+ 2. **`readMode: 'auto' | 'fast' | 'bounded'`** con `auto` = approccio A — friendly per i casi POSIX comuni, escape hatch per workload critici
416
+
417
+ **Raccomandazione per v3.1:** preferire (1) — è quello che fanno tutti gli altri server. La variante (2) con `auto` come default amichevole è tecnicamente possibile ma il caveat "best-effort, fragile su NFS/FUSE" rende l'opzione `auto` più rumorosa da documentare di quanto valga.
418
+
419
+ **Decisione finale rimandata al freeze 3.1.**
420
+
421
+ ---
@@ -127,7 +127,7 @@ const path = require('path');
127
127
  const app = new Koa();
128
128
 
129
129
  app.use(koaClassicServer(path.join(__dirname, 'public'), {
130
- showDirContents: true,
130
+ dirListing: { enabled: true },
131
131
  template: {
132
132
  ext: ['ejs'],
133
133
  render: async (ctx, next, filePath) => {
@@ -427,7 +427,7 @@ app.use(
427
427
  koaClassicServer(
428
428
  __dirname + '/public',
429
429
  {
430
- showDirContents: true,
430
+ dirListing: { enabled: true },
431
431
  template: {
432
432
  render: async (ctx, next, filePath) => {
433
433
  try {
@@ -467,7 +467,7 @@ app.use(
467
467
  koaClassicServer(
468
468
  __dirname + '/public',
469
469
  {
470
- showDirContents: true,
470
+ dirListing: { enabled: true },
471
471
  template: {
472
472
  render: async (ctx, next, filePath) => {
473
473
  try {
@@ -540,7 +540,7 @@ app.use(
540
540
  koaClassicServer(
541
541
  __dirname + '/public',
542
542
  {
543
- showDirContents: true,
543
+ dirListing: { enabled: true },
544
544
  template: {
545
545
  render: async (ctx, next, filePath) => {
546
546
  try {
@@ -680,7 +680,7 @@ app.use(
680
680
  koaClassicServer(
681
681
  __dirname + `${ital8Conf.wwwPath}`,
682
682
  {
683
- showDirContents: true,
683
+ dirListing: { enabled: true },
684
684
  urlsReserved: ['/' + ital8Conf.adminPrefix, '/' + ital8Conf.apiPrefix, '/' + ital8Conf.viewsPrefix],
685
685
  template: {
686
686
  render: async (ctx, next, filePath) => {
@@ -157,7 +157,7 @@ app.use(
157
157
  classicServer(
158
158
  __dirname + "/examples",
159
159
  {
160
- showDirContents: true,
160
+ dirListing: { enabled: true },
161
161
  template: {
162
162
  render: templateRender,
163
163
  ext: ["ejs", "EJS"],