koa-classic-server 3.0.0 โ 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.
- package/README.md +44 -120
- package/index.cjs +236 -21
- package/package.json +10 -5
- package/.github/workflows/npm-publish.yml +0 -98
- package/CLAUDE.md +0 -101
- package/__tests__/benchmark-results-baseline-v1.2.0.txt +0 -354
- package/__tests__/benchmark-results-optimized-v2.0.0.txt +0 -354
- package/__tests__/benchmark-results-v3.0.0.txt +0 -372
- package/__tests__/benchmark.js +0 -239
- package/__tests__/caching-headers.test.js +0 -556
- package/__tests__/compression-fixtures/data.json +0 -1
- package/__tests__/compression-fixtures/large.txt +0 -1
- package/__tests__/compression-fixtures/small.txt +0 -1
- package/__tests__/compression.test.js +0 -284
- package/__tests__/customTest/README.md +0 -6
- package/__tests__/customTest/loadConfig.util.js +0 -41
- package/__tests__/customTest/serversToLoad.util.js +0 -93
- package/__tests__/demo-regex-index.js +0 -140
- package/__tests__/deprecation-warnings.test.js +0 -105
- package/__tests__/directory-sorting-links.test.js +0 -135
- package/__tests__/dt-unknown.test.js +0 -635
- package/__tests__/ejs.test.js +0 -299
- package/__tests__/hidden-fixtures/.dot-dir/inside.txt +0 -1
- package/__tests__/hidden-fixtures/.env +0 -2
- package/__tests__/hidden-fixtures/.well-known/acme-challenge.txt +0 -1
- package/__tests__/hidden-fixtures/config/secrets/password.txt +0 -1
- package/__tests__/hidden-fixtures/data.key +0 -1
- package/__tests__/hidden-fixtures/file.secret +0 -1
- package/__tests__/hidden-fixtures/index.html +0 -1
- package/__tests__/hidden-fixtures/normal.txt +0 -1
- package/__tests__/hidden-fixtures/subdir/.env +0 -1
- package/__tests__/hidden-fixtures/subdir/regular.txt +0 -1
- package/__tests__/hidden-option.test.js +0 -407
- package/__tests__/hideExtension.test.js +0 -607
- package/__tests__/index-option.test.js +0 -449
- package/__tests__/index.test.js +0 -345
- package/__tests__/listing.test.js +0 -437
- package/__tests__/logger.test.js +0 -232
- package/__tests__/performance.test.js +0 -370
- package/__tests__/publicWwwTest/cartella/sottocartella/ciao.html +0 -11
- package/__tests__/publicWwwTest/cartella/sottocartella/provaEjs/testEjs.ejs +0 -11
- package/__tests__/publicWwwTest/cartella vuota con spazi nel nome/file con spazio nel nome .txt +0 -1
- package/__tests__/publicWwwTest/cartella vuota con spazi nel nome /file con spazio nel nome .txt +0 -1
- package/__tests__/publicWwwTest/ejs-templates/complex.ejs +0 -56
- package/__tests__/publicWwwTest/ejs-templates/index.ejs +0 -30
- package/__tests__/publicWwwTest/ejs-templates/simple.ejs +0 -13
- package/__tests__/publicWwwTest/ejs-templates/with-conditional.ejs +0 -28
- package/__tests__/publicWwwTest/ejs-templates/with-escaping.ejs +0 -26
- package/__tests__/publicWwwTest/ejs-templates/with-loop.ejs +0 -16
- package/__tests__/publicWwwTest/hideext-test/about/index.html +0 -1
- package/__tests__/publicWwwTest/hideext-test/about.EJS +0 -1
- package/__tests__/publicWwwTest/hideext-test/about.ejs +0 -2
- package/__tests__/publicWwwTest/hideext-test/blog/articolo.ejs +0 -2
- package/__tests__/publicWwwTest/hideext-test/conflict-test/pagina +0 -1
- package/__tests__/publicWwwTest/hideext-test/conflict-test/pagina.ejs +0 -1
- package/__tests__/publicWwwTest/hideext-test/file.ejs.bak +0 -1
- package/__tests__/publicWwwTest/hideext-test/index.ejs +0 -2
- package/__tests__/publicWwwTest/hideext-test/photo.txt +0 -1
- package/__tests__/publicWwwTest/hideext-test/sezione/index.ejs +0 -1
- package/__tests__/publicWwwTest/hideext-test/style.css +0 -1
- package/__tests__/publicWwwTest/ile_vuoto.txt +0 -0
- package/__tests__/publicWwwTest/index.html +0 -11
- package/__tests__/publicWwwTest/prova file .txt +0 -2
- package/__tests__/publicWwwTest/semplicetxt.txt +0 -1
- package/__tests__/publicWwwTest/test-page.html +0 -1
- package/__tests__/publicWwwTest/test.txt +0 -1
- package/__tests__/range-fixtures/sample.txt +0 -1
- package/__tests__/range.test.js +0 -223
- package/__tests__/security-headers.test.js +0 -165
- package/__tests__/security.test.js +0 -322
- package/__tests__/server-cache-fixtures/large.txt +0 -1
- package/__tests__/server-cache-fixtures/small.txt +0 -1
- package/__tests__/server-cache.test.js +0 -594
- package/__tests__/setup-benchmark.js +0 -178
- package/__tests__/symlink.test.js +0 -441
- package/__tests__/template-timeout.test.js +0 -321
- package/__tests__/test-regex-quick.js +0 -158
- package/__tests__/useOriginalUrl.test.js +0 -213
- package/docs/ACTION_PLAN.md +0 -293
- package/docs/BENCHMARKS.md +0 -317
- package/docs/CHANGELOG.md +0 -880
- package/docs/CODE_REVIEW.md +0 -300
- package/docs/DEBUG_REPORT.md +0 -593
- package/docs/DOCUMENTATION.md +0 -1864
- package/docs/EXAMPLES_INDEX_OPTION.md +0 -395
- package/docs/FLOW_DIAGRAM.md +0 -954
- package/docs/INDEX_OPTION_PRIORITY.md +0 -527
- package/docs/OPTIMIZATION_HTTP_CACHING.md +0 -689
- package/docs/OPTIMIZATION_ROADMAP_for_V3.md +0 -864
- package/docs/PERFORMANCE_ANALYSIS.md +0 -839
- package/docs/PERFORMANCE_COMPARISON.md +0 -388
- package/docs/noteExports.md +0 -148
- package/docs/security_improvement_for_V3.md +0 -421
- package/docs/template-engine/TEMPLATE_ENGINE_GUIDE.md +0 -1734
- package/docs/template-engine/esempi-incrementali.js +0 -192
- package/docs/template-engine/examples/esempio1-nessun-dato.ejs +0 -12
- package/docs/template-engine/examples/esempio2-una-variabile.ejs +0 -11
- package/docs/template-engine/examples/esempio3-piu-variabili.ejs +0 -15
- package/docs/template-engine/examples/esempio4-condizionale.ejs +0 -18
- package/docs/template-engine/examples/esempio5-loop.ejs +0 -18
- package/docs/template-engine/examples/index-esempi.html +0 -181
- package/docs/template-engine/examples/index.html +0 -40
- package/docs/template-engine/examples/test.ejs +0 -64
- package/eslint.config.mjs +0 -17
- package/jest.config.js +0 -18
package/docs/CHANGELOG.md
DELETED
|
@@ -1,880 +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.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
|
-
|
|
297
|
-
## [2.6.1] - 2026-03-04
|
|
298
|
-
|
|
299
|
-
### ๐ Bug Fix
|
|
300
|
-
|
|
301
|
-
#### Fixed DT_UNKNOWN Handling (type 0) on overlayfs, NFS, FUSE, NixOS buildFHSEnv, ecryptfs
|
|
302
|
-
- **Issue**: On filesystems where `readdir({ withFileTypes: true })` returns dirents with `DT_UNKNOWN` (type 0), all `dirent.is*()` methods return `false`. This caused three failures:
|
|
303
|
-
1. `isFileOrSymlinkToFile()` missed valid files โ `findIndexFile()` returned empty results, so `GET /` showed a directory listing instead of rendering the index file
|
|
304
|
-
2. `isDirOrSymlinkToDir()` missed valid directories โ directory type resolution failed
|
|
305
|
-
3. `show_dir()` skipped entries with type 0, logging `"Unknown file type: 0"` โ directory listings appeared empty or partial
|
|
306
|
-
- **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
|
|
307
|
-
- **Impact**: HIGH โ Server unusable on affected filesystems (index file not served, directory listing empty)
|
|
308
|
-
- **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.
|
|
309
|
-
- **Code**:
|
|
310
|
-
- `isFileOrSymlinkToFile()` โ DT_UNKNOWN fallback via `stat().isFile()`
|
|
311
|
-
- `isDirOrSymlinkToDir()` โ DT_UNKNOWN fallback via `stat().isDirectory()`
|
|
312
|
-
- `show_dir()` โ Accept type 0 entries and resolve via `stat()` instead of skipping them
|
|
313
|
-
- **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."*
|
|
314
|
-
|
|
315
|
-
### ๐งช Testing
|
|
316
|
-
- Added `__tests__/dt-unknown.test.js` with 20 tests covering:
|
|
317
|
-
- `isFileOrSymlinkToFile` / `isDirOrSymlinkToDir` with DT_UNKNOWN dirents
|
|
318
|
-
- `findIndexFile` with all-unknown-type entries (string and RegExp patterns)
|
|
319
|
-
- `show_dir` rendering (resolved types, no skipped entries, correct MIME types and sizes)
|
|
320
|
-
- Full integration tests (index file serving, direct file access, complete directory listing)
|
|
321
|
-
- Edge cases (mixed regular + DT_UNKNOWN dirents, index priority, Dirent type 0 verification)
|
|
322
|
-
- 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
|
|
323
|
-
- All 329 tests pass across 12 test suites (zero regressions)
|
|
324
|
-
|
|
325
|
-
### ๐ฆ Package Changes
|
|
326
|
-
- **Version**: `2.6.0` โ `2.6.1`
|
|
327
|
-
- **Semver**: Patch version bump (bug fix only, no API changes)
|
|
328
|
-
|
|
329
|
-
---
|
|
330
|
-
|
|
331
|
-
## [2.6.0] - 2026-03-01
|
|
332
|
-
|
|
333
|
-
### ๐ฆ Dependency Upgrades
|
|
334
|
-
|
|
335
|
-
#### mime-types: ^2.1.35 โ ^3.0.2 (Major)
|
|
336
|
-
- **Breaking change upstream**: New `mimeScore` algorithm for extension conflict resolution
|
|
337
|
-
- **Impact on this project**: Minimal โ the 11 changed MIME mappings affect only uncommon extensions
|
|
338
|
-
- **Notable mapping changes**:
|
|
339
|
-
- `.wav`: `audio/wave` โ `audio/wav` (equivalent, all browsers accept both)
|
|
340
|
-
- `.js`: `application/javascript` โ `text/javascript` (correct per RFC 9239)
|
|
341
|
-
- `.rtf`: `text/rtf` โ `application/rtf` (marginal, rare usage)
|
|
342
|
-
- `.mp4`: Unchanged in v3.0.2 โ still resolves to `video/mp4`
|
|
343
|
-
- **Node.js requirement**: mime-types 3 requires Node.js >= 18
|
|
344
|
-
|
|
345
|
-
#### ejs: ^3.1.10 โ ^4.0.0 (Major)
|
|
346
|
-
- **Breaking changes upstream**: None affecting this project
|
|
347
|
-
- EJS 4 removed deprecated `with()` statement support (this project never used it)
|
|
348
|
-
- EJS 4 added stricter `exports` map in package.json
|
|
349
|
-
- **API fully compatible**: `ejs.render()` and `ejs.renderFile()` work identically
|
|
350
|
-
- **Security**: EJS 3.x is EOL โ v4 resolves known CVEs in the 3.x line
|
|
351
|
-
|
|
352
|
-
### ๐ง Configuration Changes
|
|
353
|
-
|
|
354
|
-
#### Added `engines` field
|
|
355
|
-
- Added `"engines": { "node": ">=18" }` to package.json
|
|
356
|
-
- Formalizes the Node.js minimum version requirement imposed by mime-types 3
|
|
357
|
-
|
|
358
|
-
#### Tightened Koa peerDependency for 2.x
|
|
359
|
-
- **koa**: `"^2.0.0 || >=3.1.2"` โ `"^2.16.4 || >=3.1.2"`
|
|
360
|
-
- Excludes Koa 2.0.0โ2.16.3 which are affected by 4 known CVEs:
|
|
361
|
-
- CVE-2025-25200: ReDoS via `X-Forwarded-Proto`/`X-Forwarded-Host` (CVSS 9.2, fixed in 2.15.4)
|
|
362
|
-
- CVE-2025-32379: XSS via `ctx.redirect()` (fixed in 2.16.1)
|
|
363
|
-
- CVE-2025-62595: Open Redirect via trailing `//` (fixed in 2.16.3)
|
|
364
|
-
- CVE-2026-27959: Host Header Injection via `ctx.hostname` (CVSS 7.5, fixed in 2.16.4)
|
|
365
|
-
|
|
366
|
-
### ๐งช Testing
|
|
367
|
-
- All 309 tests pass across 11 test suites (zero regressions)
|
|
368
|
-
- No code changes required โ both library upgrades are API-compatible
|
|
369
|
-
|
|
370
|
-
### ๐ฆ Package Changes
|
|
371
|
-
- **Version**: `2.5.2` โ `2.6.0`
|
|
372
|
-
- **Semver**: Minor version bump (dependency upgrades, no API changes)
|
|
373
|
-
|
|
374
|
-
---
|
|
375
|
-
|
|
376
|
-
## [2.5.2] - 2026-03-01
|
|
377
|
-
|
|
378
|
-
### ๐ Security Fix
|
|
379
|
-
|
|
380
|
-
#### Resolved all 11 npm audit vulnerabilities
|
|
381
|
-
- **jest**: `^29.7.0` โ `^30.2.0` (major โ fixes minimatch ReDoS, brace-expansion ReDoS, @babel/helpers inefficient RegExp)
|
|
382
|
-
- **supertest**: `^7.0.0` โ `^7.2.2` (fixes critical form-data unsafe random boundary)
|
|
383
|
-
- **inquirer**: `^12.4.1` โ `^13.3.0` (fixes tmp arbitrary file write via symlink, external-editor chain)
|
|
384
|
-
- **autocannon**: `^7.15.0` โ `^8.0.0` (major)
|
|
385
|
-
|
|
386
|
-
#### Updated peerDependency
|
|
387
|
-
- **koa**: `"^2.0.0 || ^3.0.0"` โ `"^2.0.0 || >=3.1.2"`
|
|
388
|
-
- Excludes Koa 3.0.0โ3.1.1 which had Host Header Injection via `ctx.hostname`
|
|
389
|
-
|
|
390
|
-
### ๐งช Testing
|
|
391
|
-
- All 309 tests pass across 11 test suites (zero regressions)
|
|
392
|
-
- `npm audit` reports 0 vulnerabilities
|
|
393
|
-
|
|
394
|
-
### ๐ฆ Package Changes
|
|
395
|
-
- **Version**: `2.5.1` โ `2.5.2`
|
|
396
|
-
- **Semver**: Patch version bump (security fixes only, no API changes)
|
|
397
|
-
|
|
398
|
-
---
|
|
399
|
-
|
|
400
|
-
## [2.5.1] - 2026-03-01
|
|
401
|
-
|
|
402
|
-
### ๐ Documentation
|
|
403
|
-
|
|
404
|
-
- Added dedicated usage example for `useOriginalUrl` (Section 7) with realistic i18n middleware scenario (/it/, /en/, /fr/)
|
|
405
|
-
- Added "Advanced hideExtension Scenarios" section (Section 8):
|
|
406
|
-
- Recommended file/directory structure (ASCII tree)
|
|
407
|
-
- Combined `hideExtension` + i18n middleware example with `useOriginalUrl: false`
|
|
408
|
-
- Temporary redirect (302) variant with guidance on 301 vs 302 usage
|
|
409
|
-
- Added `hideExtension` and `useOriginalUrl` to the Complete Production Example (Section 11)
|
|
410
|
-
|
|
411
|
-
### ๐ฆ Package Changes
|
|
412
|
-
- **Version**: `2.5.0` โ `2.5.1`
|
|
413
|
-
- **Semver**: Patch version bump (documentation only, no code changes)
|
|
414
|
-
|
|
415
|
-
---
|
|
416
|
-
|
|
417
|
-
## [2.5.0] - 2026-02-28
|
|
418
|
-
|
|
419
|
-
### โจ New Feature
|
|
420
|
-
|
|
421
|
-
#### hideExtension - Clean URLs (mod_rewrite-like)
|
|
422
|
-
- **New Option**: `hideExtension: { ext: '.ejs', redirect: 301 }`
|
|
423
|
-
- **Purpose**: Hide file extensions from URLs for SEO-friendly clean URLs
|
|
424
|
-
- **Clean URL Resolution**: `/about` โ serves `about.ejs` (when file exists)
|
|
425
|
-
- **Extension Redirect**: `/about.ejs` โ 301 redirect to `/about` (preserves query string)
|
|
426
|
-
- **Index File Redirect**: `/index.ejs` โ redirect to `/`, `/section/index.ejs` โ redirect to `/section/`
|
|
427
|
-
- **Conflict Resolution**: `.ejs` file wins over both directories and extensionless files with same base name
|
|
428
|
-
- **Case-Sensitive**: Extension matching is case-sensitive (`.ejs` โ `.EJS`)
|
|
429
|
-
- **No Interference**: URLs with other extensions (`.css`, `.png`, etc.) pass through normally
|
|
430
|
-
- **Trailing Slash**: `/about/` always means directory, never resolves to file
|
|
431
|
-
- **Redirect uses `ctx.originalUrl`**: Preserves URL prefixes from upstream middleware (i18n, routing)
|
|
432
|
-
|
|
433
|
-
#### Input Validation
|
|
434
|
-
- `hideExtension: true` โ throws Error (must be an object)
|
|
435
|
-
- `hideExtension: {}` โ throws Error (missing `ext`)
|
|
436
|
-
- `hideExtension: { ext: '' }` โ throws Error (empty ext)
|
|
437
|
-
- `hideExtension: { ext: 'ejs' }` โ warning + auto-normalizes to `.ejs`
|
|
438
|
-
- `hideExtension: { ext: '.ejs', redirect: 'abc' }` โ throws Error (redirect must be number)
|
|
439
|
-
|
|
440
|
-
#### Integration with Existing Options
|
|
441
|
-
- **urlsReserved**: Checked before `hideExtension`, no interference
|
|
442
|
-
- **urlPrefix**: `hideExtension` works on path after prefix removal
|
|
443
|
-
- **useOriginalUrl**: Resolution follows setting; redirect always uses `ctx.originalUrl`
|
|
444
|
-
- **template**: Resolved files pass through template engine normally
|
|
445
|
-
- **method**: `hideExtension` only applies to allowed HTTP methods
|
|
446
|
-
|
|
447
|
-
### ๐งช Testing
|
|
448
|
-
- Added `__tests__/hideExtension.test.js` with 31 tests covering:
|
|
449
|
-
- Clean URL resolution (single and multi-level paths)
|
|
450
|
-
- Extension redirect (301/302, query string preservation)
|
|
451
|
-
- Directory/file conflict resolution
|
|
452
|
-
- Trailing slash behavior
|
|
453
|
-
- Extensionless file conflict
|
|
454
|
-
- Index file redirect (`/index.ejs` โ `/`)
|
|
455
|
-
- `urlsReserved` interaction
|
|
456
|
-
- `useOriginalUrl` interaction (redirect preserves prefix)
|
|
457
|
-
- Case-sensitive matching
|
|
458
|
-
- No interference with other extensions
|
|
459
|
-
- Template engine integration
|
|
460
|
-
- Input validation (7 validation tests)
|
|
461
|
-
- All 278 existing tests still pass (zero regressions)
|
|
462
|
-
|
|
463
|
-
### ๐ฆ Package Changes
|
|
464
|
-
- **Version**: `2.4.0` โ `2.5.0`
|
|
465
|
-
- **Semver**: Minor version bump (new feature, backward compatible)
|
|
466
|
-
|
|
467
|
-
---
|
|
468
|
-
|
|
469
|
-
## [2.4.0] - 2026-02-28
|
|
470
|
-
|
|
471
|
-
### ๐ Bug Fix
|
|
472
|
-
|
|
473
|
-
#### Fixed Symlink Support in Index File Discovery and Directory Listing
|
|
474
|
-
- **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.
|
|
475
|
-
- **Impact**: HIGH - Server unusable on NixOS/buildFHSEnv and similar environments
|
|
476
|
-
- **Fix**: Added `isFileOrSymlinkToFile()` / `isDirOrSymlinkToDir()` helpers that follow symlinks via `fs.promises.stat()` only when `dirent.isSymbolicLink()` is true, adding zero overhead for regular files.
|
|
477
|
-
- **Code**: `index.cjs` - new helpers + `findIndexFile()` + `show_dir()`
|
|
478
|
-
|
|
479
|
-
### โจ Improvements
|
|
480
|
-
|
|
481
|
-
#### Directory Listing Symlink Indicators
|
|
482
|
-
- Symlinks to files/directories show `( Symlink )` label next to the name
|
|
483
|
-
- Broken/circular symlinks show `( Broken Symlink )` label (name visible but not clickable)
|
|
484
|
-
- Symlinks resolved to effective type for MIME and size display (e.g. symlink to dir shows `DIR`)
|
|
485
|
-
- Sorting uses effective type (symlink-to-dir sorts with directories)
|
|
486
|
-
|
|
487
|
-
### ๐งช Testing
|
|
488
|
-
- Added `__tests__/symlink.test.js` with 17 tests covering:
|
|
489
|
-
- Regular file as index (regression)
|
|
490
|
-
- Symlink to file as index (string and RegExp patterns)
|
|
491
|
-
- Direct GET to symlinked file
|
|
492
|
-
- EJS template via symlink
|
|
493
|
-
- Symlink to directory (listing and file access)
|
|
494
|
-
- Broken and circular symlinks
|
|
495
|
-
- Directory listing indicators (`( Symlink )`, `( Broken Symlink )`)
|
|
496
|
-
- Regular file regression (no false symlink indicator)
|
|
497
|
-
- All 187 existing tests still pass (zero regressions)
|
|
498
|
-
|
|
499
|
-
### ๐ฆ Package Changes
|
|
500
|
-
- **Semver**: Minor version bump (new feature, backward compatible)
|
|
501
|
-
|
|
502
|
-
---
|
|
503
|
-
|
|
504
|
-
## [2.3.0] - 2026-01-03
|
|
505
|
-
|
|
506
|
-
### ๐ Renamed Options (with Backward Compatibility)
|
|
507
|
-
|
|
508
|
-
#### Renamed Caching Options for Clarity
|
|
509
|
-
- **Old Names** (DEPRECATED): `enableCaching`, `cacheMaxAge`
|
|
510
|
-
- **New Names**: `browserCacheEnabled`, `browserCacheMaxAge`
|
|
511
|
-
- **Reason**: Improved clarity - these options specifically control browser-side HTTP caching
|
|
512
|
-
- **Backward Compatible**: Old names still work but display deprecation warnings
|
|
513
|
-
|
|
514
|
-
#### Deprecation Warnings
|
|
515
|
-
When using deprecated option names, a warning is displayed on the terminal:
|
|
516
|
-
```
|
|
517
|
-
[koa-classic-server] DEPRECATION WARNING: The "enableCaching" option is deprecated and will be removed in future versions.
|
|
518
|
-
Current usage: enableCaching: true
|
|
519
|
-
Recommended: browserCacheEnabled: true
|
|
520
|
-
Please update your configuration to use the new option name.
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
### ๐ Documentation Updates
|
|
524
|
-
|
|
525
|
-
- Updated README.md with new option names
|
|
526
|
-
- Updated JSDoc comments in index.cjs
|
|
527
|
-
- Added deprecation notes in Options table
|
|
528
|
-
- All examples updated to use new names
|
|
529
|
-
|
|
530
|
-
### ๐ง Changes
|
|
531
|
-
|
|
532
|
-
- **index.cjs**: Lines 109-135 - Added backward compatibility logic with deprecation warnings
|
|
533
|
-
- **index.cjs**: Lines 47-58 - Updated JSDoc comments
|
|
534
|
-
- **index.cjs**: Lines 350, 361 - Updated code to use new option names
|
|
535
|
-
- **README.md**: Updated all references to use new names, added deprecation notes
|
|
536
|
-
- **package.json**: Version bumped from `2.2.0` to `2.3.0`
|
|
537
|
-
|
|
538
|
-
### โ ๏ธ Migration Guide
|
|
539
|
-
|
|
540
|
-
**No immediate changes required** - old option names still work.
|
|
541
|
-
|
|
542
|
-
**Recommended migration:**
|
|
543
|
-
|
|
544
|
-
```javascript
|
|
545
|
-
// Old (still works, but deprecated)
|
|
546
|
-
app.use(koaClassicServer('/public', {
|
|
547
|
-
enableCaching: true,
|
|
548
|
-
cacheMaxAge: 3600
|
|
549
|
-
}));
|
|
550
|
-
|
|
551
|
-
// New (recommended)
|
|
552
|
-
app.use(koaClassicServer('/public', {
|
|
553
|
-
browserCacheEnabled: true,
|
|
554
|
-
browserCacheMaxAge: 3600
|
|
555
|
-
}));
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
**Timeline:**
|
|
559
|
-
- **v2.3.0**: Old names work with deprecation warnings
|
|
560
|
-
- **Future versions**: Old names may be removed (will be announced in advance)
|
|
561
|
-
|
|
562
|
-
### ๐ฆ Package Changes
|
|
563
|
-
|
|
564
|
-
- **Version**: `2.2.0` โ `2.3.0`
|
|
565
|
-
- **Semver**: Minor version bump (new feature names, backward compatible)
|
|
566
|
-
|
|
567
|
-
---
|
|
568
|
-
|
|
569
|
-
## [2.2.0] - 2026-01-03
|
|
570
|
-
|
|
571
|
-
### โจ Features
|
|
572
|
-
|
|
573
|
-
#### Added useOriginalUrl Option
|
|
574
|
-
- **New Option**: `useOriginalUrl` (Boolean, default: `true`)
|
|
575
|
-
- **Purpose**: Controls URL resolution for file serving - use `ctx.originalUrl` (immutable) or `ctx.url` (mutable)
|
|
576
|
-
- **Use Case**: Compatibility with URL rewriting middleware (i18n, routing)
|
|
577
|
-
- **Backward Compatible**: Default value `true` maintains existing behavior
|
|
578
|
-
|
|
579
|
-
#### URL Rewriting Middleware Support
|
|
580
|
-
- **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`
|
|
581
|
-
- **Solution**: Set `useOriginalUrl: false` to use the rewritten URL from `ctx.url` instead
|
|
582
|
-
- **Example**: i18n middleware that strips language prefixes (`/it/page.html` โ `/page.html`)
|
|
583
|
-
|
|
584
|
-
### ๐ Documentation
|
|
585
|
-
|
|
586
|
-
- Added comprehensive `useOriginalUrl` documentation in README.md
|
|
587
|
-
- Added JSDoc comments in index.cjs
|
|
588
|
-
- Included practical i18n middleware example
|
|
589
|
-
- Added option to API reference table
|
|
590
|
-
|
|
591
|
-
### ๐ง Changes
|
|
592
|
-
|
|
593
|
-
- **index.cjs**: Line 108 - Added `useOriginalUrl` option initialization
|
|
594
|
-
- **index.cjs**: Lines 117-125 - Modified URL construction logic to support both `ctx.originalUrl` and `ctx.url`
|
|
595
|
-
- **README.md**: Added detailed section explaining `useOriginalUrl` with examples
|
|
596
|
-
- **package.json**: Version bumped from `2.1.4` to `2.2.0`
|
|
597
|
-
|
|
598
|
-
### ๐ก Usage Example
|
|
599
|
-
|
|
600
|
-
```javascript
|
|
601
|
-
// i18n middleware example
|
|
602
|
-
app.use(async (ctx, next) => {
|
|
603
|
-
if (ctx.path.match(/^\/it\//)) {
|
|
604
|
-
ctx.url = ctx.path.replace(/^\/it/, ''); // /it/page.html โ /page.html
|
|
605
|
-
}
|
|
606
|
-
await next();
|
|
607
|
-
});
|
|
608
|
-
|
|
609
|
-
app.use(koaClassicServer('/www', {
|
|
610
|
-
useOriginalUrl: false // Use rewritten URL
|
|
611
|
-
}));
|
|
612
|
-
```
|
|
613
|
-
|
|
614
|
-
### โ ๏ธ Migration Notes
|
|
615
|
-
|
|
616
|
-
**No breaking changes** - this is a backward-compatible release.
|
|
617
|
-
|
|
618
|
-
- **Default behavior unchanged**: `useOriginalUrl` defaults to `true`
|
|
619
|
-
- **No code changes required** for existing implementations
|
|
620
|
-
- **New feature**: Set `useOriginalUrl: false` if you use URL rewriting middleware
|
|
621
|
-
|
|
622
|
-
### ๐ฆ Package Changes
|
|
623
|
-
|
|
624
|
-
- **Version**: `2.1.4` โ `2.2.0`
|
|
625
|
-
- **Semver**: Minor version bump (new feature, backward compatible)
|
|
626
|
-
|
|
627
|
-
---
|
|
628
|
-
|
|
629
|
-
## [2.1.3] - 2025-11-25
|
|
630
|
-
|
|
631
|
-
### ๐ง Configuration Changes
|
|
632
|
-
|
|
633
|
-
#### Changed Default Caching Behavior
|
|
634
|
-
- **Change**: `enableCaching` default value changed from `true` to `false`
|
|
635
|
-
- **Rationale**: Better development experience - changes are immediately visible without cache invalidation
|
|
636
|
-
- **Production Impact**: **Users should explicitly set `enableCaching: true` in production environments**
|
|
637
|
-
- **Benefits in Production**:
|
|
638
|
-
- 80-95% bandwidth reduction
|
|
639
|
-
- Faster page loads with 304 Not Modified responses
|
|
640
|
-
- Reduced server load
|
|
641
|
-
- **Code**: `index.cjs:107`
|
|
642
|
-
|
|
643
|
-
### ๐ Documentation Improvements
|
|
644
|
-
|
|
645
|
-
#### Enhanced Caching Documentation
|
|
646
|
-
- Added comprehensive production recommendations in README.md
|
|
647
|
-
- Added inline code comments explaining the default behavior
|
|
648
|
-
- Clear guidance on when to enable caching (development vs production)
|
|
649
|
-
- **Files**: `README.md`, `index.cjs`
|
|
650
|
-
|
|
651
|
-
### โ ๏ธ Migration Notice
|
|
652
|
-
|
|
653
|
-
**IMPORTANT**: If you are upgrading from 2.1.2 or earlier and rely on HTTP caching:
|
|
654
|
-
|
|
655
|
-
```javascript
|
|
656
|
-
// You must now explicitly enable caching in production
|
|
657
|
-
app.use(koaClassicServer(__dirname + '/public', {
|
|
658
|
-
enableCaching: true // โ Add this for production environments
|
|
659
|
-
}));
|
|
660
|
-
```
|
|
661
|
-
|
|
662
|
-
**Development**: No changes needed - the new default (`false`) is better for development.
|
|
663
|
-
|
|
664
|
-
**Production**: Explicitly set `enableCaching: true` to maintain previous behavior and performance benefits.
|
|
665
|
-
|
|
666
|
-
### ๐ฆ Package Changes
|
|
667
|
-
|
|
668
|
-
- **Version**: `2.1.2` โ `2.1.3`
|
|
669
|
-
|
|
670
|
-
---
|
|
671
|
-
|
|
672
|
-
## [2.1.2] - 2025-11-24
|
|
673
|
-
|
|
674
|
-
### ๐จ Features
|
|
675
|
-
|
|
676
|
-
#### Sortable Directory Columns
|
|
677
|
-
- Apache2-like directory listing with clickable column headers
|
|
678
|
-
- Sort by Name, Type, or Size (ascending/descending)
|
|
679
|
-
- Fixed navigation bug after sorting
|
|
680
|
-
|
|
681
|
-
#### File Size Display
|
|
682
|
-
- Human-readable file sizes (B, KB, MB, GB, TB)
|
|
683
|
-
- Proper formatting and precision
|
|
684
|
-
|
|
685
|
-
#### HTTP Caching
|
|
686
|
-
- ETag and Last-Modified headers
|
|
687
|
-
- 304 Not Modified responses
|
|
688
|
-
- 80-95% bandwidth reduction
|
|
689
|
-
|
|
690
|
-
### ๐งช Testing
|
|
691
|
-
- 153 tests passing
|
|
692
|
-
- Comprehensive test coverage
|
|
693
|
-
|
|
694
|
-
---
|
|
695
|
-
|
|
696
|
-
## [2.1.1] - 2025-11-23
|
|
697
|
-
|
|
698
|
-
### ๐ Production Release
|
|
699
|
-
|
|
700
|
-
- Async/await implementation
|
|
701
|
-
- Non-blocking I/O
|
|
702
|
-
- Performance optimizations
|
|
703
|
-
- Flow documentation
|
|
704
|
-
|
|
705
|
-
---
|
|
706
|
-
|
|
707
|
-
## [1.2.0] - 2025-11-17
|
|
708
|
-
|
|
709
|
-
### ๐ SECURITY & BUG FIX RELEASE
|
|
710
|
-
|
|
711
|
-
This release contains **critical security fixes** and important bug fixes. All users should upgrade immediately.
|
|
712
|
-
|
|
713
|
-
### ๐ Security Fixes (CRITICAL)
|
|
714
|
-
|
|
715
|
-
#### Fixed Path Traversal Vulnerability
|
|
716
|
-
- **Issue**: Attackers could access files outside the served directory using `../` sequences
|
|
717
|
-
- **Impact**: CRITICAL - Unauthorized file access
|
|
718
|
-
- **Fix**: Added path normalization and validation to ensure all file access stays within `rootDir`
|
|
719
|
-
- **Code**: `index.cjs:106-124`
|
|
720
|
-
|
|
721
|
-
#### Fixed Template Rendering Crash
|
|
722
|
-
- **Issue**: Unhandled errors in template rendering could crash the entire server
|
|
723
|
-
- **Impact**: CRITICAL - Denial of Service
|
|
724
|
-
- **Fix**: Added try-catch around template render calls with proper error handling
|
|
725
|
-
- **Code**: `index.cjs:195-205`
|
|
726
|
-
|
|
727
|
-
### โ
Bug Fixes
|
|
728
|
-
|
|
729
|
-
#### Fixed HTTP Status Code 404
|
|
730
|
-
- **Issue**: Missing files returned HTML "Not Found" with HTTP 200 status instead of 404
|
|
731
|
-
- **Impact**: HIGH - Violates HTTP standards, affects SEO, breaks caching
|
|
732
|
-
- **Fix**: Properly set `ctx.status = 404` when resources are not found
|
|
733
|
-
- **Locations**:
|
|
734
|
-
- `index.cjs:130` - File/directory not found
|
|
735
|
-
- `index.cjs:158` - Directory listing disabled
|
|
736
|
-
|
|
737
|
-
#### Fixed Race Condition in File Access
|
|
738
|
-
- **Issue**: Files could be deleted between existence check and reading, causing uncaught errors
|
|
739
|
-
- **Impact**: HIGH - Server crashes on file access errors
|
|
740
|
-
- **Fix**: Added `fs.promises.access()` check before streaming files with error handling
|
|
741
|
-
- **Code**: `index.cjs:208-216`
|
|
742
|
-
|
|
743
|
-
#### Fixed File Extension Extraction
|
|
744
|
-
- **Issue**: Using `split(".")` failed for:
|
|
745
|
-
- Files without extension (`README`)
|
|
746
|
-
- Hidden files (`.gitignore`)
|
|
747
|
-
- Paths with dots (`/folder.backup/file`)
|
|
748
|
-
- **Impact**: HIGH - Template rendering activated incorrectly
|
|
749
|
-
- **Fix**: Use `path.extname()` for robust extension extraction
|
|
750
|
-
- **Code**: `index.cjs:192`
|
|
751
|
-
|
|
752
|
-
#### Fixed Directory Read Errors
|
|
753
|
-
- **Issue**: `fs.readdirSync()` could throw unhandled errors (permissions, deleted directories)
|
|
754
|
-
- **Impact**: MEDIUM - Server crashes on directory access errors
|
|
755
|
-
- **Fix**: Added try-catch with user-friendly error message
|
|
756
|
-
- **Code**: `index.cjs:245-264`
|
|
757
|
-
|
|
758
|
-
#### Fixed Content-Disposition Header
|
|
759
|
-
- **Issue**: Filename in Content-Disposition header was not quoted and included full path
|
|
760
|
-
- **Impact**: MEDIUM - Download issues with special characters in filenames
|
|
761
|
-
- **Fix**:
|
|
762
|
-
- Use only basename (not full path)
|
|
763
|
-
- Quote filename and escape quotes
|
|
764
|
-
- **Code**: `index.cjs:234-239`
|
|
765
|
-
|
|
766
|
-
### ๐จ Improvements
|
|
767
|
-
|
|
768
|
-
#### Added Input Validation
|
|
769
|
-
- Validate `rootDir` is a non-empty string
|
|
770
|
-
- Validate `rootDir` is an absolute path
|
|
771
|
-
- Throw meaningful errors for invalid input
|
|
772
|
-
|
|
773
|
-
#### Added XSS Protection
|
|
774
|
-
- HTML-escape all user-controlled content in directory listings
|
|
775
|
-
- Escapes filenames, paths, and MIME types
|
|
776
|
-
- Prevents XSS attacks through malicious filenames
|
|
777
|
-
|
|
778
|
-
#### Improved Error Messages
|
|
779
|
-
- More descriptive error messages
|
|
780
|
-
- Console logging for debugging
|
|
781
|
-
- Stream error handling
|
|
782
|
-
|
|
783
|
-
#### Code Quality
|
|
784
|
-
- Fixed usage of `Array()` constructor to literal syntax `[]`
|
|
785
|
-
- Better code organization and comments
|
|
786
|
-
- Improved HTML output formatting
|
|
787
|
-
|
|
788
|
-
### ๐ Added Files
|
|
789
|
-
|
|
790
|
-
- **`__tests__/security.test.js`**: Comprehensive security and bug tests
|
|
791
|
-
- **`DEBUG_REPORT.md`**: Detailed analysis of all bugs and fixes
|
|
792
|
-
- **`DOCUMENTATION.md`**: Complete documentation (1500+ lines)
|
|
793
|
-
- **`CHANGELOG.md`**: This file
|
|
794
|
-
|
|
795
|
-
### ๐งช Testing
|
|
796
|
-
|
|
797
|
-
- All 71 tests passing
|
|
798
|
-
- Added security test suite
|
|
799
|
-
- Path traversal tests
|
|
800
|
-
- Template error handling tests
|
|
801
|
-
- Status code validation tests
|
|
802
|
-
- Race condition tests
|
|
803
|
-
- Content-Disposition tests
|
|
804
|
-
|
|
805
|
-
### ๐ฆ Package Changes
|
|
806
|
-
|
|
807
|
-
- **Version**: `1.1.0` โ `1.2.0`
|
|
808
|
-
- **Description**: Enhanced with security fixes
|
|
809
|
-
- **Keywords**: Added `secure`, `middleware`, `file-server`, `directory-listing`
|
|
810
|
-
- **Scripts**: Added `test:security` command
|
|
811
|
-
|
|
812
|
-
### โ ๏ธ Breaking Changes
|
|
813
|
-
|
|
814
|
-
**None** - This is a backwards-compatible release. However, behavior changes for security:
|
|
815
|
-
|
|
816
|
-
1. **404 Status Codes**: Now properly returns 404 instead of 200 for missing resources
|
|
817
|
-
2. **Path Traversal**: Requests with `../` now return 403 Forbidden instead of allowing access
|
|
818
|
-
3. **Error Handling**: Template errors return 500 instead of crashing the server
|
|
819
|
-
|
|
820
|
-
These changes fix bugs and security issues. The new behavior is correct and standards-compliant.
|
|
821
|
-
|
|
822
|
-
### ๐ Migration Guide
|
|
823
|
-
|
|
824
|
-
No code changes required! Simply update:
|
|
825
|
-
|
|
826
|
-
```bash
|
|
827
|
-
npm update koa-classic-server
|
|
828
|
-
```
|
|
829
|
-
|
|
830
|
-
**Recommended**: Verify that:
|
|
831
|
-
1. `rootDir` is an absolute path (e.g., `__dirname + '/public'`)
|
|
832
|
-
2. Your error handling expects proper 404/403/500 status codes
|
|
833
|
-
3. Your tests pass with the new behavior
|
|
834
|
-
|
|
835
|
-
### ๐ Statistics
|
|
836
|
-
|
|
837
|
-
- **Lines of code fixed**: ~200
|
|
838
|
-
- **Security vulnerabilities fixed**: 2 critical
|
|
839
|
-
- **Bugs fixed**: 6
|
|
840
|
-
- **Tests added**: 12 security tests
|
|
841
|
-
- **Documentation added**: 2000+ lines
|
|
842
|
-
- **Test coverage**: 71 tests passing
|
|
843
|
-
|
|
844
|
-
### ๐ Credits
|
|
845
|
-
|
|
846
|
-
- **Author**: Italo Paesano
|
|
847
|
-
- **Security Audit**: Comprehensive code analysis
|
|
848
|
-
- **Testing**: Jest & Supertest
|
|
849
|
-
|
|
850
|
-
---
|
|
851
|
-
|
|
852
|
-
## [1.1.0] - Previous Release
|
|
853
|
-
|
|
854
|
-
### Features
|
|
855
|
-
- Basic static file serving
|
|
856
|
-
- Directory listing
|
|
857
|
-
- Template engine support
|
|
858
|
-
- URL prefixes
|
|
859
|
-
- Reserved URLs
|
|
860
|
-
|
|
861
|
-
### Known Issues (Fixed in 1.2.0)
|
|
862
|
-
- Path traversal vulnerability โ ๏ธ CRITICAL
|
|
863
|
-
- Missing 404 status codes
|
|
864
|
-
- Unhandled template errors โ ๏ธ CRITICAL
|
|
865
|
-
- Race condition in file access
|
|
866
|
-
- Fragile file extension extraction
|
|
867
|
-
- Missing error handling
|
|
868
|
-
|
|
869
|
-
---
|
|
870
|
-
|
|
871
|
-
## Links
|
|
872
|
-
|
|
873
|
-
- [Full Documentation](./DOCUMENTATION.md)
|
|
874
|
-
- [Debug Report](./DEBUG_REPORT.md)
|
|
875
|
-
- [Repository](https://github.com/italopaesano/koa-classic-server)
|
|
876
|
-
- [npm Package](https://www.npmjs.com/package/koa-classic-server)
|
|
877
|
-
|
|
878
|
-
---
|
|
879
|
-
|
|
880
|
-
**โ ๏ธ Security Notice**: Version 1.2.0 fixes critical vulnerabilities. Update immediately if using 1.1.0 or earlier.
|