fix: API get_file_details could reveal info about files without permission
Massimo Melina committed
Jan 16, 2026 at 17:57 UTC
a24d1949e570ff1b48a8ee9013fc05a40b654494
3 files changed
+5
-7
src/frontEndApis.ts
+2
-2
@@ -49,10 +49,10 @@ export const frontEndApis: ApiHandlers = {
49
if (typeof uri !== 'string')
50
return false // false means error
51
const node = await urlToNode(uri, ctx)
52
- if (!node)
52
+ if (!node || !hasPermission(node, 'can_see', ctx))
53
return false
54
let upload = node.source && await getUploadMeta(node.source).catch(() => undefined)
55
- if (!upload) return
55
+ if (!upload) return false
56
if (!isAdmin)
57
upload = _.omit(upload, 'ip')
58
return { upload }
src/vfs.ts
+1
-1
@@ -128,7 +128,7 @@ export async function urlToNode(
128
return urlToNode(rest, ctx, ret, resolveMissing)
129
if (ret.source)
130
if (!showHiddenFiles.get() && await isHiddenFile(ret.source))
131
- throw 'hiddenFile'
131
+ return
132
else if (await setIsFolder(ret) === undefined) { // undefined = not found on disk
133
if (!resolveMissing)
134
return
tests/test.ts
+2
-4
@@ -97,6 +97,7 @@ describe('basics', () => {
97
body: '{'
98
}))
99
test('file_details.missing', reqApi('get_file_details', { uris: ['/missing'] }, res => res?.details?.[0] === false))
100
+ test('file_details.hidden', reqApi('get_file_details', { uris: ['/tests/config.yaml'] }, res => res?.details?.[0] === false))
101
test('file_list.traversal', reqApi('get_file_list', { uri: '/f1/%2e%2e/for-admins' }, 404))
102
test('file_details.traversal', reqApi('get_file_details', { uris: ['/f1/%2e%2e/for-admins/alfa.txt'] }, res => res?.details?.[0] === false))
103
test('forbidden list', req('/cantListPage/page/', 403))
@@ -255,10 +256,7 @@ describe('after-login', () => {
256
const u = res?.details?.[0]?.upload
257
throwIf(!u?.ip ? 'ip' : u?.username !== username ? 'username' : '')
258
}))
258
- test('file_details.non-admin', reqApi('get_file_details', { uris: [UPLOAD_DEST] }, res => {
259
- const u = res?.details?.[0]?.upload
260
- throwIf(!u ? 'missing upload' : u?.ip ? 'ip' : u?.username !== username ? 'username' : '')
261
- }, { jar: {} }))
259
+ test('file_details.non-admin', reqApi('get_file_details', { uris: [UPLOAD_DEST] }, res => res?.details?.[0] === false, { jar: {} }))
260
test('upload but not delete', async () => {
261
const name = `cant-delete`
262
await mkdir(resolve(ROOT, name), { recursive: true })