mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-24 15:07:17 +00:00
test: pin that a leaked uuid buys no access
A masked share path hides which folder an item sits in; it was never the thing deciding who may open it. Nothing checked that at the route level, so the guarantee rested on unit tests of the resolver alone. Reads one shared file through its masked path, then tries the sibling four ways: the shared uuid with the sibling's name, the sibling's own uuid, a `..` back out of the root, and the owner's real path. Worth knowing about this one: it's mutation-checked. Removing the head !== root.name guard in sharePathMask.ts fails it with reachable: /testuser/55dd54c0…/share-http-1df8933e.txt. I verified that specifically because two tests I wrote earlier in this chunk passed with their guards broken — both were vacuous, and I deleted them rather than commit false assurance.
This commit is contained in:
@@ -67,6 +67,48 @@ describe('share endpoints over HTTP', () => {
|
||||
return { uid, path, name };
|
||||
};
|
||||
|
||||
// Masking hides where an item sits, not who may open it.
|
||||
it('will not let a masked path reach an unshared sibling', async () => {
|
||||
const owner = env.users.user;
|
||||
const recipient = env.users.other;
|
||||
const shared = await makeFile(owner);
|
||||
const secret = await makeFile(owner);
|
||||
|
||||
await post('/share', owner.token, {
|
||||
recipients: [recipient.username],
|
||||
items: [{ uid: shared.uid }],
|
||||
mode: 'read',
|
||||
});
|
||||
|
||||
const read = (path: string) =>
|
||||
fetch(
|
||||
`${env.apiOrigin}/read?${new URLSearchParams({ file: path })}`,
|
||||
{
|
||||
headers: {
|
||||
authorization: `Bearer ${recipient.token}`,
|
||||
origin: env.apiOrigin,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const masked = `/${owner.username}/${shared.uid}/${shared.name}`;
|
||||
expect((await read(masked)).status).toBe(200);
|
||||
|
||||
for (const attempt of [
|
||||
// The shared item's root, renamed to the sibling.
|
||||
`/${owner.username}/${shared.uid}/${secret.name}`,
|
||||
// The sibling's own uuid, as if it had leaked.
|
||||
`/${owner.username}/${secret.uid}/${secret.name}`,
|
||||
// Back out of the root the uuid vouched for.
|
||||
`/${owner.username}/${shared.uid}/${shared.name}/../${secret.name}`,
|
||||
// The owner's real path, named outright.
|
||||
secret.path,
|
||||
]) {
|
||||
const res = await read(attempt);
|
||||
expect(res.status, `reachable: ${attempt}`).not.toBe(200);
|
||||
}
|
||||
});
|
||||
|
||||
it('shares an item, lists it for the recipient, then revokes it', async () => {
|
||||
const owner = env.users.user;
|
||||
const recipient = env.users.other;
|
||||
|
||||
Reference in New Issue
Block a user