Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/backend/services/fs/FSService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3367,6 +3367,26 @@ describe('FSService permission rules', () => {
expect(error.legacyCode).toBe('subject_does_not_exist');
});

// The old raw `split('fs:')` dropped the mode and stripped the trailing `fs`, resolving this to a bare `fs:<home_uuid>` that subsumes every mode.
it('does not let an embedded fs: escalate a scoped grant to a bare one', async () => {
const error = await caught(() =>
server.services.permission.rewritePermission(
`fs:${user.home}fs:junk:read`,
),
);
expect(error.statusCode).toBe(404);
expect(error.legacyCode).toBe('subject_does_not_exist');
});

it('keeps the mode when a later component contains fs:', async () => {
// `fs` in the mode position is data, not a delimiter: the path resolves and the mode is preserved, never collapsed to bare.
await expect(
server.services.permission.rewritePermission(
`fs:${file.path}:fs:read`,
),
).resolves.toBe(`fs:${file.uuid}:fs:read`);
});

it('leaves uuid-addressed and non-fs permissions untouched', async () => {
await expect(
server.services.permission.rewritePermission(
Expand Down
17 changes: 10 additions & 7 deletions src/backend/services/fs/FSService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,24 +182,27 @@ export class FSService extends PuterService {
!permission.startsWith(`${MANAGE_PERM_PREFIX}:fs:`)
)
return false;
const [, specifier] = permission.split('fs:');
// Parse on component boundaries: a path can contain `fs:` (e.g. a home dir named `…fs`), which a raw split mistakes for the mode delimiter.
const parts = PermissionUtil.split(permission);
const fsIndex = parts[0] === MANAGE_PERM_PREFIX ? 1 : 0;
const specifier = parts[fsIndex + 1];
return Boolean(specifier && specifier.startsWith('/'));
},
rewrite: async (permission: string): Promise<string> => {
const [manageOpt, pathPerm] = permission.split('fs:');
const parts = PermissionUtil.split(pathPerm);
const path = parts[0];
const rest = parts.slice(1);
const parts = PermissionUtil.split(permission);
const hasManage = parts[0] === MANAGE_PERM_PREFIX;
const fsIndex = hasManage ? 1 : 0;
const path = parts[fsIndex + 1];
const rest = parts.slice(fsIndex + 2);
if (!path) return permission;
const entry = await fsEntryStore.getEntryByPath(path);
if (!entry) {
throw new HttpError(404, `Entry not found: path=${path}`, {
legacyCode: 'subject_does_not_exist',
});
}
const manage = manageOpt.replace(':', '');
const joined = PermissionUtil.join('fs', entry.uuid, ...rest);
return manage ? `${manage}:${joined}` : joined;
return hasManage ? `${MANAGE_PERM_PREFIX}:${joined}` : joined;
},
});

Expand Down
Loading