feat(editor): add external link warning page
A malicious user could modify a valid looking link (for example of the editor UI) to point to a malicous site, by using overlays and CSS. Since CSS should stay enabled, we need to make the user aware of possible risks when leaving the HedgeDoc instance, in order to protect them from credential-theft. This commit adds a new interstitial page for external links, that shows the target URL and asks the user, whether they really want to continue. Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
committed by
Philip Molares
parent
fa00e34635
commit
814271dc0b
@@ -1,5 +1,11 @@
|
||||
# Release Notes
|
||||
|
||||
## <i class="fa fa-tag"></i> 1.x <i class="fa fa-calendar-o"></i> UNRELEASED
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Added a warning page when clicking external links
|
||||
|
||||
## <i class="fa fa-tag"></i> 1.10.8 <i class="fa fa-calendar-o"></i> 2026-04-15
|
||||
|
||||
### Bugfixes
|
||||
|
||||
@@ -557,10 +557,6 @@ export function postProcess (code) {
|
||||
html = html.replace(/@import url\(([^)]*)\);?/gi, '')
|
||||
$(value).html(html)
|
||||
})
|
||||
// link should open in new window or tab
|
||||
// also add noopener to prevent clickjacking
|
||||
// See details: https://mathiasbynens.github.io/rel-noopener/
|
||||
result.find('a:not([href^="#"]):not([target])').attr('target', '_blank').attr('rel', 'noopener')
|
||||
|
||||
// If it's hashtag link then make it base uri independent
|
||||
result.find('a[href^="#"]').each((index, linkTag) => {
|
||||
@@ -568,6 +564,7 @@ export function postProcess (code) {
|
||||
currentLocation.hash = linkTag.hash
|
||||
linkTag.href = currentLocation.toString()
|
||||
})
|
||||
rewriteExternalLinks(result)
|
||||
|
||||
// update continue line numbers
|
||||
const linenumberdivs = result.find('.gutter.linenumber').toArray()
|
||||
@@ -593,6 +590,30 @@ export function postProcess (code) {
|
||||
}
|
||||
window.postProcess = postProcess
|
||||
|
||||
// rewrite external links to go through the /_link warning page
|
||||
export function rewriteExternalLinks (view) {
|
||||
view.find('a[href]').each((index, linkTag) => {
|
||||
const anchor = $(linkTag)
|
||||
const href = anchor.attr('href')
|
||||
if (!href || href.startsWith('#')) {
|
||||
return
|
||||
}
|
||||
let parsed
|
||||
try {
|
||||
parsed = new URL(href)
|
||||
} catch (err) {
|
||||
return
|
||||
}
|
||||
// only rewrite links that have an absolute http(s) URL on a different origin
|
||||
if (!['http:', 'https:'].includes(parsed.protocol) || parsed.origin === window.location.origin) {
|
||||
return
|
||||
}
|
||||
const noteURL = window.location.pathname.split('/').pop()
|
||||
anchor.attr('href', `${window.location.origin}/_link?url=${encodeURIComponent(parsed.href)}¬e=${noteURL}`)
|
||||
})
|
||||
}
|
||||
window.rewriteExternalLinks = rewriteExternalLinks
|
||||
|
||||
const domevents = Object.getOwnPropertyNames(document).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(Object.getPrototypeOf(document)))).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(window))).filter(function (i) {
|
||||
return !i.indexOf('on') && (document[i] === null || typeof document[i] === 'function')
|
||||
}).filter(function (elem, pos, self) {
|
||||
|
||||
@@ -55,6 +55,7 @@ import {
|
||||
renderTOC,
|
||||
renderTags,
|
||||
renderTitle,
|
||||
rewriteExternalLinks,
|
||||
scrollToHash,
|
||||
smoothHashScroll,
|
||||
updateLastChange,
|
||||
@@ -3498,6 +3499,7 @@ function updateViewInner () {
|
||||
}
|
||||
removeDOMEvents(ui.area.markdown)
|
||||
finishView(ui.area.markdown)
|
||||
rewriteExternalLinks(ui.area.markdown)
|
||||
autoLinkify(ui.area.markdown)
|
||||
deduplicatedHeaderId(ui.area.markdown)
|
||||
renderTOC(ui.area.markdown)
|
||||
|
||||
41
public/views/link.ejs
Normal file
41
public/views/link.ejs
Normal file
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<%- include('hedgedoc/head') %>
|
||||
<link rel="stylesheet" href="<%- serverURL %>/css/center.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%- include('hedgedoc/header') %>
|
||||
<div class="container-fluid text-center">
|
||||
<div class="vertical-center-row">
|
||||
<h1>
|
||||
<% if (valid) { %>
|
||||
<%= __('External link') %>
|
||||
<% } else { %>
|
||||
<%= __('Invalid link') %>
|
||||
<% } %>
|
||||
</h1>
|
||||
<p>
|
||||
<%= __('You\'ve clicked a link that points to an external page.') %>
|
||||
<br>
|
||||
<%= __('The content of that page is unrelated to HedgeDoc. Do not enter your HedgeDoc credentials there.') %>
|
||||
</p>
|
||||
<% if (valid) { %>
|
||||
<p><code class="text-break"><%- targetURL %></code></p>
|
||||
<p><%= __('Do you want to continue?') %></p>
|
||||
<p>
|
||||
<a class="btn btn-primary" href="<%- targetURL %>"><%= __('Continue to external page') %></a>
|
||||
<a class="btn btn-default" href="<%- serverURL %>/<%- noteURL %>"><%= __('Back to your note') %></a>
|
||||
</p>
|
||||
<% } else { %>
|
||||
<p class="text-danger"><strong><%= __('The provided link is not a valid URL.') %></strong></p>
|
||||
<p>
|
||||
<a class="btn btn-default" href="<%- serverURL %>/<%- noteURL %>"><%= __('Back to your note') %></a>
|
||||
</p>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user