koa-classic-server 2.6.1 → 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.
Files changed (57) hide show
  1. package/CLAUDE.md +101 -0
  2. package/README.md +564 -591
  3. package/__tests__/benchmark-results-v3.0.0.txt +372 -0
  4. package/__tests__/benchmark.js +1 -1
  5. package/__tests__/caching-headers.test.js +30 -30
  6. package/__tests__/compression-fixtures/data.json +1 -0
  7. package/__tests__/compression-fixtures/large.txt +1 -0
  8. package/__tests__/compression-fixtures/small.txt +1 -0
  9. package/__tests__/compression.test.js +284 -0
  10. package/__tests__/customTest/serversToLoad.util.js +5 -5
  11. package/__tests__/demo-regex-index.js +4 -4
  12. package/__tests__/deprecation-warnings.test.js +71 -183
  13. package/__tests__/directory-sorting-links.test.js +1 -1
  14. package/__tests__/dt-unknown.test.js +39 -28
  15. package/__tests__/ejs.test.js +1 -1
  16. package/__tests__/hidden-fixtures/.dot-dir/inside.txt +1 -0
  17. package/__tests__/hidden-fixtures/.env +2 -0
  18. package/__tests__/hidden-fixtures/.well-known/acme-challenge.txt +1 -0
  19. package/__tests__/hidden-fixtures/config/secrets/password.txt +1 -0
  20. package/__tests__/hidden-fixtures/data.key +1 -0
  21. package/__tests__/hidden-fixtures/file.secret +1 -0
  22. package/__tests__/hidden-fixtures/index.html +1 -0
  23. package/__tests__/hidden-fixtures/normal.txt +1 -0
  24. package/__tests__/hidden-fixtures/subdir/.env +1 -0
  25. package/__tests__/hidden-fixtures/subdir/regular.txt +1 -0
  26. package/__tests__/hidden-option.test.js +407 -0
  27. package/__tests__/hideExtension.test.js +70 -13
  28. package/__tests__/index-option.test.js +18 -16
  29. package/__tests__/index.test.js +14 -10
  30. package/__tests__/listing.test.js +437 -0
  31. package/__tests__/logger.test.js +232 -0
  32. package/__tests__/range-fixtures/sample.txt +1 -0
  33. package/__tests__/range.test.js +223 -0
  34. package/__tests__/security-headers.test.js +165 -0
  35. package/__tests__/security.test.js +148 -162
  36. package/__tests__/server-cache-fixtures/large.txt +1 -0
  37. package/__tests__/server-cache-fixtures/small.txt +1 -0
  38. package/__tests__/server-cache.test.js +594 -0
  39. package/__tests__/symlink.test.js +18 -15
  40. package/__tests__/template-timeout.test.js +321 -0
  41. package/docs/ACTION_PLAN.md +293 -0
  42. package/docs/CHANGELOG.md +289 -0
  43. package/docs/CODE_REVIEW.md +2 -0
  44. package/docs/DOCUMENTATION.md +259 -32
  45. package/docs/EXAMPLES_INDEX_OPTION.md +3 -3
  46. package/docs/FLOW_DIAGRAM.md +15 -13
  47. package/docs/INDEX_OPTION_PRIORITY.md +2 -2
  48. package/docs/OPTIMIZATION_HTTP_CACHING.md +2 -0
  49. package/docs/OPTIMIZATION_ROADMAP_for_V3.md +864 -0
  50. package/docs/PERFORMANCE_COMPARISON.md +7 -7
  51. package/docs/security_improvement_for_V3.md +421 -0
  52. package/docs/template-engine/TEMPLATE_ENGINE_GUIDE.md +5 -5
  53. package/docs/template-engine/esempi-incrementali.js +1 -1
  54. package/eslint.config.mjs +17 -0
  55. package/index.cjs +1507 -429
  56. package/index.mjs +1 -5
  57. package/package.json +9 -1
@@ -0,0 +1,293 @@
1
+ # Piano d'Azione — koa-classic-server
2
+
3
+ > Documento di lavoro per il miglioramento progressivo del progetto.
4
+ > Ogni fase segue il ciclo: **Descrizione → Soluzioni proposte → Domande → Implementazione → Test → Avanzamento**
5
+
6
+ ---
7
+
8
+ ## Indice delle fasi
9
+
10
+ | # | Fase | Priorità | Stato |
11
+ |---|------|----------|-------|
12
+ | 1 | [File nascosti nel directory listing](#fase-1--file-nascosti-nel-directory-listing) | Alta | ⬜ Da fare |
13
+ | 2 | [Opzione glob per escludere file sensibili](#fase-2--opzione-glob-per-escludere-file-sensibili) | Alta | ⬜ Da fare |
14
+ | 3 | [Content-Security-Policy nell'HTML generato](#fase-3--content-security-policy-nellhtml-generato) | Media | ⬜ Da fare |
15
+ | 4 | [Aggiunta ESLint e standardizzazione `===`](#fase-4--aggiunta-eslint-e-standardizzazione-) | Media | ⬜ Da fare |
16
+ | 5 | [Range Requests HTTP 206](#fase-5--range-requests-http-206) | Media | ⬜ Da fare |
17
+ | 6 | [Gzip / Brotli compression](#fase-6--gzip--brotli-compression) | Media | ⬜ Da fare |
18
+ | 7 | [Traduzione commenti in inglese nei test](#fase-7--traduzione-commenti-in-inglese-nei-test) | Bassa | ⬜ Da fare |
19
+ | 8 | [Roadmap rimozione opzioni deprecate (v3.0.0)](#fase-8--roadmap-rimozione-opzioni-deprecate-v300) | Bassa | ⬜ Da fare |
20
+
21
+ ---
22
+
23
+ > **Legenda stato:** ⬜ Da fare · 🔄 In corso · ✅ Completato · ⏸️ In pausa
24
+
25
+ ---
26
+
27
+ ## Processo standard per ogni fase
28
+
29
+ Per ogni fase si segue sempre questo ciclo:
30
+
31
+ 1. **Descrizione** — Spiegazione del problema e del suo impatto
32
+ 2. **Soluzioni proposte** — Almeno 2 alternative valutate con pro/contro
33
+ 3. **Domande** — Se le informazioni disponibili sono < 90%, si chiedono chiarimenti prima di procedere
34
+ 4. **Implementazione** — Modifica del codice sul branch `claude/project-review-EN6kt`
35
+ 5. **Test** — Esecuzione della suite di test e verifica regressioni
36
+ 6. **Avanzamento** — Aggiornamento dello stato in questo file e passaggio alla fase successiva
37
+
38
+ ---
39
+
40
+ ## Fase 1 — File nascosti nel directory listing
41
+
42
+ **Stato:** ⬜ Da fare
43
+ **Priorità:** Alta
44
+ **File coinvolti:** `index.cjs` (funzione `show_dir`)
45
+
46
+ ### Descrizione del problema
47
+
48
+ Il directory listing mostra **tutti** i file e le cartelle presenti nella webroot, inclusi quelli che iniziano con `.` (dot files), come:
49
+
50
+ - `.env` — variabili d'ambiente con credenziali
51
+ - `.gitignore`, `.gitattributes` — metadati Git
52
+ - `.htpasswd` — password Apache
53
+ - `.DS_Store` — metadati macOS
54
+ - `.npmrc` — configurazioni npm con token
55
+
56
+ Server come Apache e Nginx nascondono questi file per default. Un file dimenticato nella webroot potrebbe esporre informazioni sensibili agli utenti del browser.
57
+
58
+ ### Soluzioni proposte
59
+
60
+ *Da definire nella sessione di lavoro dedicata a questa fase.*
61
+
62
+ ### Domande
63
+
64
+ *Da porre prima dell'implementazione se necessario.*
65
+
66
+ ### Implementazione
67
+
68
+ *Da eseguire dopo la scelta della soluzione.*
69
+
70
+ ### Test
71
+
72
+ *Da verificare dopo l'implementazione.*
73
+
74
+ ---
75
+
76
+ ## Fase 2 — Opzione glob per escludere file sensibili
77
+
78
+ **Stato:** ⬜ Da fare
79
+ **Priorità:** Alta
80
+ **File coinvolti:** `index.cjs` (opzioni di configurazione + `show_dir`)
81
+
82
+ ### Descrizione del problema
83
+
84
+ Non esiste un'opzione per escludere file o cartelle specifici dal serving e dal directory listing tramite pattern (glob o RegExp). Se un file come `config.json`, `secrets.yaml` o qualsiasi altro file sensibile si trova nella webroot, viene servito senza restrizioni.
85
+
86
+ Esempi di pattern utili:
87
+ - `*.env` — tutti i file .env
88
+ - `**/*.secret` — file con estensione .secret
89
+ - `private/**` — intera cartella private
90
+
91
+ ### Soluzioni proposte
92
+
93
+ *Da definire nella sessione di lavoro dedicata a questa fase.*
94
+
95
+ ### Domande
96
+
97
+ *Da porre prima dell'implementazione se necessario.*
98
+
99
+ ### Implementazione
100
+
101
+ *Da eseguire dopo la scelta della soluzione.*
102
+
103
+ ### Test
104
+
105
+ *Da verificare dopo l'implementazione.*
106
+
107
+ ---
108
+
109
+ ## Fase 3 — Content-Security-Policy nell'HTML generato
110
+
111
+ **Stato:** ⬜ Da fare
112
+ **Priorità:** Media
113
+ **File coinvolti:** `index.cjs` (funzione `show_dir`)
114
+
115
+ ### Descrizione del problema
116
+
117
+ Le pagine HTML generate dal directory listing non includono un header `Content-Security-Policy`. Senza CSP, il browser non ha indicazioni su quali risorse sono autorizzate, aumentando il rischio di attacchi XSS in caso di eventuali vulnerabilità future nel codice di generazione HTML.
118
+
119
+ Il progetto già implementa l'escape HTML (funzione `escapeHtml`), ma aggiungere CSP è un ulteriore livello di difesa in profondità.
120
+
121
+ ### Soluzioni proposte
122
+
123
+ *Da definire nella sessione di lavoro dedicata a questa fase.*
124
+
125
+ ### Domande
126
+
127
+ *Da porre prima dell'implementazione se necessario.*
128
+
129
+ ### Implementazione
130
+
131
+ *Da eseguire dopo la scelta della soluzione.*
132
+
133
+ ### Test
134
+
135
+ *Da verificare dopo l'implementazione.*
136
+
137
+ ---
138
+
139
+ ## Fase 4 — Aggiunta ESLint e standardizzazione `===`
140
+
141
+ **Stato:** ⬜ Da fare
142
+ **Priorità:** Media
143
+ **File coinvolti:** `index.cjs`, `package.json`, nuovi file di config
144
+
145
+ ### Descrizione del problema
146
+
147
+ Il codice usa `==` (uguaglianza debole con coercizione di tipo) in circa 25 punti, documentati in `docs/CODE_REVIEW.md`. Anche se nel contesto attuale non causano bug, l'uso di `===` è la best practice JavaScript e previene errori sottili in futuro.
148
+
149
+ Inoltre il progetto non ha alcun linter o formatter configurato, il che rende difficile mantenere la coerenza stilistica nel tempo e nelle contribuzioni esterne.
150
+
151
+ ### Soluzioni proposte
152
+
153
+ *Da definire nella sessione di lavoro dedicata a questa fase.*
154
+
155
+ ### Domande
156
+
157
+ *Da porre prima dell'implementazione se necessario.*
158
+
159
+ ### Implementazione
160
+
161
+ *Da eseguire dopo la scelta della soluzione.*
162
+
163
+ ### Test
164
+
165
+ *Da verificare dopo l'implementazione.*
166
+
167
+ ---
168
+
169
+ ## Fase 5 — Range Requests HTTP 206
170
+
171
+ **Stato:** ⬜ Da fare
172
+ **Priorità:** Media
173
+ **File coinvolti:** `index.cjs` (logica di file serving)
174
+
175
+ ### Descrizione del problema
176
+
177
+ Il middleware non supporta l'header `Range` delle richieste HTTP. Questo significa che:
178
+
179
+ - File audio e video non possono essere riprodotti in streaming dai browser
180
+ - Download di file grandi non possono essere ripresi dopo interruzione
181
+ - Strumenti come `curl --range` non funzionano
182
+
183
+ Il supporto HTTP 206 (Partial Content) è lo standard per la distribuzione di contenuti multimediali e file di grandi dimensioni.
184
+
185
+ ### Soluzioni proposte
186
+
187
+ *Da definire nella sessione di lavoro dedicata a questa fase.*
188
+
189
+ ### Domande
190
+
191
+ *Da porre prima dell'implementazione se necessario.*
192
+
193
+ ### Implementazione
194
+
195
+ *Da eseguire dopo la scelta della soluzione.*
196
+
197
+ ### Test
198
+
199
+ *Da verificare dopo l'implementazione.*
200
+
201
+ ---
202
+
203
+ ## Fase 6 — Gzip / Brotli compression
204
+
205
+ **Stato:** ⬜ Da fare
206
+ **Priorità:** Media
207
+ **File coinvolti:** `index.cjs`, `package.json`
208
+
209
+ ### Descrizione del problema
210
+
211
+ Il middleware serve tutti i file senza compressione. Per file di testo (HTML, CSS, JS, JSON, SVG), la compressione può ridurre le dimensioni del 60-80%, con impatto diretto su:
212
+
213
+ - Velocità di caricamento per gli utenti
214
+ - Consumo di banda del server
215
+ - Score Lighthouse / Web Vitals
216
+
217
+ ### Soluzioni proposte
218
+
219
+ *Da definire nella sessione di lavoro dedicata a questa fase.*
220
+
221
+ ### Domande
222
+
223
+ *Da porre prima dell'implementazione se necessario.*
224
+
225
+ ### Implementazione
226
+
227
+ *Da eseguire dopo la scelta della soluzione.*
228
+
229
+ ### Test
230
+
231
+ *Da verificare dopo l'implementazione.*
232
+
233
+ ---
234
+
235
+ ## Fase 7 — Traduzione commenti in inglese nei test
236
+
237
+ **Stato:** ⬜ Da fare
238
+ **Priorità:** Bassa
239
+ **File coinvolti:** `__tests__/dt-unknown.test.js` e altri
240
+
241
+ ### Descrizione del problema
242
+
243
+ Alcuni file di test contengono commenti in italiano, il che riduce l'accessibilità del codice per i contribuenti internazionali. Per un progetto open source pubblicato su npm, la lingua standard della codebase dovrebbe essere l'inglese.
244
+
245
+ ### Soluzioni proposte
246
+
247
+ *Da definire nella sessione di lavoro dedicata a questa fase.*
248
+
249
+ ### Domande
250
+
251
+ *Da porre prima dell'implementazione se necessario.*
252
+
253
+ ### Implementazione
254
+
255
+ *Da eseguire dopo la scelta della soluzione.*
256
+
257
+ ### Test
258
+
259
+ *Da verificare dopo l'implementazione.*
260
+
261
+ ---
262
+
263
+ ## Fase 8 — Roadmap rimozione opzioni deprecate (v3.0.0)
264
+
265
+ **Stato:** ⬜ Da fare
266
+ **Priorità:** Bassa
267
+ **File coinvolti:** `index.cjs`, `docs/CHANGELOG.md`, `README.md`
268
+
269
+ ### Descrizione del problema
270
+
271
+ Le opzioni legacy `cacheMaxAge` ed `enableCaching` sono ancora supportate con deprecation warning ma non hanno una data di rimozione pianificata. Mantenerle a tempo indeterminato appesantisce il codice e può confondere i nuovi utenti che leggono la documentazione.
272
+
273
+ Una roadmap chiara verso `v3.0.0` con breaking changes definiti aiuta gli utenti a pianificare la migrazione.
274
+
275
+ ### Soluzioni proposte
276
+
277
+ *Da definire nella sessione di lavoro dedicata a questa fase.*
278
+
279
+ ### Domande
280
+
281
+ *Da porre prima dell'implementazione se necessario.*
282
+
283
+ ### Implementazione
284
+
285
+ *Da eseguire dopo la scelta della soluzione.*
286
+
287
+ ### Test
288
+
289
+ *Da verificare dopo l'implementazione.*
290
+
291
+ ---
292
+
293
+ *Documento aggiornato: 2026-03-21*
package/docs/CHANGELOG.md CHANGED
@@ -5,6 +5,295 @@ All notable changes to koa-classic-server will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.0.0] - 2026-05-13
9
+
10
+ ### 🆕 New Features
11
+
12
+ #### `hidden` option — protect dot-files, dot-dirs and custom patterns (Fase 1 + Fase 2)
13
+
14
+ A new `hidden` option controls which files and directories are blocked from both directory listing and direct HTTP access (returning **404**). Applies recursively to the entire directory tree.
15
+
16
+ **Default behavior (secure out of the box):**
17
+ - Dot-files (names starting with `.`) → **hidden by default** (`dotFiles.default: 'hidden'`)
18
+ - Dot-directories → **visible by default** (`dotDirs.default: 'visible'`)
19
+
20
+ ```js
21
+ app.use(koaClassicServer('/public', {
22
+ hidden: {
23
+ dotFiles: {
24
+ default: 'hidden', // 'hidden' | 'visible'
25
+ whitelist: ['.well-known'], // Always visible (string exact/glob or RegExp)
26
+ blacklist: [], // Always hidden — overrides whitelist
27
+ },
28
+ dotDirs: {
29
+ default: 'visible',
30
+ whitelist: [],
31
+ blacklist: ['.git', /^\.svn/],
32
+ },
33
+ alwaysHide: ['*.secret', 'config/secrets/**', /\.key$/], // Path-aware patterns
34
+ }
35
+ }));
36
+ ```
37
+
38
+ **Priority (highest to lowest):**
39
+ 1. `blacklist` — always hidden, beats everything
40
+ 2. `whitelist` — always visible, overrides `alwaysHide` and `default`
41
+ 3. `alwaysHide` — path-aware patterns, beats `default`
42
+ 4. `default` — fallback behavior for unmatched dot-entries
43
+
44
+ **`alwaysHide` pattern rules:**
45
+ - String without `/`: matches basename at any depth (e.g. `*.secret` hides `a/b/file.secret`)
46
+ - String with `/`: path-anchored from root (e.g. `config/secrets/**`)
47
+ - RegExp: tested against the full relative path
48
+
49
+ **Blocked dot-dirs block sub-paths too:**
50
+ `GET /.git/config` returns 404 if `.git` is in `dotDirs.blacklist`.
51
+
52
+ #### `template.renderTimeout` — bounded template execution (Security M-1)
53
+
54
+ The template `render` callback now runs under a configurable timeout (default **30 000 ms**, `0` = disabled). When a render exceeds the timeout the middleware responds **`504 Gateway Timeout`** with the usual security headers, instead of leaving the client connection blocked on a slow/hung render. Protects against DoS via connection exhaustion when a render performs unbounded I/O (DB queries, remote fetches, etc.).
55
+
56
+ The `render` function now receives an **`AbortSignal` as 5th argument**. The signal aborts on timeout *and* when the client disconnects (even when `renderTimeout: 0`). Cooperative renders that propagate the signal to `fetch` / DB clients / `fs.promises.*` also free backend resources on timeout.
57
+
58
+ ```js
59
+ app.use(koaClassicServer('/public', {
60
+ template: {
61
+ ext: ['ejs'],
62
+ renderTimeout: 5000, // ms; 0 disables
63
+ render: async (ctx, next, filePath, rawBuffer, signal) => {
64
+ const data = await db.query('SELECT ...', { signal }); // honour signal
65
+ const ext = await fetch('https://api/...', { signal });
66
+ signal.throwIfAborted();
67
+ ctx.type = 'text/html';
68
+ ctx.body = ejs.render(rawBuffer.toString(), { data, ext });
69
+ }
70
+ }
71
+ }));
72
+ ```
73
+
74
+ **Backward compatible:** existing 4-argument render functions keep working — the 5th argument is simply ignored.
75
+
76
+ #### `serverCache.*.maxAge` — time-based cache invalidation (Security M-2)
77
+
78
+ Both server-side caches (`serverCache.rawFile` and `serverCache.compressedFile`) accept a new `maxAge` option (ms, default `0` = disabled). When `> 0`, an entry is considered stale after `maxAge` ms regardless of `mtime + size`, forcing a fresh disk read on the next request.
79
+
80
+ Designed for **NFS / SMB / Docker bind mounts** where the OS attribute cache can keep `stat()` returning a stale `mtime` for several seconds after a remote modification — making the mtime+size invariant insufficient to detect changes. `maxAge` bounds the worst-case staleness window to a known value.
81
+
82
+ ```js
83
+ app.use(koaClassicServer('/public', {
84
+ serverCache: {
85
+ rawFile: { enabled: true, maxAge: 30000 }, // refresh every 30 s
86
+ compressedFile: { enabled: true, maxAge: 30000 }
87
+ }
88
+ }));
89
+ ```
90
+
91
+ > **Limitation:** `maxAge` limits but does not eliminate NFS staleness. For strict freshness combine with a low `actimeo=` on the mount.
92
+
93
+ Internally a new `LFUCache.refresh(key, fields)` method updates the entry in place while preserving its LFU frequency, so popular files refreshed by `maxAge` don't fall to the bottom of the eviction bucket.
94
+
95
+ #### `logger` option — pluggable structured logging (Security N-1)
96
+
97
+ All internal `console.error` / `console.warn` calls now route through an injectable logger. Pass any object that exposes `error(...)` and `warn(...)` methods — `console` (default), `pino`, `winston`, `bunyan`, or a custom adapter — to integrate with aggregated logging pipelines in production.
98
+
99
+ ```js
100
+ const pino = require('pino')();
101
+
102
+ app.use(koaClassicServer('/public', {
103
+ logger: pino
104
+ }));
105
+ ```
106
+
107
+ **Contract:**
108
+ - Must be an object with `typeof logger.error === 'function'` and `typeof logger.warn === 'function'`
109
+ - Invalid loggers (missing methods, non-objects, `null`, `false`, arrays) throw at factory time
110
+ - Extra methods (`info`, `debug`, `fatal`, ...) are ignored — pass any superset freely
111
+
112
+ **ANSI escape codes** in warning messages are only emitted when the logger is the global `console` (detected by reference). Structured loggers receive the plain text, keeping log aggregators clean.
113
+
114
+ **Backward compatible:** when `logger` is omitted, the default is `console` — existing code and tests that spy on `console.error` / `console.warn` continue to work unchanged.
115
+
116
+ #### `dirListing` namespace — bounded and paginated directory listings (Security N-2)
117
+
118
+ A new namespaced option groups all directory-listing config together, replacing the v2-era flat `showDirContents` knob with a structured object. Hardens the listing against indirect DoS via very large directories and improves usability on big folders.
119
+
120
+ ```js
121
+ app.use(koaClassicServer('/public', {
122
+ dirListing: {
123
+ enabled: true, // render listing HTML when no index file matches (default: true)
124
+ maxEntries: 10000, // hard cap on visible / sorted / stat'd entries (default; 0 = disabled)
125
+ entriesPerPage: 100, // entries per page in the listing UI (default; 0 = disabled)
126
+ }
127
+ }));
128
+ ```
129
+
130
+ **dirListing.enabled**
131
+ - Replaces the v2 top-level `showDirContents`. Accepts `true` / `false`. When `false`, requests for a directory without a matching index file return 404 instead of an HTML listing.
132
+
133
+ **dirListing.maxEntries**
134
+ - Caps how many entries are sorted, stat'd, and rendered per directory listing. Excess entries trigger a yellow banner at the top of the page and an `X-Dir-Truncated: <N>` response header so monitoring can distinguish capped listings.
135
+ - Implementation: the middleware calls `fs.promises.readdir()` once and then slices the result. This bounds rendering and CPU cost but **not** the size of the initial `readdir()` allocation. For typical static-file servers (where the directory contents are controlled by the operator) this is the right trade-off — it recovers v2-class listing performance.
136
+ - Default `10000` is permissive enough for normal use while bounding rendering cost on accidentally-large folders.
137
+ - **Caveat for adversarial workloads:** if you serve a directory writable by untrusted parties, an attacker creating millions of files could still force a large `readdir()` allocation. Tracked for v3.1 as opt-in streaming reads — see `docs/security_improvement_for_V3.md` → *Future Work* → *[F-1]*.
138
+
139
+ **dirListing.entriesPerPage**
140
+ - Pagination kicks in only when the visible entries exceed `entriesPerPage`; small directories render in a single page exactly like before.
141
+ - The current page is selected by `?page=N` (0-based). Invalid or out-of-range values clamp silently to the nearest valid page.
142
+ - A numbered paginator (`« First | ‹ Prev | 0 1 … N-1 | Next › | Last »`) is rendered below the table, preserving any active `sort`/`order`. An `X-Dir-Pagination: <current>/<last>` header is also emitted.
143
+
144
+ **Migration from v2**
145
+
146
+ `showDirContents` (a v2-stable option) keeps working as a **backward-compatibility alias** for `dirListing.enabled`. v2 code that passes it continues to function unchanged. A one-time deprecation warning is emitted via the configured `logger.warn(...)` to encourage migration:
147
+
148
+ ```
149
+ [koa-classic-server] DEPRECATION: options.showDirContents was renamed to dirListing.enabled in v3.0.0.
150
+ The old name is currently accepted as an alias and may be removed in a future major version.
151
+ Replace with: dirListing: { enabled: true }
152
+ ```
153
+
154
+ Passing both `showDirContents` and `dirListing.enabled` at the same time throws — pick one.
155
+
156
+ **Migration from v3.0.0-alpha.0**
157
+
158
+ The two V3-alpha-only legacy names throw helpful errors at startup (no v2 user can have these in production):
159
+
160
+ ```
161
+ options.maxDirEntries was relocated in v3.0.0.
162
+ Replace with: dirListing: { maxEntries: 10000 }
163
+
164
+ options.pageSize was relocated and renamed in v3.0.0.
165
+ Replace with: dirListing: { entriesPerPage: 100 }
166
+ ```
167
+
168
+ **CSP impact:** the listing CSS now includes rules for `.kcs-banner` and `.kcs-pagination`. The page's CSP hash is auto-recomputed at module load (no manual config change needed).
169
+
170
+ ### 📝 Documentation
171
+
172
+ #### DNS Rebinding deployment guidance (Security M-3)
173
+
174
+ The `Host` header is intentionally not validated by the middleware — host validation belongs to the reverse proxy or to a dedicated application-level guard. The new *Best Practices → Sicurezza → DNS Rebinding* section in `docs/DOCUMENTATION.md` explains:
175
+
176
+ - When the risk applies (LAN/loopback exposure without a fronting proxy).
177
+ - When it doesn't (reverse proxy with `server_name` allowlist, public IP behind CDN/WAF).
178
+ - A drop-in nginx allowlist snippet.
179
+ - A Koa middleware that checks `ctx.host` against an allowlist and returns `421 Misdirected Request`, plus a note on `app.proxy = true` + `X-Forwarded-Host` when terminating TLS upstream.
180
+
181
+ No code change in `index.cjs` — documentation only.
182
+
183
+ #### Security headers scope and limits (Security M-4)
184
+
185
+ Clarify that the security headers emitted by the middleware (`Content-Security-Policy`, `X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`, `Permissions-Policy`) are applied **only** to middleware-generated responses (directory listing + error pages). User-served static files are returned without these headers — by design, because the right policy is application-specific.
186
+
187
+ The new *Best Practices → Sicurezza → Limiti dei Security Headers sui file statici* section in `docs/DOCUMENTATION.md` covers:
188
+
189
+ - A table listing which headers are emitted automatically and on which responses.
190
+ - An upstream Koa middleware example that adds `X-Content-Type-Options`, `Referrer-Policy`, `Strict-Transport-Security` to every response and a strict CSP only to HTML responses.
191
+ - Notes on rolling out CSP via `Content-Security-Policy-Report-Only`, and on `COOP`/`COEP` for projects using `SharedArrayBuffer`.
192
+
193
+ No code change in `index.cjs` — documentation only.
194
+
195
+ ### 🎯 Design Philosophy
196
+
197
+ v3.0.0 codifies the project's design intent in a new top-level `CLAUDE.md`: **koa-classic-server is an HTTP file server first**. The contract with the operator is: *"if a file is in `rootDir`, `GET` on its path returns it"*. Defaults serve files without applying surprise restrictions — the operator's directory is the source of truth.
198
+
199
+ This drove a revision of two v3-alpha defaults late in the cycle (see *Breaking Changes* below) and shapes how new features will be designed going forward. Operators harden via explicit configuration; the README and `docs/DOCUMENTATION.md` now ship a **Security Checklist** and a **Suggested Production Security Configuration** to help with that.
200
+
201
+ ### ⚠️ Breaking Changes
202
+
203
+ #### Dot-files visible by default (philosophy alignment)
204
+
205
+ Earlier in the v3.0.0 alpha cycle, `hidden.dotFiles.default` was flipped to `'hidden'` as a security-by-default choice. This created surprise behavior — `GET /.env` returning 404 even when the file exists — which violates the "file server first" design philosophy.
206
+
207
+ **Final v3.0.0 behavior:** `hidden.dotFiles.default` is `'visible'`, restoring v2 behavior. The implicit-default warning that fired in alpha when the option was omitted is also removed.
208
+
209
+ | Default | v2 | v3.0.0-alpha early | **v3.0.0 final** |
210
+ |---|---|---|---|
211
+ | `hidden.dotFiles.default` | `'visible'` | `'hidden'` | **`'visible'`** |
212
+ | Implicit-default runtime warning | — | emitted | **removed** |
213
+
214
+ **Operators upgrading from v2:** no change in behavior — your existing dot-files keep being served. **Migration to harden** (recommended for production): set `hidden.dotFiles.default: 'hidden'` explicitly and whitelist `.well-known` for ACME. See the *Security Checklist* in `README.md`.
215
+
216
+ #### `dirListing.maxEntries` default raised from 10,000 → 100,000
217
+
218
+ The earlier v3-alpha default of `10,000` was tight enough that operators with normal-sized media catalogs, releases archives, or asset directories would hit truncation silently (the listing banner would appear). This violated the "no surprise restrictions" rule — the cap was acting as a policy restriction rather than a safety net.
219
+
220
+ **Final v3.0.0 behavior:** `dirListing.maxEntries` defaults to `100,000` — high enough that 99% of legitimate deployments never hit it, low enough to bound rendering cost on accidentally-huge directories (log rotation broken, mistakenly mounted FS).
221
+
222
+ | Default | v3.0.0-alpha early | **v3.0.0 final** |
223
+ |---|---|---|
224
+ | `dirListing.maxEntries` | `10000` | **`100000`** |
225
+ | `dirListing.entriesPerPage` | `100` | `100` (unchanged) |
226
+
227
+ **Caveat:** even with `maxEntries: 100000`, the initial `fs.promises.readdir()` allocation is not bounded. For adversarial-directory workloads (multi-tenant uploads, untrusted writes), this gap will be closed by the v3.1 `dirListing.readMode: 'bounded'` option — tracked under `[F-1]` in `docs/security_improvement_for_V3.md`.
228
+
229
+ #### Dot-files hardening is now opt-in (was implicit "secure by default")
230
+
231
+ Operators who *want* the v3-alpha behavior (dot-files hidden by default, including `.env`, `.git/config`, etc.) must now opt in explicitly:
232
+
233
+ ```javascript
234
+ app.use(koaClassicServer('/public', {
235
+ hidden: {
236
+ dotFiles: { default: 'hidden', whitelist: ['.well-known'] },
237
+ dotDirs: { default: 'hidden', whitelist: ['.well-known'] },
238
+ },
239
+ }));
240
+ ```
241
+
242
+ This snippet now appears in the *Suggested Production Security Configuration* in both `README.md` and `docs/DOCUMENTATION.md`.
243
+
244
+ #### Removed string format for `index` option
245
+ - **Removed**: `index: 'index.html'` — passing a non-empty string now throws an `Error` at startup
246
+ - **Empty string** `index: ''` is still silently treated as `[]` (no index file, show directory listing)
247
+ - **Migration**:
248
+ ```js
249
+ // Before (v2.x — now throws)
250
+ app.use(koaClassicServer('/public', { index: 'index.html' }));
251
+
252
+ // After (v3.0.0)
253
+ app.use(koaClassicServer('/public', { index: ['index.html'] }));
254
+ ```
255
+
256
+ #### Removed deprecated option names `cacheMaxAge` and `enableCaching`
257
+ - **Removed**: `cacheMaxAge` — use `browserCacheMaxAge` instead
258
+ - **Removed**: `enableCaching` — use `browserCacheEnabled` instead
259
+ - **Behaviour**: Passing either removed option now throws an `Error` at startup with a clear migration message pointing to the new name and the current value.
260
+ - **Migration**:
261
+ ```js
262
+ // Before (v2.x — now throws)
263
+ app.use(koaClassicServer('/public', {
264
+ enableCaching: true,
265
+ cacheMaxAge: 3600
266
+ }));
267
+
268
+ // After (v3.0.0)
269
+ app.use(koaClassicServer('/public', {
270
+ browserCacheEnabled: true,
271
+ browserCacheMaxAge: 3600
272
+ }));
273
+ ```
274
+
275
+ #### Renamed `compression.minSize` → `compression.minFileSize`
276
+
277
+ The threshold below which files are served uncompressed has a clearer name. Brings naming into line with `serverCache.rawFile.maxFileSize`, where "file size" is the explicit unit. Affects only alpha-tester code (the `compression` namespace was introduced in v3 and is not present in v2).
278
+
279
+ - **Removed**: `compression.minSize` — passing it now throws an `Error` at startup
280
+ - **Migration**:
281
+ ```js
282
+ // Before (v3.0.0-alpha.0 — now throws)
283
+ app.use(koaClassicServer('/public', {
284
+ compression: { minSize: 2048 }
285
+ }));
286
+
287
+ // After (v3.0.0)
288
+ app.use(koaClassicServer('/public', {
289
+ compression: { minFileSize: 2048 }
290
+ }));
291
+ ```
292
+
293
+ The `false` shorthand (disable the threshold entirely) is preserved on the new name: `compression: { minFileSize: false }`.
294
+
295
+ ---
296
+
8
297
  ## [2.6.1] - 2026-03-04
9
298
 
10
299
  ### 🐛 Bug Fix
@@ -1,5 +1,7 @@
1
1
  # Code Review - koa-classic-server
2
2
 
3
+ > **Nota storica:** questo documento è uno snapshot di code review precedente al refactor V3. Riferimenti a `showDirContents` corrispondono a `dirListing.enabled` nell'API V3 corrente. Vedi [README.md → Migration Guide](../README.md#from-v2x-to-v3x).
4
+
3
5
  ## Analisi Generale del Codice
4
6
 
5
7
  Data: 2025-11-18