Skip to content

Commit 4fd50ce

Browse files
authored
Merge pull request from GHSA-h4w9-6x78-8vrj
Signed-off-by: Michael Crenshaw <[email protected]>
1 parent 3fab7de commit 4fd50ce

File tree

2 files changed

+53
-2
lines changed

2 files changed

+53
-2
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import {ExternalLink, InvalidExternalLinkError} from './application-urls';
2+
3+
test('rejects malicious URLs', () => {
4+
expect(() => {
5+
const _ = new ExternalLink('javascript:alert("hi")');
6+
}).toThrowError(InvalidExternalLinkError);
7+
expect(() => {
8+
const _ = new ExternalLink('data:text/html;<h1>hi</h1>');
9+
}).toThrowError(InvalidExternalLinkError);
10+
});
11+
12+
test('allows absolute URLs', () => {
13+
expect(new ExternalLink('https://localhost:8080/applications').ref).toEqual('https://localhost:8080/applications');
14+
});
15+
16+
test('allows relative URLs', () => {
17+
// @ts-ignore
18+
window.location = new URL('https://localhost:8080/applications');
19+
expect(new ExternalLink('/applications').ref).toEqual('/applications');
20+
});

ui/src/app/applications/components/application-urls.tsx

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import {DropDownMenu} from 'argo-ui';
22
import * as React from 'react';
33

4-
class ExternalLink {
4+
export class InvalidExternalLinkError extends Error {
5+
constructor(message: string) {
6+
super(message);
7+
Object.setPrototypeOf(this, InvalidExternalLinkError.prototype);
8+
this.name = 'InvalidExternalLinkError';
9+
}
10+
}
11+
12+
export class ExternalLink {
513
public title: string;
614
public ref: string;
715

@@ -14,13 +22,36 @@ class ExternalLink {
1422
this.title = url;
1523
this.ref = url;
1624
}
25+
if (!ExternalLink.isValidURL(this.ref)) {
26+
throw new InvalidExternalLinkError('Invalid URL');
27+
}
28+
}
29+
30+
private static isValidURL(url: string): boolean {
31+
try {
32+
const parsedUrl = new URL(url);
33+
return parsedUrl.protocol !== 'javascript:' && parsedUrl.protocol !== 'data:';
34+
} catch (TypeError) {
35+
try {
36+
// Try parsing as a relative URL.
37+
const parsedUrl = new URL(url, window.location.origin);
38+
return parsedUrl.protocol !== 'javascript:' && parsedUrl.protocol !== 'data:';
39+
} catch (TypeError) {
40+
return false;
41+
}
42+
}
1743
}
1844
}
1945

2046
export const ApplicationURLs = ({urls}: {urls: string[]}) => {
2147
const externalLinks: ExternalLink[] = [];
2248
for (const url of urls || []) {
23-
externalLinks.push(new ExternalLink(url));
49+
try {
50+
const externalLink = new ExternalLink(url);
51+
externalLinks.push(externalLink);
52+
} catch (InvalidExternalLinkError) {
53+
continue;
54+
}
2455
}
2556

2657
// sorted alphabetically & links with titles first

0 commit comments

Comments
 (0)