diff --git a/.changeset/red-pugs-tap.md b/.changeset/red-pugs-tap.md
new file mode 100644
index 0000000000..85da2b7990
--- /dev/null
+++ b/.changeset/red-pugs-tap.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-components': patch
+---
+
+Updated Link URL validation to be more strict.
diff --git a/packages/core-components/src/components/Link/Link.test.tsx b/packages/core-components/src/components/Link/Link.test.tsx
index 3538b79052..09e1293726 100644
--- a/packages/core-components/src/components/Link/Link.test.tsx
+++ b/packages/core-components/src/components/Link/Link.test.tsx
@@ -173,4 +173,13 @@ describe('', () => {
});
});
});
+
+ it('throws an error when attempting to link to script code', () => {
+ expect(() =>
+ // eslint-disable-next-line no-script-url
+ render(wrapInTestApp(Script)),
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"Link component rejected javascript: URL as a security precaution"`,
+ );
+ });
});
diff --git a/packages/core-components/src/components/Link/Link.tsx b/packages/core-components/src/components/Link/Link.tsx
index 440fcb811f..bb753e10bd 100644
--- a/packages/core-components/src/components/Link/Link.tsx
+++ b/packages/core-components/src/components/Link/Link.tsx
@@ -55,6 +55,11 @@ const useStyles = makeStyles(
export const isExternalUri = (uri: string) => /^([a-z+.-]+):/.test(uri);
+// See https://github.com/facebook/react/blob/f0cf832e1d0c8544c36aa8b310960885a11a847c/packages/react-dom-bindings/src/shared/sanitizeURL.js
+const scriptProtocolPattern =
+ // eslint-disable-next-line no-control-regex
+ /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*\:/i;
+
export type LinkProps = Omit &
Omit & {
to: string;
@@ -144,6 +149,12 @@ export const Link = React.forwardRef(
const external = isExternalUri(to);
const newWindow = external && !!/^https?:/.exec(to);
+ if (scriptProtocolPattern.test(to)) {
+ throw new Error(
+ 'Link component rejected javascript: URL as a security precaution',
+ );
+ }
+
const handleClick = (event: React.MouseEvent) => {
onClick?.(event);
if (!noTrack) {