fix: (regression 3.0.2) some illegal paths were not blocked
Massimo Melina committed
Apr 1, 2026 at 16:37 UTC
fe02718e946219ddccac0a57b99a5b0e76dd6435
3 files changed
+13
-2
src/cross.ts
+3
-1
@@ -464,7 +464,9 @@ export async function promiseBestEffort<T>(promises: Promise<T>[]) {
464
export function pathEncode(s: string, all=false) {
465
return all ? encodeURI(s).replace(/#/g, escape) : s.replace(/[:&#'"% ?\\]/g, escape) // escape() is not utf8, but we are encoding only ascii chars
466
}
467
-export function pathDecode(s: string) { return decodeURI(s).replace(/%23/g, '#') }
467
+export function pathDecode(s: string) {
468
+ return decodeURI(s).replace(/%23/g, '#')
469
+}
470
471
// run at a specific point in time, also solving the limit of setTimeout, which doesn't work with +32bit delays
472
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'
@@ -62,6 +62,9 @@ export const someSecurity: Koa.Middleware = (ctx, next) => {
62
63
if (!ctx.state.skipFilters && applyBlock(ctx.socket, ctx.ip))
64
return
65
+ const decodedPath = try_(() => decodeURI(ctx.path))
66
+ if (!decodedPath || hasDirTraversal(decodedPath))
67
+ return
68
69
if (ctx.get('X-Forwarded-For')
70
// we have some dev-proxies to ignore
tests/test.ts
+6
@@ -1026,6 +1026,12 @@ describe('admin', () => {
1026
return entry.hits || 0
1027
}
1028
})
1029
+ test('plugins.public traversal', async () => {
1030
+ const id = 'list-uploader'
1031
+ await reqApi('start_plugin', { id }, 200, { auth })()
1032
+ return req(`/~/plugins/${id}/../../../tests/config.yaml`, 404)()
1033
+ .finally(() => reqApi('stop_plugin', { id }, 200, { auth })())
1034
+ })
1035
})
1036
1037
function login(usr: string, pwd=password) {