koa-classic-server 3.0.1 โ†’ 3.1.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 (106) hide show
  1. package/README.md +44 -120
  2. package/index.cjs +200 -21
  3. package/package.json +10 -5
  4. package/.github/workflows/npm-publish.yml +0 -98
  5. package/CLAUDE.md +0 -101
  6. package/__tests__/benchmark-results-baseline-v1.2.0.txt +0 -354
  7. package/__tests__/benchmark-results-optimized-v2.0.0.txt +0 -354
  8. package/__tests__/benchmark-results-v3.0.0.txt +0 -372
  9. package/__tests__/benchmark.js +0 -239
  10. package/__tests__/caching-headers.test.js +0 -556
  11. package/__tests__/compression-fixtures/data.json +0 -1
  12. package/__tests__/compression-fixtures/large.txt +0 -1
  13. package/__tests__/compression-fixtures/small.txt +0 -1
  14. package/__tests__/compression.test.js +0 -284
  15. package/__tests__/customTest/README.md +0 -6
  16. package/__tests__/customTest/loadConfig.util.js +0 -41
  17. package/__tests__/customTest/serversToLoad.util.js +0 -93
  18. package/__tests__/demo-regex-index.js +0 -140
  19. package/__tests__/deprecation-warnings.test.js +0 -105
  20. package/__tests__/directory-sorting-links.test.js +0 -135
  21. package/__tests__/dt-unknown.test.js +0 -635
  22. package/__tests__/ejs.test.js +0 -299
  23. package/__tests__/head-method.test.js +0 -160
  24. package/__tests__/hidden-fixtures/.dot-dir/inside.txt +0 -1
  25. package/__tests__/hidden-fixtures/.env +0 -2
  26. package/__tests__/hidden-fixtures/.well-known/acme-challenge.txt +0 -1
  27. package/__tests__/hidden-fixtures/config/secrets/password.txt +0 -1
  28. package/__tests__/hidden-fixtures/data.key +0 -1
  29. package/__tests__/hidden-fixtures/file.secret +0 -1
  30. package/__tests__/hidden-fixtures/index.html +0 -1
  31. package/__tests__/hidden-fixtures/normal.txt +0 -1
  32. package/__tests__/hidden-fixtures/subdir/.env +0 -1
  33. package/__tests__/hidden-fixtures/subdir/regular.txt +0 -1
  34. package/__tests__/hidden-option.test.js +0 -407
  35. package/__tests__/hideExtension.test.js +0 -607
  36. package/__tests__/index-option.test.js +0 -449
  37. package/__tests__/index.test.js +0 -345
  38. package/__tests__/listing.test.js +0 -437
  39. package/__tests__/logger.test.js +0 -232
  40. package/__tests__/performance.test.js +0 -370
  41. package/__tests__/publicWwwTest/cartella/sottocartella/ciao.html +0 -11
  42. package/__tests__/publicWwwTest/cartella/sottocartella/provaEjs/testEjs.ejs +0 -11
  43. package/__tests__/publicWwwTest/cartella vuota con spazi nel nome/file con spazio nel nome .txt +0 -1
  44. package/__tests__/publicWwwTest/cartella vuota con spazi nel nome /file con spazio nel nome .txt +0 -1
  45. package/__tests__/publicWwwTest/ejs-templates/complex.ejs +0 -56
  46. package/__tests__/publicWwwTest/ejs-templates/index.ejs +0 -30
  47. package/__tests__/publicWwwTest/ejs-templates/simple.ejs +0 -13
  48. package/__tests__/publicWwwTest/ejs-templates/with-conditional.ejs +0 -28
  49. package/__tests__/publicWwwTest/ejs-templates/with-escaping.ejs +0 -26
  50. package/__tests__/publicWwwTest/ejs-templates/with-loop.ejs +0 -16
  51. package/__tests__/publicWwwTest/hideext-test/about/index.html +0 -1
  52. package/__tests__/publicWwwTest/hideext-test/about.EJS +0 -1
  53. package/__tests__/publicWwwTest/hideext-test/about.ejs +0 -2
  54. package/__tests__/publicWwwTest/hideext-test/blog/articolo.ejs +0 -2
  55. package/__tests__/publicWwwTest/hideext-test/conflict-test/pagina +0 -1
  56. package/__tests__/publicWwwTest/hideext-test/conflict-test/pagina.ejs +0 -1
  57. package/__tests__/publicWwwTest/hideext-test/file.ejs.bak +0 -1
  58. package/__tests__/publicWwwTest/hideext-test/index.ejs +0 -2
  59. package/__tests__/publicWwwTest/hideext-test/photo.txt +0 -1
  60. package/__tests__/publicWwwTest/hideext-test/sezione/index.ejs +0 -1
  61. package/__tests__/publicWwwTest/hideext-test/style.css +0 -1
  62. package/__tests__/publicWwwTest/ile_vuoto.txt +0 -0
  63. package/__tests__/publicWwwTest/index.html +0 -11
  64. package/__tests__/publicWwwTest/prova file .txt +0 -2
  65. package/__tests__/publicWwwTest/semplicetxt.txt +0 -1
  66. package/__tests__/publicWwwTest/test-page.html +0 -1
  67. package/__tests__/publicWwwTest/test.txt +0 -1
  68. package/__tests__/range-fixtures/sample.txt +0 -1
  69. package/__tests__/range.test.js +0 -223
  70. package/__tests__/security-headers.test.js +0 -165
  71. package/__tests__/security.test.js +0 -322
  72. package/__tests__/server-cache-fixtures/large.txt +0 -1
  73. package/__tests__/server-cache-fixtures/small.txt +0 -1
  74. package/__tests__/server-cache.test.js +0 -594
  75. package/__tests__/setup-benchmark.js +0 -178
  76. package/__tests__/symlink.test.js +0 -441
  77. package/__tests__/template-timeout.test.js +0 -321
  78. package/__tests__/test-regex-quick.js +0 -158
  79. package/__tests__/useOriginalUrl.test.js +0 -213
  80. package/docs/ACTION_PLAN.md +0 -293
  81. package/docs/BENCHMARKS.md +0 -317
  82. package/docs/CHANGELOG.md +0 -906
  83. package/docs/CODE_REVIEW.md +0 -300
  84. package/docs/DEBUG_REPORT.md +0 -593
  85. package/docs/DOCUMENTATION.md +0 -1864
  86. package/docs/EXAMPLES_INDEX_OPTION.md +0 -395
  87. package/docs/FLOW_DIAGRAM.md +0 -954
  88. package/docs/INDEX_OPTION_PRIORITY.md +0 -527
  89. package/docs/OPTIMIZATION_HTTP_CACHING.md +0 -689
  90. package/docs/OPTIMIZATION_ROADMAP_for_V3.md +0 -864
  91. package/docs/PERFORMANCE_ANALYSIS.md +0 -839
  92. package/docs/PERFORMANCE_COMPARISON.md +0 -388
  93. package/docs/noteExports.md +0 -148
  94. package/docs/security_improvement_for_V3.md +0 -421
  95. package/docs/template-engine/TEMPLATE_ENGINE_GUIDE.md +0 -1734
  96. package/docs/template-engine/esempi-incrementali.js +0 -192
  97. package/docs/template-engine/examples/esempio1-nessun-dato.ejs +0 -12
  98. package/docs/template-engine/examples/esempio2-una-variabile.ejs +0 -11
  99. package/docs/template-engine/examples/esempio3-piu-variabili.ejs +0 -15
  100. package/docs/template-engine/examples/esempio4-condizionale.ejs +0 -18
  101. package/docs/template-engine/examples/esempio5-loop.ejs +0 -18
  102. package/docs/template-engine/examples/index-esempi.html +0 -181
  103. package/docs/template-engine/examples/index.html +0 -40
  104. package/docs/template-engine/examples/test.ejs +0 -64
  105. package/eslint.config.mjs +0 -17
  106. package/jest.config.js +0 -18
package/docs/CHANGELOG.md DELETED
@@ -1,906 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to koa-classic-server will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [3.0.1] - 2026-06-10
9
-
10
- ### ๐Ÿ› Bug Fix
11
-
12
- #### Fixed `HEAD` on template-engine routes returning 404 (HTTP conformance, RFC 9110 ยง9.3.2)
13
- - **Issue**: When `HEAD` was enabled (`method: ['GET', 'HEAD']`), a `GET` on a route served by the template engine (e.g. `index.ejs` as the index of `/`) returned **200**, but a `HEAD` on the same route returned **404**. The static-file branch already handled `HEAD` correctly, and directory listings worked incidentally, so only the template branch was affected.
14
- - **Root cause**: `loadFile()` calls `tryRenderTemplate()` before the static-serving branch and returns as soon as it reports the request was handled. `tryRenderTemplate()` invoked the operator's `render` callback with the real `ctx.method` and did nothing `HEAD`-specific. A render that does not itself set a body on non-`GET` requests (a common pattern โ€” operators guard render work behind a `GET` check) therefore left `ctx.status` at Koa's default **404** for `HEAD`, even though `GET` rendered normally.
15
- - **Impact**: MEDIUM โ€” breaks caches, reverse proxies, link-checkers, and uptime monitors that issue `HEAD`. `HEAD` must be identical to `GET` minus the body (same status code and headers).
16
- - **Fix**: In `tryRenderTemplate()`, a `HEAD` request now runs the render exactly as a `GET` (the method is presented as `GET` for the duration of the render, then restored) so it resolves, validates, and sets `Content-Type` / status identically. The new `stripBodyForHead()` helper then replaces the rendered body with an empty buffer and restores `Content-Length` to the byte length the `GET` body would have had โ€” sending the correct status and headers with no body. The `GET` path and all public options are unchanged; compatible with Koa 2 and Koa 3.
17
- - **Code**:
18
- - `tryRenderTemplate()` โ€” present `ctx.method` as `GET` for the render, restore to `HEAD` and call `stripBodyForHead()` in `finally`
19
- - `stripBodyForHead()` โ€” new helper: empty body + restored `Content-Length`, preserving the status and headers the render produced
20
-
21
- ### ๐Ÿงช Testing
22
- - Added `__tests__/head-method.test.js` (9 tests) covering, with both a method-aware and a method-agnostic render:
23
- - `HEAD` on a directory whose index is a template โ†’ **200**, status/`Content-Type`/`Content-Length` match `GET`, empty body
24
- - `HEAD` on a directly-requested template file โ†’ **200**, matches `GET`
25
- - `HEAD` on a static file โ†’ **200**, matches `GET` (and still advertises `Accept-Ranges`)
26
- - `HEAD` on a listable directory (no index) โ†’ **200**, matches `GET`
27
- - `HEAD` on a non-existent template/static path โ†’ **404**, matches `GET`
28
- - All 556 tests pass across 21 test suites (zero regressions)
29
-
30
- ### ๐Ÿ“ฆ Package Changes
31
- - **Version**: `3.0.0` โ†’ `3.0.1`
32
- - **Semver**: Patch version bump (bug fix only, no API changes)
33
-
34
- ## [3.0.0] - 2026-05-13
35
-
36
- ### ๐Ÿ†• New Features
37
-
38
- #### `hidden` option โ€” protect dot-files, dot-dirs and custom patterns (Fase 1 + Fase 2)
39
-
40
- 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.
41
-
42
- **Default behavior (secure out of the box):**
43
- - Dot-files (names starting with `.`) โ†’ **hidden by default** (`dotFiles.default: 'hidden'`)
44
- - Dot-directories โ†’ **visible by default** (`dotDirs.default: 'visible'`)
45
-
46
- ```js
47
- app.use(koaClassicServer('/public', {
48
- hidden: {
49
- dotFiles: {
50
- default: 'hidden', // 'hidden' | 'visible'
51
- whitelist: ['.well-known'], // Always visible (string exact/glob or RegExp)
52
- blacklist: [], // Always hidden โ€” overrides whitelist
53
- },
54
- dotDirs: {
55
- default: 'visible',
56
- whitelist: [],
57
- blacklist: ['.git', /^\.svn/],
58
- },
59
- alwaysHide: ['*.secret', 'config/secrets/**', /\.key$/], // Path-aware patterns
60
- }
61
- }));
62
- ```
63
-
64
- **Priority (highest to lowest):**
65
- 1. `blacklist` โ€” always hidden, beats everything
66
- 2. `whitelist` โ€” always visible, overrides `alwaysHide` and `default`
67
- 3. `alwaysHide` โ€” path-aware patterns, beats `default`
68
- 4. `default` โ€” fallback behavior for unmatched dot-entries
69
-
70
- **`alwaysHide` pattern rules:**
71
- - String without `/`: matches basename at any depth (e.g. `*.secret` hides `a/b/file.secret`)
72
- - String with `/`: path-anchored from root (e.g. `config/secrets/**`)
73
- - RegExp: tested against the full relative path
74
-
75
- **Blocked dot-dirs block sub-paths too:**
76
- `GET /.git/config` returns 404 if `.git` is in `dotDirs.blacklist`.
77
-
78
- #### `template.renderTimeout` โ€” bounded template execution (Security M-1)
79
-
80
- 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.).
81
-
82
- 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.
83
-
84
- ```js
85
- app.use(koaClassicServer('/public', {
86
- template: {
87
- ext: ['ejs'],
88
- renderTimeout: 5000, // ms; 0 disables
89
- render: async (ctx, next, filePath, rawBuffer, signal) => {
90
- const data = await db.query('SELECT ...', { signal }); // honour signal
91
- const ext = await fetch('https://api/...', { signal });
92
- signal.throwIfAborted();
93
- ctx.type = 'text/html';
94
- ctx.body = ejs.render(rawBuffer.toString(), { data, ext });
95
- }
96
- }
97
- }));
98
- ```
99
-
100
- **Backward compatible:** existing 4-argument render functions keep working โ€” the 5th argument is simply ignored.
101
-
102
- #### `serverCache.*.maxAge` โ€” time-based cache invalidation (Security M-2)
103
-
104
- 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.
105
-
106
- 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.
107
-
108
- ```js
109
- app.use(koaClassicServer('/public', {
110
- serverCache: {
111
- rawFile: { enabled: true, maxAge: 30000 }, // refresh every 30 s
112
- compressedFile: { enabled: true, maxAge: 30000 }
113
- }
114
- }));
115
- ```
116
-
117
- > **Limitation:** `maxAge` limits but does not eliminate NFS staleness. For strict freshness combine with a low `actimeo=` on the mount.
118
-
119
- 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.
120
-
121
- #### `logger` option โ€” pluggable structured logging (Security N-1)
122
-
123
- 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.
124
-
125
- ```js
126
- const pino = require('pino')();
127
-
128
- app.use(koaClassicServer('/public', {
129
- logger: pino
130
- }));
131
- ```
132
-
133
- **Contract:**
134
- - Must be an object with `typeof logger.error === 'function'` and `typeof logger.warn === 'function'`
135
- - Invalid loggers (missing methods, non-objects, `null`, `false`, arrays) throw at factory time
136
- - Extra methods (`info`, `debug`, `fatal`, ...) are ignored โ€” pass any superset freely
137
-
138
- **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.
139
-
140
- **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.
141
-
142
- #### `dirListing` namespace โ€” bounded and paginated directory listings (Security N-2)
143
-
144
- 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.
145
-
146
- ```js
147
- app.use(koaClassicServer('/public', {
148
- dirListing: {
149
- enabled: true, // render listing HTML when no index file matches (default: true)
150
- maxEntries: 10000, // hard cap on visible / sorted / stat'd entries (default; 0 = disabled)
151
- entriesPerPage: 100, // entries per page in the listing UI (default; 0 = disabled)
152
- }
153
- }));
154
- ```
155
-
156
- **dirListing.enabled**
157
- - 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.
158
-
159
- **dirListing.maxEntries**
160
- - 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.
161
- - 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.
162
- - Default `10000` is permissive enough for normal use while bounding rendering cost on accidentally-large folders.
163
- - **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]*.
164
-
165
- **dirListing.entriesPerPage**
166
- - Pagination kicks in only when the visible entries exceed `entriesPerPage`; small directories render in a single page exactly like before.
167
- - The current page is selected by `?page=N` (0-based). Invalid or out-of-range values clamp silently to the nearest valid page.
168
- - 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.
169
-
170
- **Migration from v2**
171
-
172
- `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:
173
-
174
- ```
175
- [koa-classic-server] DEPRECATION: options.showDirContents was renamed to dirListing.enabled in v3.0.0.
176
- The old name is currently accepted as an alias and may be removed in a future major version.
177
- Replace with: dirListing: { enabled: true }
178
- ```
179
-
180
- Passing both `showDirContents` and `dirListing.enabled` at the same time throws โ€” pick one.
181
-
182
- **Migration from v3.0.0-alpha.0**
183
-
184
- The two V3-alpha-only legacy names throw helpful errors at startup (no v2 user can have these in production):
185
-
186
- ```
187
- options.maxDirEntries was relocated in v3.0.0.
188
- Replace with: dirListing: { maxEntries: 10000 }
189
-
190
- options.pageSize was relocated and renamed in v3.0.0.
191
- Replace with: dirListing: { entriesPerPage: 100 }
192
- ```
193
-
194
- **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).
195
-
196
- ### ๐Ÿ“ Documentation
197
-
198
- #### DNS Rebinding deployment guidance (Security M-3)
199
-
200
- 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:
201
-
202
- - When the risk applies (LAN/loopback exposure without a fronting proxy).
203
- - When it doesn't (reverse proxy with `server_name` allowlist, public IP behind CDN/WAF).
204
- - A drop-in nginx allowlist snippet.
205
- - 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.
206
-
207
- No code change in `index.cjs` โ€” documentation only.
208
-
209
- #### Security headers scope and limits (Security M-4)
210
-
211
- 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.
212
-
213
- The new *Best Practices โ†’ Sicurezza โ†’ Limiti dei Security Headers sui file statici* section in `docs/DOCUMENTATION.md` covers:
214
-
215
- - A table listing which headers are emitted automatically and on which responses.
216
- - 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.
217
- - Notes on rolling out CSP via `Content-Security-Policy-Report-Only`, and on `COOP`/`COEP` for projects using `SharedArrayBuffer`.
218
-
219
- No code change in `index.cjs` โ€” documentation only.
220
-
221
- ### ๐ŸŽฏ Design Philosophy
222
-
223
- 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.
224
-
225
- 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.
226
-
227
- ### โš ๏ธ Breaking Changes
228
-
229
- #### Dot-files visible by default (philosophy alignment)
230
-
231
- 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.
232
-
233
- **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.
234
-
235
- | Default | v2 | v3.0.0-alpha early | **v3.0.0 final** |
236
- |---|---|---|---|
237
- | `hidden.dotFiles.default` | `'visible'` | `'hidden'` | **`'visible'`** |
238
- | Implicit-default runtime warning | โ€” | emitted | **removed** |
239
-
240
- **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`.
241
-
242
- #### `dirListing.maxEntries` default raised from 10,000 โ†’ 100,000
243
-
244
- 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.
245
-
246
- **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).
247
-
248
- | Default | v3.0.0-alpha early | **v3.0.0 final** |
249
- |---|---|---|
250
- | `dirListing.maxEntries` | `10000` | **`100000`** |
251
- | `dirListing.entriesPerPage` | `100` | `100` (unchanged) |
252
-
253
- **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`.
254
-
255
- #### Dot-files hardening is now opt-in (was implicit "secure by default")
256
-
257
- Operators who *want* the v3-alpha behavior (dot-files hidden by default, including `.env`, `.git/config`, etc.) must now opt in explicitly:
258
-
259
- ```javascript
260
- app.use(koaClassicServer('/public', {
261
- hidden: {
262
- dotFiles: { default: 'hidden', whitelist: ['.well-known'] },
263
- dotDirs: { default: 'hidden', whitelist: ['.well-known'] },
264
- },
265
- }));
266
- ```
267
-
268
- This snippet now appears in the *Suggested Production Security Configuration* in both `README.md` and `docs/DOCUMENTATION.md`.
269
-
270
- #### Removed string format for `index` option
271
- - **Removed**: `index: 'index.html'` โ€” passing a non-empty string now throws an `Error` at startup
272
- - **Empty string** `index: ''` is still silently treated as `[]` (no index file, show directory listing)
273
- - **Migration**:
274
- ```js
275
- // Before (v2.x โ€” now throws)
276
- app.use(koaClassicServer('/public', { index: 'index.html' }));
277
-
278
- // After (v3.0.0)
279
- app.use(koaClassicServer('/public', { index: ['index.html'] }));
280
- ```
281
-
282
- #### Removed deprecated option names `cacheMaxAge` and `enableCaching`
283
- - **Removed**: `cacheMaxAge` โ€” use `browserCacheMaxAge` instead
284
- - **Removed**: `enableCaching` โ€” use `browserCacheEnabled` instead
285
- - **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.
286
- - **Migration**:
287
- ```js
288
- // Before (v2.x โ€” now throws)
289
- app.use(koaClassicServer('/public', {
290
- enableCaching: true,
291
- cacheMaxAge: 3600
292
- }));
293
-
294
- // After (v3.0.0)
295
- app.use(koaClassicServer('/public', {
296
- browserCacheEnabled: true,
297
- browserCacheMaxAge: 3600
298
- }));
299
- ```
300
-
301
- #### Renamed `compression.minSize` โ†’ `compression.minFileSize`
302
-
303
- 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).
304
-
305
- - **Removed**: `compression.minSize` โ€” passing it now throws an `Error` at startup
306
- - **Migration**:
307
- ```js
308
- // Before (v3.0.0-alpha.0 โ€” now throws)
309
- app.use(koaClassicServer('/public', {
310
- compression: { minSize: 2048 }
311
- }));
312
-
313
- // After (v3.0.0)
314
- app.use(koaClassicServer('/public', {
315
- compression: { minFileSize: 2048 }
316
- }));
317
- ```
318
-
319
- The `false` shorthand (disable the threshold entirely) is preserved on the new name: `compression: { minFileSize: false }`.
320
-
321
- ---
322
-
323
- ## [2.6.1] - 2026-03-04
324
-
325
- ### ๐Ÿ› Bug Fix
326
-
327
- #### Fixed DT_UNKNOWN Handling (type 0) on overlayfs, NFS, FUSE, NixOS buildFHSEnv, ecryptfs
328
- - **Issue**: On filesystems where `readdir({ withFileTypes: true })` returns dirents with `DT_UNKNOWN` (type 0), all `dirent.is*()` methods return `false`. This caused three failures:
329
- 1. `isFileOrSymlinkToFile()` missed valid files โ€” `findIndexFile()` returned empty results, so `GET /` showed a directory listing instead of rendering the index file
330
- 2. `isDirOrSymlinkToDir()` missed valid directories โ€” directory type resolution failed
331
- 3. `show_dir()` skipped entries with type 0, logging `"Unknown file type: 0"` โ€” directory listings appeared empty or partial
332
- - **Affected environments**: overlayfs (Docker image layers), NFS (some implementations), FUSE filesystems (sshfs, s3fs, rclone mount), NixOS with buildFHSEnv, ecryptfs (encrypted home directories), and any filesystem that doesn't fill `d_type` in the kernel's `getdents64` syscall
333
- - **Impact**: HIGH โ€” Server unusable on affected filesystems (index file not served, directory listing empty)
334
- - **Fix**: Added `fs.promises.stat()` fallback in all three locations when none of the `dirent.is*()` type methods return `true` (i.e., type is genuinely unknown). On standard filesystems (ext4, btrfs, xfs, APFS, NTFS), `d_type` is always filled correctly, so the `stat()` fallback is never reached โ€” **zero performance overhead** on the fast path.
335
- - **Code**:
336
- - `isFileOrSymlinkToFile()` โ€” DT_UNKNOWN fallback via `stat().isFile()`
337
- - `isDirOrSymlinkToDir()` โ€” DT_UNKNOWN fallback via `stat().isDirectory()`
338
- - `show_dir()` โ€” Accept type 0 entries and resolve via `stat()` instead of skipping them
339
- - **Reference**: Linux `man 2 getdents` โ€” *"Currently, only some filesystems have full support for returning the file type in d_type. All applications must properly handle a return of DT_UNKNOWN."*
340
-
341
- ### ๐Ÿงช Testing
342
- - Added `__tests__/dt-unknown.test.js` with 20 tests covering:
343
- - `isFileOrSymlinkToFile` / `isDirOrSymlinkToDir` with DT_UNKNOWN dirents
344
- - `findIndexFile` with all-unknown-type entries (string and RegExp patterns)
345
- - `show_dir` rendering (resolved types, no skipped entries, correct MIME types and sizes)
346
- - Full integration tests (index file serving, direct file access, complete directory listing)
347
- - Edge cases (mixed regular + DT_UNKNOWN dirents, index priority, Dirent type 0 verification)
348
- - Tests use `jest.spyOn(fs.promises, 'readdir')` to mock DT_UNKNOWN dirents via `new fs.Dirent(name, 0)` while keeping `fs.promises.stat()` working normally
349
- - All 329 tests pass across 12 test suites (zero regressions)
350
-
351
- ### ๐Ÿ“ฆ Package Changes
352
- - **Version**: `2.6.0` โ†’ `2.6.1`
353
- - **Semver**: Patch version bump (bug fix only, no API changes)
354
-
355
- ---
356
-
357
- ## [2.6.0] - 2026-03-01
358
-
359
- ### ๐Ÿ“ฆ Dependency Upgrades
360
-
361
- #### mime-types: ^2.1.35 โ†’ ^3.0.2 (Major)
362
- - **Breaking change upstream**: New `mimeScore` algorithm for extension conflict resolution
363
- - **Impact on this project**: Minimal โ€” the 11 changed MIME mappings affect only uncommon extensions
364
- - **Notable mapping changes**:
365
- - `.wav`: `audio/wave` โ†’ `audio/wav` (equivalent, all browsers accept both)
366
- - `.js`: `application/javascript` โ†’ `text/javascript` (correct per RFC 9239)
367
- - `.rtf`: `text/rtf` โ†’ `application/rtf` (marginal, rare usage)
368
- - `.mp4`: Unchanged in v3.0.2 โ€” still resolves to `video/mp4`
369
- - **Node.js requirement**: mime-types 3 requires Node.js >= 18
370
-
371
- #### ejs: ^3.1.10 โ†’ ^4.0.0 (Major)
372
- - **Breaking changes upstream**: None affecting this project
373
- - EJS 4 removed deprecated `with()` statement support (this project never used it)
374
- - EJS 4 added stricter `exports` map in package.json
375
- - **API fully compatible**: `ejs.render()` and `ejs.renderFile()` work identically
376
- - **Security**: EJS 3.x is EOL โ€” v4 resolves known CVEs in the 3.x line
377
-
378
- ### ๐Ÿ”ง Configuration Changes
379
-
380
- #### Added `engines` field
381
- - Added `"engines": { "node": ">=18" }` to package.json
382
- - Formalizes the Node.js minimum version requirement imposed by mime-types 3
383
-
384
- #### Tightened Koa peerDependency for 2.x
385
- - **koa**: `"^2.0.0 || >=3.1.2"` โ†’ `"^2.16.4 || >=3.1.2"`
386
- - Excludes Koa 2.0.0โ€“2.16.3 which are affected by 4 known CVEs:
387
- - CVE-2025-25200: ReDoS via `X-Forwarded-Proto`/`X-Forwarded-Host` (CVSS 9.2, fixed in 2.15.4)
388
- - CVE-2025-32379: XSS via `ctx.redirect()` (fixed in 2.16.1)
389
- - CVE-2025-62595: Open Redirect via trailing `//` (fixed in 2.16.3)
390
- - CVE-2026-27959: Host Header Injection via `ctx.hostname` (CVSS 7.5, fixed in 2.16.4)
391
-
392
- ### ๐Ÿงช Testing
393
- - All 309 tests pass across 11 test suites (zero regressions)
394
- - No code changes required โ€” both library upgrades are API-compatible
395
-
396
- ### ๐Ÿ“ฆ Package Changes
397
- - **Version**: `2.5.2` โ†’ `2.6.0`
398
- - **Semver**: Minor version bump (dependency upgrades, no API changes)
399
-
400
- ---
401
-
402
- ## [2.5.2] - 2026-03-01
403
-
404
- ### ๐Ÿ”’ Security Fix
405
-
406
- #### Resolved all 11 npm audit vulnerabilities
407
- - **jest**: `^29.7.0` โ†’ `^30.2.0` (major โ€” fixes minimatch ReDoS, brace-expansion ReDoS, @babel/helpers inefficient RegExp)
408
- - **supertest**: `^7.0.0` โ†’ `^7.2.2` (fixes critical form-data unsafe random boundary)
409
- - **inquirer**: `^12.4.1` โ†’ `^13.3.0` (fixes tmp arbitrary file write via symlink, external-editor chain)
410
- - **autocannon**: `^7.15.0` โ†’ `^8.0.0` (major)
411
-
412
- #### Updated peerDependency
413
- - **koa**: `"^2.0.0 || ^3.0.0"` โ†’ `"^2.0.0 || >=3.1.2"`
414
- - Excludes Koa 3.0.0โ€“3.1.1 which had Host Header Injection via `ctx.hostname`
415
-
416
- ### ๐Ÿงช Testing
417
- - All 309 tests pass across 11 test suites (zero regressions)
418
- - `npm audit` reports 0 vulnerabilities
419
-
420
- ### ๐Ÿ“ฆ Package Changes
421
- - **Version**: `2.5.1` โ†’ `2.5.2`
422
- - **Semver**: Patch version bump (security fixes only, no API changes)
423
-
424
- ---
425
-
426
- ## [2.5.1] - 2026-03-01
427
-
428
- ### ๐Ÿ“ Documentation
429
-
430
- - Added dedicated usage example for `useOriginalUrl` (Section 7) with realistic i18n middleware scenario (/it/, /en/, /fr/)
431
- - Added "Advanced hideExtension Scenarios" section (Section 8):
432
- - Recommended file/directory structure (ASCII tree)
433
- - Combined `hideExtension` + i18n middleware example with `useOriginalUrl: false`
434
- - Temporary redirect (302) variant with guidance on 301 vs 302 usage
435
- - Added `hideExtension` and `useOriginalUrl` to the Complete Production Example (Section 11)
436
-
437
- ### ๐Ÿ“ฆ Package Changes
438
- - **Version**: `2.5.0` โ†’ `2.5.1`
439
- - **Semver**: Patch version bump (documentation only, no code changes)
440
-
441
- ---
442
-
443
- ## [2.5.0] - 2026-02-28
444
-
445
- ### โœจ New Feature
446
-
447
- #### hideExtension - Clean URLs (mod_rewrite-like)
448
- - **New Option**: `hideExtension: { ext: '.ejs', redirect: 301 }`
449
- - **Purpose**: Hide file extensions from URLs for SEO-friendly clean URLs
450
- - **Clean URL Resolution**: `/about` โ†’ serves `about.ejs` (when file exists)
451
- - **Extension Redirect**: `/about.ejs` โ†’ 301 redirect to `/about` (preserves query string)
452
- - **Index File Redirect**: `/index.ejs` โ†’ redirect to `/`, `/section/index.ejs` โ†’ redirect to `/section/`
453
- - **Conflict Resolution**: `.ejs` file wins over both directories and extensionless files with same base name
454
- - **Case-Sensitive**: Extension matching is case-sensitive (`.ejs` โ‰  `.EJS`)
455
- - **No Interference**: URLs with other extensions (`.css`, `.png`, etc.) pass through normally
456
- - **Trailing Slash**: `/about/` always means directory, never resolves to file
457
- - **Redirect uses `ctx.originalUrl`**: Preserves URL prefixes from upstream middleware (i18n, routing)
458
-
459
- #### Input Validation
460
- - `hideExtension: true` โ†’ throws Error (must be an object)
461
- - `hideExtension: {}` โ†’ throws Error (missing `ext`)
462
- - `hideExtension: { ext: '' }` โ†’ throws Error (empty ext)
463
- - `hideExtension: { ext: 'ejs' }` โ†’ warning + auto-normalizes to `.ejs`
464
- - `hideExtension: { ext: '.ejs', redirect: 'abc' }` โ†’ throws Error (redirect must be number)
465
-
466
- #### Integration with Existing Options
467
- - **urlsReserved**: Checked before `hideExtension`, no interference
468
- - **urlPrefix**: `hideExtension` works on path after prefix removal
469
- - **useOriginalUrl**: Resolution follows setting; redirect always uses `ctx.originalUrl`
470
- - **template**: Resolved files pass through template engine normally
471
- - **method**: `hideExtension` only applies to allowed HTTP methods
472
-
473
- ### ๐Ÿงช Testing
474
- - Added `__tests__/hideExtension.test.js` with 31 tests covering:
475
- - Clean URL resolution (single and multi-level paths)
476
- - Extension redirect (301/302, query string preservation)
477
- - Directory/file conflict resolution
478
- - Trailing slash behavior
479
- - Extensionless file conflict
480
- - Index file redirect (`/index.ejs` โ†’ `/`)
481
- - `urlsReserved` interaction
482
- - `useOriginalUrl` interaction (redirect preserves prefix)
483
- - Case-sensitive matching
484
- - No interference with other extensions
485
- - Template engine integration
486
- - Input validation (7 validation tests)
487
- - All 278 existing tests still pass (zero regressions)
488
-
489
- ### ๐Ÿ“ฆ Package Changes
490
- - **Version**: `2.4.0` โ†’ `2.5.0`
491
- - **Semver**: Minor version bump (new feature, backward compatible)
492
-
493
- ---
494
-
495
- ## [2.4.0] - 2026-02-28
496
-
497
- ### ๐Ÿ› Bug Fix
498
-
499
- #### Fixed Symlink Support in Index File Discovery and Directory Listing
500
- - **Issue**: On systems where served files are symbolic links (NixOS buildFHSEnv, Docker bind mounts, `npm link`, Capistrano-style deploys), `findIndexFile()` failed because `dirent.isFile()` returns `false` for symlinks. This caused `GET /` to show directory listing instead of rendering the index file, and `GET /index.ejs` to return 404.
501
- - **Impact**: HIGH - Server unusable on NixOS/buildFHSEnv and similar environments
502
- - **Fix**: Added `isFileOrSymlinkToFile()` / `isDirOrSymlinkToDir()` helpers that follow symlinks via `fs.promises.stat()` only when `dirent.isSymbolicLink()` is true, adding zero overhead for regular files.
503
- - **Code**: `index.cjs` - new helpers + `findIndexFile()` + `show_dir()`
504
-
505
- ### โœจ Improvements
506
-
507
- #### Directory Listing Symlink Indicators
508
- - Symlinks to files/directories show `( Symlink )` label next to the name
509
- - Broken/circular symlinks show `( Broken Symlink )` label (name visible but not clickable)
510
- - Symlinks resolved to effective type for MIME and size display (e.g. symlink to dir shows `DIR`)
511
- - Sorting uses effective type (symlink-to-dir sorts with directories)
512
-
513
- ### ๐Ÿงช Testing
514
- - Added `__tests__/symlink.test.js` with 17 tests covering:
515
- - Regular file as index (regression)
516
- - Symlink to file as index (string and RegExp patterns)
517
- - Direct GET to symlinked file
518
- - EJS template via symlink
519
- - Symlink to directory (listing and file access)
520
- - Broken and circular symlinks
521
- - Directory listing indicators (`( Symlink )`, `( Broken Symlink )`)
522
- - Regular file regression (no false symlink indicator)
523
- - All 187 existing tests still pass (zero regressions)
524
-
525
- ### ๐Ÿ“ฆ Package Changes
526
- - **Semver**: Minor version bump (new feature, backward compatible)
527
-
528
- ---
529
-
530
- ## [2.3.0] - 2026-01-03
531
-
532
- ### ๐Ÿ”„ Renamed Options (with Backward Compatibility)
533
-
534
- #### Renamed Caching Options for Clarity
535
- - **Old Names** (DEPRECATED): `enableCaching`, `cacheMaxAge`
536
- - **New Names**: `browserCacheEnabled`, `browserCacheMaxAge`
537
- - **Reason**: Improved clarity - these options specifically control browser-side HTTP caching
538
- - **Backward Compatible**: Old names still work but display deprecation warnings
539
-
540
- #### Deprecation Warnings
541
- When using deprecated option names, a warning is displayed on the terminal:
542
- ```
543
- [koa-classic-server] DEPRECATION WARNING: The "enableCaching" option is deprecated and will be removed in future versions.
544
- Current usage: enableCaching: true
545
- Recommended: browserCacheEnabled: true
546
- Please update your configuration to use the new option name.
547
- ```
548
-
549
- ### ๐Ÿ“ Documentation Updates
550
-
551
- - Updated README.md with new option names
552
- - Updated JSDoc comments in index.cjs
553
- - Added deprecation notes in Options table
554
- - All examples updated to use new names
555
-
556
- ### ๐Ÿ”ง Changes
557
-
558
- - **index.cjs**: Lines 109-135 - Added backward compatibility logic with deprecation warnings
559
- - **index.cjs**: Lines 47-58 - Updated JSDoc comments
560
- - **index.cjs**: Lines 350, 361 - Updated code to use new option names
561
- - **README.md**: Updated all references to use new names, added deprecation notes
562
- - **package.json**: Version bumped from `2.2.0` to `2.3.0`
563
-
564
- ### โš ๏ธ Migration Guide
565
-
566
- **No immediate changes required** - old option names still work.
567
-
568
- **Recommended migration:**
569
-
570
- ```javascript
571
- // Old (still works, but deprecated)
572
- app.use(koaClassicServer('/public', {
573
- enableCaching: true,
574
- cacheMaxAge: 3600
575
- }));
576
-
577
- // New (recommended)
578
- app.use(koaClassicServer('/public', {
579
- browserCacheEnabled: true,
580
- browserCacheMaxAge: 3600
581
- }));
582
- ```
583
-
584
- **Timeline:**
585
- - **v2.3.0**: Old names work with deprecation warnings
586
- - **Future versions**: Old names may be removed (will be announced in advance)
587
-
588
- ### ๐Ÿ“ฆ Package Changes
589
-
590
- - **Version**: `2.2.0` โ†’ `2.3.0`
591
- - **Semver**: Minor version bump (new feature names, backward compatible)
592
-
593
- ---
594
-
595
- ## [2.2.0] - 2026-01-03
596
-
597
- ### โœจ Features
598
-
599
- #### Added useOriginalUrl Option
600
- - **New Option**: `useOriginalUrl` (Boolean, default: `true`)
601
- - **Purpose**: Controls URL resolution for file serving - use `ctx.originalUrl` (immutable) or `ctx.url` (mutable)
602
- - **Use Case**: Compatibility with URL rewriting middleware (i18n, routing)
603
- - **Backward Compatible**: Default value `true` maintains existing behavior
604
-
605
- #### URL Rewriting Middleware Support
606
- - **Problem Solved**: koa-classic-server previously used `ctx.href` (based on `ctx.originalUrl`), which caused 404 errors when middleware rewrites URLs by modifying `ctx.url`
607
- - **Solution**: Set `useOriginalUrl: false` to use the rewritten URL from `ctx.url` instead
608
- - **Example**: i18n middleware that strips language prefixes (`/it/page.html` โ†’ `/page.html`)
609
-
610
- ### ๐Ÿ“ Documentation
611
-
612
- - Added comprehensive `useOriginalUrl` documentation in README.md
613
- - Added JSDoc comments in index.cjs
614
- - Included practical i18n middleware example
615
- - Added option to API reference table
616
-
617
- ### ๐Ÿ”ง Changes
618
-
619
- - **index.cjs**: Line 108 - Added `useOriginalUrl` option initialization
620
- - **index.cjs**: Lines 117-125 - Modified URL construction logic to support both `ctx.originalUrl` and `ctx.url`
621
- - **README.md**: Added detailed section explaining `useOriginalUrl` with examples
622
- - **package.json**: Version bumped from `2.1.4` to `2.2.0`
623
-
624
- ### ๐Ÿ’ก Usage Example
625
-
626
- ```javascript
627
- // i18n middleware example
628
- app.use(async (ctx, next) => {
629
- if (ctx.path.match(/^\/it\//)) {
630
- ctx.url = ctx.path.replace(/^\/it/, ''); // /it/page.html โ†’ /page.html
631
- }
632
- await next();
633
- });
634
-
635
- app.use(koaClassicServer('/www', {
636
- useOriginalUrl: false // Use rewritten URL
637
- }));
638
- ```
639
-
640
- ### โš ๏ธ Migration Notes
641
-
642
- **No breaking changes** - this is a backward-compatible release.
643
-
644
- - **Default behavior unchanged**: `useOriginalUrl` defaults to `true`
645
- - **No code changes required** for existing implementations
646
- - **New feature**: Set `useOriginalUrl: false` if you use URL rewriting middleware
647
-
648
- ### ๐Ÿ“ฆ Package Changes
649
-
650
- - **Version**: `2.1.4` โ†’ `2.2.0`
651
- - **Semver**: Minor version bump (new feature, backward compatible)
652
-
653
- ---
654
-
655
- ## [2.1.3] - 2025-11-25
656
-
657
- ### ๐Ÿ”ง Configuration Changes
658
-
659
- #### Changed Default Caching Behavior
660
- - **Change**: `enableCaching` default value changed from `true` to `false`
661
- - **Rationale**: Better development experience - changes are immediately visible without cache invalidation
662
- - **Production Impact**: **Users should explicitly set `enableCaching: true` in production environments**
663
- - **Benefits in Production**:
664
- - 80-95% bandwidth reduction
665
- - Faster page loads with 304 Not Modified responses
666
- - Reduced server load
667
- - **Code**: `index.cjs:107`
668
-
669
- ### ๐Ÿ“ Documentation Improvements
670
-
671
- #### Enhanced Caching Documentation
672
- - Added comprehensive production recommendations in README.md
673
- - Added inline code comments explaining the default behavior
674
- - Clear guidance on when to enable caching (development vs production)
675
- - **Files**: `README.md`, `index.cjs`
676
-
677
- ### โš ๏ธ Migration Notice
678
-
679
- **IMPORTANT**: If you are upgrading from 2.1.2 or earlier and rely on HTTP caching:
680
-
681
- ```javascript
682
- // You must now explicitly enable caching in production
683
- app.use(koaClassicServer(__dirname + '/public', {
684
- enableCaching: true // โ† Add this for production environments
685
- }));
686
- ```
687
-
688
- **Development**: No changes needed - the new default (`false`) is better for development.
689
-
690
- **Production**: Explicitly set `enableCaching: true` to maintain previous behavior and performance benefits.
691
-
692
- ### ๐Ÿ“ฆ Package Changes
693
-
694
- - **Version**: `2.1.2` โ†’ `2.1.3`
695
-
696
- ---
697
-
698
- ## [2.1.2] - 2025-11-24
699
-
700
- ### ๐ŸŽจ Features
701
-
702
- #### Sortable Directory Columns
703
- - Apache2-like directory listing with clickable column headers
704
- - Sort by Name, Type, or Size (ascending/descending)
705
- - Fixed navigation bug after sorting
706
-
707
- #### File Size Display
708
- - Human-readable file sizes (B, KB, MB, GB, TB)
709
- - Proper formatting and precision
710
-
711
- #### HTTP Caching
712
- - ETag and Last-Modified headers
713
- - 304 Not Modified responses
714
- - 80-95% bandwidth reduction
715
-
716
- ### ๐Ÿงช Testing
717
- - 153 tests passing
718
- - Comprehensive test coverage
719
-
720
- ---
721
-
722
- ## [2.1.1] - 2025-11-23
723
-
724
- ### ๐Ÿš€ Production Release
725
-
726
- - Async/await implementation
727
- - Non-blocking I/O
728
- - Performance optimizations
729
- - Flow documentation
730
-
731
- ---
732
-
733
- ## [1.2.0] - 2025-11-17
734
-
735
- ### ๐ŸŽ‰ SECURITY & BUG FIX RELEASE
736
-
737
- This release contains **critical security fixes** and important bug fixes. All users should upgrade immediately.
738
-
739
- ### ๐Ÿ”’ Security Fixes (CRITICAL)
740
-
741
- #### Fixed Path Traversal Vulnerability
742
- - **Issue**: Attackers could access files outside the served directory using `../` sequences
743
- - **Impact**: CRITICAL - Unauthorized file access
744
- - **Fix**: Added path normalization and validation to ensure all file access stays within `rootDir`
745
- - **Code**: `index.cjs:106-124`
746
-
747
- #### Fixed Template Rendering Crash
748
- - **Issue**: Unhandled errors in template rendering could crash the entire server
749
- - **Impact**: CRITICAL - Denial of Service
750
- - **Fix**: Added try-catch around template render calls with proper error handling
751
- - **Code**: `index.cjs:195-205`
752
-
753
- ### โœ… Bug Fixes
754
-
755
- #### Fixed HTTP Status Code 404
756
- - **Issue**: Missing files returned HTML "Not Found" with HTTP 200 status instead of 404
757
- - **Impact**: HIGH - Violates HTTP standards, affects SEO, breaks caching
758
- - **Fix**: Properly set `ctx.status = 404` when resources are not found
759
- - **Locations**:
760
- - `index.cjs:130` - File/directory not found
761
- - `index.cjs:158` - Directory listing disabled
762
-
763
- #### Fixed Race Condition in File Access
764
- - **Issue**: Files could be deleted between existence check and reading, causing uncaught errors
765
- - **Impact**: HIGH - Server crashes on file access errors
766
- - **Fix**: Added `fs.promises.access()` check before streaming files with error handling
767
- - **Code**: `index.cjs:208-216`
768
-
769
- #### Fixed File Extension Extraction
770
- - **Issue**: Using `split(".")` failed for:
771
- - Files without extension (`README`)
772
- - Hidden files (`.gitignore`)
773
- - Paths with dots (`/folder.backup/file`)
774
- - **Impact**: HIGH - Template rendering activated incorrectly
775
- - **Fix**: Use `path.extname()` for robust extension extraction
776
- - **Code**: `index.cjs:192`
777
-
778
- #### Fixed Directory Read Errors
779
- - **Issue**: `fs.readdirSync()` could throw unhandled errors (permissions, deleted directories)
780
- - **Impact**: MEDIUM - Server crashes on directory access errors
781
- - **Fix**: Added try-catch with user-friendly error message
782
- - **Code**: `index.cjs:245-264`
783
-
784
- #### Fixed Content-Disposition Header
785
- - **Issue**: Filename in Content-Disposition header was not quoted and included full path
786
- - **Impact**: MEDIUM - Download issues with special characters in filenames
787
- - **Fix**:
788
- - Use only basename (not full path)
789
- - Quote filename and escape quotes
790
- - **Code**: `index.cjs:234-239`
791
-
792
- ### ๐ŸŽจ Improvements
793
-
794
- #### Added Input Validation
795
- - Validate `rootDir` is a non-empty string
796
- - Validate `rootDir` is an absolute path
797
- - Throw meaningful errors for invalid input
798
-
799
- #### Added XSS Protection
800
- - HTML-escape all user-controlled content in directory listings
801
- - Escapes filenames, paths, and MIME types
802
- - Prevents XSS attacks through malicious filenames
803
-
804
- #### Improved Error Messages
805
- - More descriptive error messages
806
- - Console logging for debugging
807
- - Stream error handling
808
-
809
- #### Code Quality
810
- - Fixed usage of `Array()` constructor to literal syntax `[]`
811
- - Better code organization and comments
812
- - Improved HTML output formatting
813
-
814
- ### ๐Ÿ“ Added Files
815
-
816
- - **`__tests__/security.test.js`**: Comprehensive security and bug tests
817
- - **`DEBUG_REPORT.md`**: Detailed analysis of all bugs and fixes
818
- - **`DOCUMENTATION.md`**: Complete documentation (1500+ lines)
819
- - **`CHANGELOG.md`**: This file
820
-
821
- ### ๐Ÿงช Testing
822
-
823
- - All 71 tests passing
824
- - Added security test suite
825
- - Path traversal tests
826
- - Template error handling tests
827
- - Status code validation tests
828
- - Race condition tests
829
- - Content-Disposition tests
830
-
831
- ### ๐Ÿ“ฆ Package Changes
832
-
833
- - **Version**: `1.1.0` โ†’ `1.2.0`
834
- - **Description**: Enhanced with security fixes
835
- - **Keywords**: Added `secure`, `middleware`, `file-server`, `directory-listing`
836
- - **Scripts**: Added `test:security` command
837
-
838
- ### โš ๏ธ Breaking Changes
839
-
840
- **None** - This is a backwards-compatible release. However, behavior changes for security:
841
-
842
- 1. **404 Status Codes**: Now properly returns 404 instead of 200 for missing resources
843
- 2. **Path Traversal**: Requests with `../` now return 403 Forbidden instead of allowing access
844
- 3. **Error Handling**: Template errors return 500 instead of crashing the server
845
-
846
- These changes fix bugs and security issues. The new behavior is correct and standards-compliant.
847
-
848
- ### ๐Ÿ”„ Migration Guide
849
-
850
- No code changes required! Simply update:
851
-
852
- ```bash
853
- npm update koa-classic-server
854
- ```
855
-
856
- **Recommended**: Verify that:
857
- 1. `rootDir` is an absolute path (e.g., `__dirname + '/public'`)
858
- 2. Your error handling expects proper 404/403/500 status codes
859
- 3. Your tests pass with the new behavior
860
-
861
- ### ๐Ÿ“Š Statistics
862
-
863
- - **Lines of code fixed**: ~200
864
- - **Security vulnerabilities fixed**: 2 critical
865
- - **Bugs fixed**: 6
866
- - **Tests added**: 12 security tests
867
- - **Documentation added**: 2000+ lines
868
- - **Test coverage**: 71 tests passing
869
-
870
- ### ๐Ÿ™ Credits
871
-
872
- - **Author**: Italo Paesano
873
- - **Security Audit**: Comprehensive code analysis
874
- - **Testing**: Jest & Supertest
875
-
876
- ---
877
-
878
- ## [1.1.0] - Previous Release
879
-
880
- ### Features
881
- - Basic static file serving
882
- - Directory listing
883
- - Template engine support
884
- - URL prefixes
885
- - Reserved URLs
886
-
887
- ### Known Issues (Fixed in 1.2.0)
888
- - Path traversal vulnerability โš ๏ธ CRITICAL
889
- - Missing 404 status codes
890
- - Unhandled template errors โš ๏ธ CRITICAL
891
- - Race condition in file access
892
- - Fragile file extension extraction
893
- - Missing error handling
894
-
895
- ---
896
-
897
- ## Links
898
-
899
- - [Full Documentation](./DOCUMENTATION.md)
900
- - [Debug Report](./DEBUG_REPORT.md)
901
- - [Repository](https://github.com/italopaesano/koa-classic-server)
902
- - [npm Package](https://www.npmjs.com/package/koa-classic-server)
903
-
904
- ---
905
-
906
- **โš ๏ธ Security Notice**: Version 1.2.0 fixes critical vulnerabilities. Update immediately if using 1.1.0 or earlier.