soulseek: scope cached folder reads to their peer

Probing the live routes turned up that /browse/<peer>/dirs/<id>/files only checked
the owner, not the peer: dir ids are global, so asking for one peer's folder id
under a different peer's name returned the other peer's files with a 200. The UI
always sends a matching pair so nothing misbehaved, but the URL was asserting a
relationship the query never verified — a mismatched or stale request would show
the wrong peer's contents rather than a 404.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 00:27:39 +00:00
co-authored by Claude Opus 4.8
parent 1ac5bffb6c
commit 80ca9aa9b6
2 changed files with 17 additions and 3 deletions
@@ -192,12 +192,26 @@ export async function getSoulseekBrowseDirs(params: BrowseDirPageParams): Promis
}
/** One cached folder's files. Scoped by user so a guessed dir id can't read another account's cache. */
export async function getSoulseekBrowseDirFiles(userId: number, dirId: number): Promise<BrowsedFile[] | null> {
type DirFilesParams = { userId: number; username: string; dirId: number };
// Joined and scoped by BOTH owner and peer: a dir id alone would happily return another peer's folder
// (ids are global), so the username in the request URL has to be part of the predicate, not decoration.
export async function getSoulseekBrowseDirFiles({
userId,
username,
dirId,
}: DirFilesParams): Promise<BrowsedFile[] | null> {
const [row] = await db
.select({ files: soulseekBrowseDirs.files })
.from(soulseekBrowseDirs)
.innerJoin(soulseekBrowseSnapshots, eq(soulseekBrowseDirs.snapshotId, soulseekBrowseSnapshots.id))
.where(and(eq(soulseekBrowseDirs.id, dirId), eq(soulseekBrowseSnapshots.userId, userId)))
.where(
and(
eq(soulseekBrowseDirs.id, dirId),
eq(soulseekBrowseSnapshots.userId, userId),
eq(soulseekBrowseSnapshots.username, username),
),
)
.limit(1);
return row ? (row.files as BrowsedFile[]) : null;
}
+1 -1
View File
@@ -127,7 +127,7 @@ async function handleBrowse({ req, url, userId, segments }: BrowseRouteParams):
if (req.method !== 'GET') return methodNotAllowed();
const dirId = Number(segments[3]);
if (!Number.isInteger(dirId) || dirId <= 0) return badRequest('invalid directory id');
const files = await getSoulseekBrowseDirFiles(userId, dirId);
const files = await getSoulseekBrowseDirFiles({ userId, username, dirId });
return files ? Response.json(files) : notFound();
}