@samitouri / QOSami-HFS / commits / a24d1949

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 })