Skip to content

Commit 1ef7a4f

Browse files
Merge pull request from GHSA-44cc-43rp-5947
Co-authored-by: Frédéric Collonval <fcollonval@users.noreply.github.com> (cherry picked from commit 19bd9b9)
1 parent 0a75101 commit 1ef7a4f

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
@@ -210,7 +210,11 @@ namespace Private {
210210
await this._state.save(LAST_SAVE_ID, path);
211211

212212
// Navigate to new workspace.
213-
const url = URLExt.join(this._application, 'workspaces', id);
213+
const workspacesBase = URLExt.join(this._application, 'workspaces');
214+
const url = URLExt.join(workspacesBase, id);
215+
if (!workspacesBase.startsWith(url)) {
216+
throw new Error('Can only be used for workspaces');
217+
}
214218
if (this._router) {
215219
this._router.navigate(url, { hard: true });
216220
} else {

packages/hub-extension/src/index.ts

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

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

6471
const { commands } = app;
6572

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
@@ -161,6 +161,11 @@ namespace Private {
161161
const idsOnlyParam = idsOnly
162162
? URLExt.objectToQueryString({ ids_only: true })
163163
: '';
164-
return `${URLExt.join(base, SERVICE_SETTINGS_URL, id)}${idsOnlyParam}`;
164+
const settingsBase = URLExt.join(base, SERVICE_SETTINGS_URL);
165+
const result = URLExt.join(settingsBase, id);
166+
if (!result.startsWith(settingsBase)) {
167+
throw new Error('Can only be used for workspaces requests');
168+
}
169+
return `${result}${idsOnlyParam}`;
165170
}
166171
}

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
@@ -144,5 +144,9 @@ describe('session', () => {
144144
SessionAPI.shutdownSession(UUID.uuid4())
145145
).resolves.not.toThrow();
146146
});
147+
148+
it('should reject invalid on invalid id', async () => {
149+
await expect(SessionAPI.shutdownSession('../')).rejects.toThrow();
150+
});
147151
});
148152
});

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy