Skip to content

Commit

Permalink
Merge pull request from GHSA-44cc-43rp-5947
Browse files Browse the repository at this point in the history
Co-authored-by: Frédéric Collonval <[email protected]>
  • Loading branch information
krassowski and fcollonval authored Jan 19, 2024
1 parent f7cc20a commit 19bd9b9
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 9 deletions.
6 changes: 5 additions & 1 deletion packages/apputils-extension/src/workspacesplugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,11 @@ namespace Private {
await this._state.save(LAST_SAVE_ID, path);

// Navigate to new workspace.
const url = URLExt.join(this._application, 'workspaces', id);
const workspacesBase = URLExt.join(this._application, 'workspaces');
const url = URLExt.join(workspacesBase, id);
if (!workspacesBase.startsWith(url)) {
throw new Error('Can only be used for workspaces');
}
if (this._router) {
this._router.navigate(url, { hard: true });
} else {
Expand Down
13 changes: 10 additions & 3 deletions packages/hub-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,16 @@ function activateHubExtension(
});

// If hubServerName is set, use JupyterHub 1.0 URL.
const restartUrl = hubServerName
? hubHost + URLExt.join(hubPrefix, 'spawn', hubUser, hubServerName)
: hubHost + URLExt.join(hubPrefix, 'spawn');
const spawnBase = URLExt.join(hubPrefix, 'spawn');
let restartUrl: string;
if (hubServerName) {
const suffix = URLExt.join(spawnBase, hubUser, hubServerName);
if (!suffix.startsWith(spawnBase)) {
throw new Error('Can only be used for spawn requests');
}
restartUrl = hubHost + suffix;
}
restartUrl = hubHost + spawnBase;

const { commands } = app;

Expand Down
7 changes: 6 additions & 1 deletion packages/services/src/session/restapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ export async function listRunning(
* Get a session url.
*/
export function getSessionUrl(baseUrl: string, id: string): string {
return URLExt.join(baseUrl, SESSION_SERVICE_URL, id);
const servicesBase = URLExt.join(baseUrl, SESSION_SERVICE_URL);
const result = URLExt.join(servicesBase, id);
if (!result.startsWith(servicesBase)) {
throw new Error('Can only be used for services requests');
}
return result;
}

/**
Expand Down
7 changes: 6 additions & 1 deletion packages/services/src/setting/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ namespace Private {
const idsOnlyParam = idsOnly
? URLExt.objectToQueryString({ ids_only: true })
: '';
return `${URLExt.join(base, SERVICE_SETTINGS_URL, id)}${idsOnlyParam}`;
const settingsBase = URLExt.join(base, SERVICE_SETTINGS_URL);
const result = URLExt.join(settingsBase, id);
if (!result.startsWith(settingsBase)) {
throw new Error('Can only be used for workspaces requests');
}
return `${result}${idsOnlyParam}`;
}
}
6 changes: 5 additions & 1 deletion packages/services/src/terminal/restapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,11 @@ export async function shutdownTerminal(
settings: ServerConnection.ISettings = ServerConnection.makeSettings()
): Promise<void> {
Private.errorIfNotAvailable();
const url = URLExt.join(settings.baseUrl, TERMINAL_SERVICE_URL, name);
const workspacesBase = URLExt.join(settings.baseUrl, TERMINAL_SERVICE_URL);
const url = URLExt.join(workspacesBase, name);
if (!url.startsWith(workspacesBase)) {
throw new Error('Can only be used for terminal requests');
}
const init = { method: 'DELETE' };
const response = await ServerConnection.makeRequest(url, init, settings);
if (response.status === 404) {
Expand Down
7 changes: 6 additions & 1 deletion packages/services/src/workspace/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ namespace Private {
* Get the url for a workspace.
*/
export function url(base: string, id: string): string {
return URLExt.join(base, SERVICE_WORKSPACES_URL, id);
const workspacesBase = URLExt.join(base, SERVICE_WORKSPACES_URL);
const result = URLExt.join(workspacesBase, id);
if (!result.startsWith(workspacesBase)) {
throw new Error('Can only be used for workspaces requests');
}
return result;
}
}
4 changes: 4 additions & 0 deletions packages/services/test/session/session.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,9 @@ describe('session', () => {
SessionAPI.shutdownSession(UUID.uuid4())
).resolves.not.toThrow();
});

it('should reject invalid on invalid id', async () => {
await expect(SessionAPI.shutdownSession('../')).rejects.toThrow();
});
});
});
20 changes: 20 additions & 0 deletions packages/services/test/setting/manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ describe('setting', () => {

expect((await manager.fetch(id)).id).toBe(id);
});

it('should reject on invalid id', async () => {
const id = '../';

const callback = async () => {
await manager.fetch(id);
};
await expect(callback).rejects.toThrow();
});
});

describe('#save()', () => {
Expand All @@ -64,6 +73,17 @@ describe('setting', () => {
await manager.save(id, raw);
expect(JSON.parse((await manager.fetch(id)).raw).theme).toBe(theme);
});

it('should reject on invalid id', async () => {
const id = '../';
const theme = 'Foo Theme';
const raw = `{"theme": "${theme}"}`;

const callback = async () => {
await manager.save(id, raw);
};
await expect(callback).rejects.toThrow();
});
});
});
});
18 changes: 18 additions & 0 deletions packages/services/test/workspace/manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ describe('workspace', () => {
expect((await manager.fetch(id)).metadata.id).toBe(id);
await manager.remove(id);
});

it('should reject on invalid id', async () => {
const id = '../';

const callback = async () => {
await manager.fetch(id);
};
await expect(callback).rejects.toThrow();
});
});

describe('#list()', () => {
Expand Down Expand Up @@ -87,6 +96,15 @@ describe('workspace', () => {
expect((await manager.fetch(id)).metadata.id).toBe(id);
await manager.remove(id);
});

it('should reject on invalid id', async () => {
const id = '../';

const callback = async () => {
await manager.save(id, { data: {}, metadata: { id } });
};
await expect(callback).rejects.toThrow();
});
});
});
});
6 changes: 5 additions & 1 deletion packages/translation/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ export async function requestTranslationsAPI<T>(
const settings = serverSettings ?? ServerConnection.makeSettings();
translationsUrl =
translationsUrl || `${settings.appUrl}/${TRANSLATIONS_SETTINGS_URL}`;
const requestUrl = URLExt.join(settings.baseUrl, translationsUrl, locale);
const translationsBase = URLExt.join(settings.baseUrl, translationsUrl);
const requestUrl = URLExt.join(translationsBase, locale);
if (!requestUrl.startsWith(translationsBase)) {
throw new Error('Can only be used for translations requests');
}
let response: Response;
try {
response = await ServerConnection.makeRequest(requestUrl, init, settings);
Expand Down

0 comments on commit 19bd9b9

Please sign in to comment.