From 3a594ac7fd708dc1e98155ffb9b21acd4e1fcf2d Mon Sep 17 00:00:00 2001 From: undergroundwires Date: Sun, 6 Aug 2023 02:09:11 +0200 Subject: [PATCH] Improve user privacy with secure outbound links All outbound links now include `rel="noopener noreferrer"` attribute. This security improvement prevents the new page from being able to access the `window.opener` property and ensures it runs in a separate process. `rel="noopener"`: When a new page is opened using `target="_blank"`, the new page runs on the same process as the originating page, and has a reference to the originating page `window.opener`. By implementing `rel="noopener"`, the new page is prevented to use `window.opener` property. It's security issue because the newly opened website could potentially redirect the page to a malicious URL. Even though privacy.sexy doesn't have any sensitive information to protect, this can still be a vector for phishing attacks. `rel="noreferrer"`: It implies features of `noopener`, and also prevents `Referer` header from being sent to the new page. Referer headers may include sensitive data, because they tell the new page the URL of the page the request is coming from. --- README.md | 28 ++++----- .../Node/Documentation/MarkdownRenderer.ts | 41 +++++++++---- .../Scripts/View/TheScriptsView.vue | 2 +- .../components/TheFooter/PrivacyPolicy.vue | 6 +- .../components/TheFooter/TheFooter.vue | 8 +-- .../Documentation/MarkdownRenderer.spec.ts | 58 +++++++++++++++---- 6 files changed, 98 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 52c69799..2d6add5f 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,13 @@

- + donation badge - + contributions are welcome
-
+ Language grade: JavaScript/TypeScript - + Maintainability
-
+ Unit tests status - + Integration tests status - + E2E tests status
-
+ Quality checks status - + Security checks status - + Build checks status
-
+ Git release status - + Site release status - + Desktop application release status
-
+ Auto-versioned by bump-everywhere = { + target: '_blank', + rel: 'noopener noreferrer', +}; + function openUrlsInNewTab(md: MarkdownIt) { // https://github.com/markdown-it/markdown-it/blob/12.2.0/docs/architecture.md#renderer - const defaultRender = getDefaultRenderer(md, 'link_open'); + const defaultRender = getOrDefaultRenderer(md, 'link_open'); md.renderer.rules.link_open = (tokens, idx, options, env, self) => { const token = tokens[idx]; - if (!getTokenAttributeValue(token, 'target')) { - token.attrPush(['target', '_blank']); - } + + Object.entries(ExternalAnchorElementAttributes).forEach(([name, value]) => { + const currentValue = getAttribute(token, name); + if (!currentValue) { + token.attrPush([name, value]); + } else if (currentValue !== value) { + setAttribute(token, name, value); + } + }); return defaultRender(tokens, idx, options, env, self); }; } -function getDefaultRenderer(md: MarkdownIt, ruleName: string): Renderer.RenderRule { +function getOrDefaultRenderer(md: MarkdownIt, ruleName: string): Renderer.RenderRule { const renderer = md.renderer.rules[ruleName]; - if (renderer) { - return renderer; - } - return (tokens, idx, options, _env, self) => { + return renderer || defaultRenderer; + function defaultRenderer(tokens, idx, options, _env, self) { return self.renderToken(tokens, idx, options); - }; + } } -function getTokenAttributeValue(token: Token, attributeName: string): string | undefined { - const attributeIndex = token.attrIndex(attributeName); +function getAttribute(token: Token, name: string): string | undefined { + const attributeIndex = token.attrIndex(name); if (attributeIndex < 0) { return undefined; } const value = token.attrs[attributeIndex][1]; return value; } + +function setAttribute(token: Token, name: string, value: string): void { + const attributeIndex = token.attrIndex(name); + if (attributeIndex < 0) { + throw new Error('Attribute does not exist'); + } + token.attrs[attributeIndex][1] = value; +} diff --git a/src/presentation/components/Scripts/View/TheScriptsView.vue b/src/presentation/components/Scripts/View/TheScriptsView.vue index de0dfb94..9d77ec9a 100644 --- a/src/presentation/components/Scripts/View/TheScriptsView.vue +++ b/src/presentation/components/Scripts/View/TheScriptsView.vue @@ -20,7 +20,7 @@

Sorry, no matches for "{{this.searchQuery | threeDotsTrim }}" 😞
Feel free to extend the scripts - here ✨ + here ✨
diff --git a/src/presentation/components/TheFooter/PrivacyPolicy.vue b/src/presentation/components/TheFooter/PrivacyPolicy.vue index d0dff64d..d135358f 100644 --- a/src/presentation/components/TheFooter/PrivacyPolicy.vue +++ b/src/presentation/components/TheFooter/PrivacyPolicy.vue @@ -19,13 +19,13 @@
🤖
All transparent: Deployed automatically from the master branch - of the source code with no changes. + of the source code with no changes.
📈
- Basic CDN statistics + Basic CDN statistics are collected by AWS but they cannot be traced to you or your behavior. You can download the offline version if you don't want any CDN data collection.
@@ -35,7 +35,7 @@
As almost no data is collected, the application gets better only with your active feedback. - Feel free to create an issue 😊
+ Feel free to create an issue 😊
diff --git a/src/presentation/components/TheFooter/TheFooter.vue b/src/presentation/components/TheFooter/TheFooter.vue index dba656f8..29c4fde7 100644 --- a/src/presentation/components/TheFooter/TheFooter.vue +++ b/src/presentation/components/TheFooter/TheFooter.vue @@ -5,7 +5,7 @@ - Online version at {{ homepageUrl }} + Online version at {{ homepageUrl }} @@ -14,19 +14,19 @@