fix: (regression 3.0.2) some illegal paths were not blocked
Massimo Melina committed
Apr 1, 2026 at 16:37 UTC
b255067734f0417ffd480fd7153161c9dd4674e9
3 files changed
+13
-2
src/cross.ts
+3
-1
@@ -460,7 +460,9 @@ export async function promiseBestEffort<T>(promises: Promise<T>[]) {
460
export function pathEncode(s: string) {
461
return s.replace(/[:&#'"% ?\\]/g, escape) // escape() is not utf8, but we are encoding only ascii chars
462
}
463
-export function pathDecode(s: string) { return decodeURI(s).replace(/%23/g, '#') }
463
+export function pathDecode(s: string) {
464
+ return decodeURI(s).replace(/%23/g, '#')
465
+}
466
467
// run at a specific point in time, also solving the limit of setTimeout, which doesn't work with +32bit delays
468
export function runAt(ts: number, cb: Callback) {
src/middlewares.ts
+4
-1
@@ -3,7 +3,7 @@
3
import compress from 'koa-compress'
4
import Koa from 'koa'
5
import { API_URI, DEV } from './const'
6
-import { ALLOW_SESSION_IP_CHANGE, DAY, isLocalHost, netMatches, splitAt, stream2string, tryJson } from './misc'
6
+import { ALLOW_SESSION_IP_CHANGE, DAY, hasDirTraversal, isLocalHost, netMatches, splitAt, stream2string, try_, tryJson } from './misc'
7
import { Readable } from 'stream'
8
import { applyBlock } from './block'
9
import { Account, accountCanLogin, getAccount, getFromAccount } from './perm'
@@ -61,6 +61,9 @@ export const someSecurity: Koa.Middleware = (ctx, next) => {
61
62
if (!ctx.state.skipFilters && applyBlock(ctx.socket, ctx.ip))
63
return
64
+ const decodedPath = try_(() => decodeURI(ctx.path))
65
+ if (!decodedPath || hasDirTraversal(decodedPath))
66
+ return
67
68
if (ctx.get('X-Forwarded-For')
69
// we have some dev-proxies to ignore
tests/test.ts
+6
@@ -607,6 +607,12 @@ describe('admin', () => {
607
return entry.hits || 0
608
}
609
})
610
+ test('plugins.public traversal', async () => {
611
+ const id = 'list-uploader'
612
+ await reqApi('start_plugin', { id }, 200, { auth })()
613
+ return req(`/~/plugins/${id}/../../../tests/config.yaml`, 404)()
614
+ .finally(() => reqApi('stop_plugin', { id }, 200, { auth })())
615
+ })
616
})
617
618
function login(usr: string, pwd=password) {