Repository: sveltejs/svelte (JavaScript)
Date: 2021-05-03 18:02:03+00:00
Task type: Security

Issue:
Security hints
**Is your feature request related to a problem? Please describe.**

Svelte is really helpful for giving hints such as a11y (eg: `A11y: '' is not a valid href attribute`). 
My point here would be to do kind of the same thing with basic security rules. 

For instance, one of a security rule would be to not make a `target=_blank` on a link without the attributes `rel="noreferrer noopener"`. 

![image](https://user-images.githubusercontent.com/23191482/114418925-42f90780-9bb3-11eb-853a-33eafd810394.png)



Issue discussion:

Solution patch:
diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts
--- a/src/compiler/compile/nodes/Element.ts
+++ b/src/compiler/compile/nodes/Element.ts
@@ -394,6 +394,26 @@ export default class Element extends Node {
 			const href_attribute = attribute_map.get('href') || attribute_map.get('xlink:href');
 			const id_attribute = attribute_map.get('id');
 			const name_attribute = attribute_map.get('name');
+			const target_attribute = attribute_map.get('target');
+
+			if (target_attribute && target_attribute.get_static_value() === '_blank' && href_attribute) {
+				const href_static_value = href_attribute.get_static_value() ? href_attribute.get_static_value().toLowerCase() : null;
+
+				if (href_static_value === null || href_static_value.match(/^(https?:)?\/\//i)) {
+					const rel = attribute_map.get('rel');
+					const rel_values = rel ? rel.get_static_value().split(' ') : [];
+					const expected_values = ['noreferrer'];
+
+					expected_values.forEach(expected_value => {
+						if (!rel || rel && rel_values.indexOf(expected_value) < 0) {
+							component.warn(this, {
+								code: `security-anchor-rel-${expected_value}`,
+								message: `Security: Anchor with "target=_blank" should have rel attribute containing the value "${expected_value}"`
+							});
+						}
+					});
+				}
+			}
 
 			if (href_attribute) {
 				const href_value = href_attribute.get_static_value();


Test patch:
diff --git a/test/validator/samples/security-anchor-rel-noreferrer/input.svelte b/test/validator/samples/security-anchor-rel-noreferrer/input.svelte
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/security-anchor-rel-noreferrer/input.svelte
@@ -0,0 +1,31 @@
+<a href="https://svelte.dev" target="_blank">svelte website (invalid)</a>
+<a href="https://svelte.dev" target="_blank" rel="">svelte website (invalid)</a>
+<a href="https://svelte.dev" target="_blank" rel="noopener">svelte website (invalid)</a>
+<a href={'https://svelte.dev'} target="_blank">svelte website (invalid)</a>
+<a href={'https://svelte.dev'} target="_blank" rel="">svelte website (invalid)</a>
+<a href={'https://svelte.dev'} target="_blank" rel="noopener">svelte website (invalid)</a>
+<a href="//svelte.dev" target="_blank">svelte website (invalid)</a>
+<a href="//svelte.dev" target="_blank" rel="">svelte website (invalid)</a>
+<a href="//svelte.dev" target="_blank" rel="noopener">svelte website (invalid)</a>
+<a href="http://svelte.dev" target="_blank">svelte website (invalid)</a>
+<a href="http://svelte.dev" target="_blank" rel="">svelte website (invalid)</a>
+<a href="http://svelte.dev" target="_blank" rel="noopener">svelte website (invalid)</a>
+<a href="HTTP://svelte.dev" target="_blank">svelte website (invalid)</a>
+<a href="HTTP://svelte.dev" target="_blank" rel="">svelte website (invalid)</a>
+<a href="HTTP://svelte.dev" target="_blank" rel="noopener">svelte website (invalid)</a>
+<a href={'HTTPS://svelte.dev'} target="_blank">svelte website (invalid)</a>
+<a href={'HTTPS://svelte.dev'} target="_blank" rel="">svelte website (invalid)</a>
+<a href={'HTTPS://svelte.dev'} target="_blank" rel="noopener">svelte website (invalid)</a>
+<a href="same-host" target="_blank">Same host (valid)</a>
+<a href="same-host" target="_blank" rel="">Same host (valid)</a>
+<a href="same-host" target="_blank" rel="noopener">Same host (valid)</a>
+<a href="http://svelte.dev" target="_blank" rel="noreferrer">svelte website (valid)</a>
+<a href="http://svelte.dev" target="_blank" rel="noreferrer noopener">svelte website (valid)</a>
+<a href="HTTP://svelte.dev" target="_blank" rel="noreferrer">svelte website (valid)</a>
+<a href="HTTP://svelte.dev" target="_blank" rel="noreferrer noopener">svelte website (valid)</a>
+<a href="https://svelte.dev" target="_blank" rel="noreferrer">svelte website (valid)</a>
+<a href="https://svelte.dev" target="_blank" rel="noreferrer noopener">svelte website (valid)</a>
+<a href="HTTPS://svelte.dev" target="_blank" rel="noreferrer">svelte website (valid)</a>
+<a href="HTTPS://svelte.dev" target="_blank" rel="noreferrer noopener">svelte website (valid)</a>
+<a href="//svelte.dev" target="_blank" rel="noreferrer">svelte website (valid)</a>
+<a href="//svelte.dev" target="_blank" rel="noreferrer noopener">svelte website (valid)</a>
\ No newline at end of file
diff --git a/test/validator/samples/security-anchor-rel-noreferrer/warnings.json b/test/validator/samples/security-anchor-rel-noreferrer/warnings.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/security-anchor-rel-noreferrer/warnings.json
@@ -0,0 +1,272 @@
+[
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 73,
+			"column": 73,
+			"line": 1
+		},
+		"pos": 0,
+		"start": {
+			"character": 0,
+			"column": 0,
+			"line": 1
+		}
+	},
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 154,
+			"column": 80,
+			"line": 2
+		},
+		"pos": 74,
+		"start": {
+			"character": 74,
+			"column": 0,
+			"line": 2
+		}
+	},
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 243,
+			"column": 88,
+			"line": 3
+		},
+		"pos": 155,
+		"start": {
+			"character": 155,
+			"column": 0,
+			"line": 3
+		}
+	},
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 319,
+			"column": 75,
+			"line": 4
+		},
+		"pos": 244,
+		"start": {
+			"character": 244,
+			"column": 0,
+			"line": 4
+		}
+	},
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 402,
+			"column": 82,
+			"line": 5
+		},
+		"pos": 320,
+		"start": {
+			"character": 320,
+			"column": 0,
+			"line": 5
+		}
+	},
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 493,
+			"column": 90,
+			"line": 6
+		},
+		"pos": 403,
+		"start": {
+			"character": 403,
+			"column": 0,
+			"line": 6
+		}
+	},
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 561,
+			"column": 67,
+			"line": 7
+		},
+		"pos": 494,
+		"start": {
+			"character": 494,
+			"column": 0,
+			"line": 7
+		}
+	},
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 636,
+			"column": 74,
+			"line": 8
+		},
+		"pos": 562,
+		"start": {
+			"character": 562,
+			"column": 0,
+			"line": 8
+		}
+	},
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 719,
+			"column": 82,
+			"line": 9
+		},
+		"pos": 637,
+		"start": {
+			"character": 637,
+			"column": 0,
+			"line": 9
+		}
+	},
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 792,
+			"column": 72,
+			"line": 10
+		},
+		"pos": 720,
+		"start": {
+			"character": 720,
+			"column": 0,
+			"line": 10
+		}
+	},
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 872,
+			"column": 79,
+			"line": 11
+		},
+		"pos": 793,
+		"start": {
+			"character": 793,
+			"column": 0,
+			"line": 11
+		}
+	},
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 960,
+			"column": 87,
+			"line": 12
+		},
+		"pos": 873,
+		"start": {
+			"character": 873,
+			"column": 0,
+			"line": 12
+		}
+	},
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 1033,
+			"column": 72,
+			"line": 13
+		},
+		"pos": 961,
+		"start": {
+			"character": 961,
+			"column": 0,
+			"line": 13
+		}
+	},
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 1113,
+			"column": 79,
+			"line": 14
+		},
+		"pos": 1034,
+		"start": {
+			"character": 1034,
+			"column": 0,
+			"line": 14
+		}
+	},
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 1201,
+			"column": 87,
+			"line": 15
+		},
+		"pos": 1114,
+		"start": {
+			"character": 1114,
+			"column": 0,
+			"line": 15
+		}
+	},
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 1277,
+			"column": 75,
+			"line": 16
+		},
+		"pos": 1202,
+		"start": {
+			"character": 1202,
+			"column": 0,
+			"line": 16
+		}
+	},
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 1360,
+			"column": 82,
+			"line": 17
+		},
+		"pos": 1278,
+		"start": {
+			"character": 1278,
+			"column": 0,
+			"line": 17
+		}
+	},
+	{
+		"code": "security-anchor-rel-noreferrer",
+		"message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"",
+		"end": {
+			"character": 1451,
+			"column": 90,
+			"line": 18
+		},
+		"pos": 1361,
+		"start": {
+			"character": 1361,
+			"column": 0,
+			"line": 18
+		}
+	}
+]