Restrict raw beta file downloads to operators

This commit is contained in:
2026-10-05 23:27:41 -04:00
parent f8ac2436b5
commit d6ff59b129
3 changed files with 13 additions and 0 deletions
+1
View File
@@ -39,6 +39,7 @@ export function routePermission(routeId: string | null, method: string): RoutePe
// bytes, and unfiltered diagnostics may contain runtime credentials.
if (routeId === '/configs/[id]' || routeId === '/api/configs/[id]' || routeId === '/api/configs/inspect'
|| routeId === '/api/campaigns/[id]/file/[fileId]' || routeId === '/api/deployments/[id]/file/[fileId]'
|| routeId === '/api/betas/[id]/revisions/[revision]/files/[scriptId]'
|| routeId === '/api/servers/[name]/logs/[file]' || routeId === '/api/servers/[name]/telemetry-requests') return 'operator';
return reading ? 'viewer' : 'operator';
+1
View File
@@ -61,6 +61,7 @@ describe('management route permissions', () => {
it.each([
'/configs/[id]', '/api/configs/[id]', '/api/configs/inspect',
'/api/campaigns/[id]/file/[fileId]', '/api/deployments/[id]/file/[fileId]',
'/api/betas/[id]/revisions/[revision]/files/[scriptId]',
'/api/servers/[name]/logs/[file]', '/api/servers/[name]/telemetry-requests'
])('prevents viewer access to secret-bearing raw content at %s', route => {
for (const method of ['GET', 'HEAD']) {
+11
View File
@@ -125,6 +125,17 @@ describe('browser authentication guard', () => {
if (expectedStatus === 200) { expect(call.resolve).toHaveBeenCalledOnce(); expect(call.event.locals.user).toEqual(session.user); }
else expect(call.resolve).not.toHaveBeenCalled();
});
it.each(['GET', 'HEAD'])('restricts raw beta file %s requests to operators and administrators', async method => {
for (const role of ['viewer', 'operator', 'admin'] as const) {
signIn(role);
const call = requestFor('/api/betas/[id]/revisions/[revision]/files/[scriptId]', {
method, path: '/api/betas/1/revisions/1/files/1'
});
expect((await call.invoke()).status).toBe(role === 'viewer' ? 403 : 200);
if (role === 'viewer') expect(call.resolve).not.toHaveBeenCalled();
else expect(call.resolve).toHaveBeenCalledOnce();
}
});
it('requires temporary-password users to change it even if their role is administrator', async () => {
signIn('admin', true);
await expect(requestFor('/settings').invoke()).rejects.toMatchObject({ status: 303, location: '/account' });