From 321cac2e80e4d54bcb562427aa2bfd7996da3e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sat, 29 Aug 2026 09:11:45 +0100 Subject: [PATCH] the folder param is relative to the base, and stops leaking an email MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `?media=/officerdev/data/tatudoemcampo@gmail.com/FofosWebsite/media/originals/ABOUT+US` became `?media=ABOUT+US`. the length is the smaller half. the absolute form put THE ACCOUNT'S EMAIL ADDRESS in every link — so a URL sent to a colleague, or pasted into a chat, carried the owner's address and the shape of their disk with it. relative, it says nothing about the machine or who owns it. it also makes the jail hold BY CONSTRUCTION rather than by comparison. the value is joined to `basePath`, so there is no "outside" left to check for — only `..` could climb out, and a `..` segment is refused outright. the previous version compared a caller-supplied absolute path against a prefix, which is the weaker shape and the one that goes wrong when somebody later "simplifies" it. round-trip and tampering both exercised against the real base path before committing: base, a folder with a space, a nested folder with a non-ascii name, then `../../etc`, `GALLERY/../../..` and `/etc/passwd` — refused, refused, contained. Co-Authored-By: Claude Opus 5 Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043 --- .../FileBrowserApp/useFileBrowserApp.ts | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/useFileBrowserApp.ts b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/useFileBrowserApp.ts index 1bd3ed45..590717d2 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/useFileBrowserApp.ts +++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/useFileBrowserApp.ts @@ -86,17 +86,22 @@ export const useFileBrowserApp = ( Three sources, mutually exclusive by construction: the pathname for /files, a named query param for an embedded browser, and component state for an ordinary panel. - A param naming a folder OUTSIDE the jail is ignored rather than obeyed — it arrives from a URL - somebody may have edited, and `basePath` is the whole promise of an embedded browser. + ── The param is RELATIVE to the base, and that is not only for tidiness ── + + An absolute path put the account's own email in every link — `?media=/officerdev/data/ + someone@gmail.com/...` — so a URL sent to a colleague carried the owner's address with it. Relative, + the same folder is `?media=ABOUT+US`: shorter, and it says nothing about the machine or the account. + + It is also what makes the jail hold by construction rather than by comparison. The value is JOINED + to `basePath`, so there is no "outside" to check for — only `..` could climb out, and a segment of + `..` is refused outright. */ - const paramPath = pathParam ? searchParams.get(pathParam) : null; - const currentPath = urlPath - ? folderFromPathname(location.pathname) - : pathParam - ? paramPath && paramPath.startsWith(basePath) - ? paramPath - : basePath - : localPath; + const paramRelative = pathParam ? searchParams.get(pathParam) : null; + const paramPath = + paramRelative && !paramRelative.split('/').includes('..') + ? `${basePath.replace(/\/$/, '')}/${paramRelative.replace(/^\/+/, '')}` + : null; + const currentPath = urlPath ? folderFromPathname(location.pathname) : pathParam ? (paramPath ?? basePath) : localPath; const setCurrentPath = (path: string) => { // Leaving a folder closes what was open in it. A pane shows a file from the folder you were in, so // carrying it into the next one leaves the two disagreeing — the listing says one place, the pane @@ -109,8 +114,9 @@ export const useFileBrowserApp = ( const next = new URLSearchParams(withoutViewerParams(prev.toString())); // The base folder is the ABSENCE of the param, so a link to the top of an embedded browser is // the plain screen URL and nothing accumulates for having gone nowhere. - if (path === basePath) next.delete(pathParam); - else next.set(pathParam, path); + const relative = path.startsWith(basePath) ? path.slice(basePath.replace(/\/$/, '').length + 1) : ''; + if (!relative) next.delete(pathParam); + else next.set(pathParam, relative); return next; }, // Browsing folders is not navigation anybody wants twenty back-button presses of.