fix: API rename allowed moving a file in an existing subfolder on Windows

Massimo Melina committed Jan 17, 2026 at 10:27 UTC 1de04730773f3abd26885377c348d7e018ba7452
2 files changed +7 -2
src/frontEndApis.ts
+1 -1
@@ -86,7 +86,7 @@ export const frontEndApis: ApiHandlers = {
86 const node = await urlToNode(uri, ctx)
87 if (!node)
88 throw new ApiError(HTTP_NOT_FOUND)
89 - if (isRoot(node) || dest.includes('/') || hasDirTraversal(dest))
89 + if (isRoot(node) || !isValidFileName(dest))
90 throw new ApiError(HTTP_FORBIDDEN)
91 if (statusCodeForMissingPerm(node, 'can_delete', ctx))
92 throw new ApiError(ctx.status)
tests/test.ts
+6 -1
@@ -8,7 +8,7 @@ import _ from 'lodash'
8 import { findDefined, randomId, try_, tryJson, UPLOAD_TEMP_HASH, wait } from '../src/cross'
9 import { httpStream, stream2string, XRequestOptions } from '../src/util-http'
10 import { ThrottledStream, ThrottleGroup } from '../src/ThrottledStream'
11 -import { mkdir, rm, writeFile } from 'fs/promises'
11 +import { mkdir, rm, rename, writeFile } from 'fs/promises'
12 import { Readable } from 'stream'
13 /*
14 import { PORT, srv } from '../src'
@@ -332,6 +332,11 @@ describe('after-login', () => {
332 throw "partial file missing"
333 await reqUpload(UPLOAD_DEST, 200, Readable.from(BIG_CONTENT.slice(partial)), BIG_CONTENT.length, partial)()
334 })
335 + test('rename.backslash', async () => {
336 + await reqApi('rename', { uri: UPLOAD_DEST, dest: 'sub\\file' }, process.platform === 'win32' ? 403 : 200)()
337 + const d = resolve(ROOT, UPLOAD_DIR)
338 + await rename(resolve(d, 'sub\\file'), resolve(d, basename(UPLOAD_DEST))).catch(() => {})
339 + })
340 const renameTo = 'z'
341 test('rename.ok', reqApi('rename', { uri: UPLOAD_DEST, dest: renameTo }, 200))
342 test('delete.miss renamed', req(UPLOAD_DEST, 404, { method: 'delete' }))