From 291c3c8034de5e5f772adc30fa64b9a8ddd7a408 Mon Sep 17 00:00:00 2001 From: undergroundwires Date: Sat, 5 Aug 2023 19:47:43 +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 ++++----- package-lock.json | 2 +- .../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 +++++++++++++++---- 7 files changed, 99 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 52c697995..2d6add5f6 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 de0dfb949..9d77ec9a3 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 d0dff64df..d135358fe 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 dba656f8c..29c4fde73 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 @@