Skip to content

Commit fccd83d

Browse files
Merge pull request from GHSA-44cc-43rp-5947
Co-authored-by: Frédéric Collonval <[email protected]> (cherry picked from commit 19bd9b9)
1 parent 3b6b789 commit fccd83d

File tree

10 files changed

+85
-9
lines changed

10 files changed

+85
-9
lines changed

packages/apputils-extension/src/workspacesplugin.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,11 @@ namespace Private {
218218
await this._state.save(LAST_SAVE_ID, path);
219219

220220
// Navigate to new workspace.
221-
const url = URLExt.join(this._application, 'workspaces', id);
221+
const workspacesBase = URLExt.join(this._application, 'workspaces');
222+
const url = URLExt.join(workspacesBase, id);
223+
if (!workspacesBase.startsWith(url)) {
224+
throw new Error('Can only be used for workspaces');
225+
}
222226
if (this._router) {
223227
this._router.navigate(url, { hard: true });
224228
} else {

packages/hub-extension/src/index.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,16 @@ function activateHubExtension(
5656
});
5757

5858
// If hubServerName is set, use JupyterHub 1.0 URL.
59-
const restartUrl = hubServerName
60-
? hubHost + URLExt.join(hubPrefix, 'spawn', hubUser, hubServerName)
61-
: hubHost + URLExt.join(hubPrefix, 'spawn');
59+
const spawnBase = URLExt.join(hubPrefix, 'spawn');
60+
let restartUrl: string;
61+
if (hubServerName) {
62+
const suffix = URLExt.join(spawnBase, hubUser, hubServerName);
63+
if (!suffix.startsWith(spawnBase)) {
64+
throw new Error('Can only be used for spawn requests');
65+
}
66+
restartUrl = hubHost + suffix;
67+
}
68+
restartUrl = hubHost + spawnBase;
6269

6370
const { commands } = app;
6471

packages/services/src/session/restapi.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,12 @@ export async function listRunning(
4242
* Get a session url.
4343
*/
4444
export function getSessionUrl(baseUrl: string, id: string): string {
45-
return URLExt.join(baseUrl, SESSION_SERVICE_URL, id);
45+
const servicesBase = URLExt.join(baseUrl, SESSION_SERVICE_URL);
46+
const result = URLExt.join(servicesBase, id);
47+
if (!result.startsWith(servicesBase)) {
48+
throw new Error('Can only be used for services requests');
49+
}
50+
return result;
4651
}
4752

4853
/**

packages/services/src/setting/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ namespace Private {
149149
* Get the url for a plugin's settings.
150150
*/
151151
export function url(base: string, id: string): string {
152-
return URLExt.join(base, SERVICE_SETTINGS_URL, id);
152+
const settingsBase = URLExt.join(base, SERVICE_SETTINGS_URL);
153+
const result = URLExt.join(settingsBase, id);
154+
if (!result.startsWith(settingsBase)) {
155+
throw new Error('Can only be used for workspaces requests');
156+
}
157+
return result;
153158
}
154159
}

packages/services/src/terminal/restapi.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,11 @@ export async function shutdownTerminal(
101101
settings: ServerConnection.ISettings = ServerConnection.makeSettings()
102102
): Promise<void> {
103103
Private.errorIfNotAvailable();
104-
const url = URLExt.join(settings.baseUrl, TERMINAL_SERVICE_URL, name);
104+
const workspacesBase = URLExt.join(settings.baseUrl, TERMINAL_SERVICE_URL);
105+
const url = URLExt.join(workspacesBase, name);
106+
if (!url.startsWith(workspacesBase)) {
107+
throw new Error('Can only be used for terminal requests');
108+
}
105109
const init = { method: 'DELETE' };
106110
const response = await ServerConnection.makeRequest(url, init, settings);
107111
if (response.status === 404) {

packages/services/src/workspace/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ namespace Private {
178178
* Get the url for a workspace.
179179
*/
180180
export function url(base: string, id: string): string {
181-
return URLExt.join(base, SERVICE_WORKSPACES_URL, id);
181+
const workspacesBase = URLExt.join(base, SERVICE_WORKSPACES_URL);
182+
const result = URLExt.join(workspacesBase, id);
183+
if (!result.startsWith(workspacesBase)) {
184+
throw new Error('Can only be used for workspaces requests');
185+
}
186+
return result;
182187
}
183188
}

packages/services/test/session/session.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,5 +157,9 @@ describe('session', () => {
157157
it('should handle a 404 status', () => {
158158
return SessionAPI.shutdownSession(UUID.uuid4());
159159
});
160+
161+
it('should reject invalid on invalid id', async () => {
162+
await expect(SessionAPI.shutdownSession('../')).rejects.toThrow();
163+
});
160164
});
161165
});

packages/services/test/setting/manager.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ describe('setting', () => {
5353

5454
expect((await manager.fetch(id)).id).toBe(id);
5555
});
56+
57+
it('should reject on invalid id', async () => {
58+
const id = '../';
59+
60+
const callback = async () => {
61+
await manager.fetch(id);
62+
};
63+
await expect(callback).rejects.toThrow();
64+
});
5665
});
5766

5867
describe('#save()', () => {
@@ -64,6 +73,17 @@ describe('setting', () => {
6473
await manager.save(id, raw);
6574
expect(JSON.parse((await manager.fetch(id)).raw).theme).toBe(theme);
6675
});
76+
77+
it('should reject on invalid id', async () => {
78+
const id = '../';
79+
const theme = 'Foo Theme';
80+
const raw = `{"theme": "${theme}"}`;
81+
82+
const callback = async () => {
83+
await manager.save(id, raw);
84+
};
85+
await expect(callback).rejects.toThrow();
86+
});
6787
});
6888
});
6989
});

packages/services/test/workspace/manager.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@ describe('workspace', () => {
5555
expect((await manager.fetch(id)).metadata.id).toBe(id);
5656
await manager.remove(id);
5757
});
58+
59+
it('should reject on invalid id', async () => {
60+
const id = '../';
61+
62+
const callback = async () => {
63+
await manager.fetch(id);
64+
};
65+
await expect(callback).rejects.toThrow();
66+
});
5867
});
5968

6069
describe('#list()', () => {
@@ -87,6 +96,15 @@ describe('workspace', () => {
8796
expect((await manager.fetch(id)).metadata.id).toBe(id);
8897
await manager.remove(id);
8998
});
99+
100+
it('should reject on invalid id', async () => {
101+
const id = '../';
102+
103+
const callback = async () => {
104+
await manager.save(id, { data: {}, metadata: { id } });
105+
};
106+
await expect(callback).rejects.toThrow();
107+
});
90108
});
91109
});
92110
});

packages/translation/src/server.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ export async function requestTranslationsAPI<T>(
2727
const settings = serverSettings ?? ServerConnection.makeSettings();
2828
translationsUrl =
2929
translationsUrl || `${settings.appUrl}/${TRANSLATIONS_SETTINGS_URL}/`;
30-
const requestUrl = URLExt.join(settings.baseUrl, translationsUrl, locale);
30+
const translationsBase = URLExt.join(settings.baseUrl, translationsUrl);
31+
const requestUrl = URLExt.join(translationsBase, locale);
32+
if (!requestUrl.startsWith(translationsBase)) {
33+
throw new Error('Can only be used for translations requests');
34+
}
3135
let response: Response;
3236
try {
3337
response = await ServerConnection.makeRequest(requestUrl, init, settings);

0 commit comments

Comments
 (0)