VYPR
High severityNVD Advisory· Published May 31, 2018· Updated Sep 16, 2024

CVE-2014-10065

CVE-2014-10065

Description

A bypass in remarkable before 1.4.1 allows injection of javascript: URLs into rendered content via crafted input that evades the bad protocol check.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A bypass in remarkable before 1.4.1 allows injection of javascript: URLs into rendered content via crafted input that evades the bad protocol check.

Vulnerability

In remarkable, a Markdown parsing library for JavaScript, versions prior to 1.4.1 are vulnerable to a URL injection flaw. The library includes a validation function intended to disallow dangerous schemes like javascript:. However, certain crafted inputs can bypass this check, allowing javascript: URLs to be injected into the final rendered HTML. The issue was addressed in commit d54ed887f4997221cd7cb9790e953a83c504de36 [2], which normalizes links before validation. The official description confirms that versions before 1.4.1 are affected [1].

Exploitation

An attacker who can supply user-controlled Markdown content (e.g., in a comment, forum post, or any application rendering Markdown via remarkable) can craft input that passes the protocol validation but eventually results in a javascript: URL when rendered. The attack requires no special network position and does not require authentication beyond normal content submission permissions. The fix introduced a normalizeLink function that first decodes and re-encodes the URL before validation, preventing the bypass [2].

Impact

Successful exploitation allows an attacker to inject arbitrary JavaScript into a web page that renders the Markdown. This could lead to cross-site scripting (XSS) attacks, where the attacker executes scripts in the context of the victim's browser, potentially stealing cookies, session tokens, or performing actions on behalf of the user. The impact is based on the privileges and context of the application using remarkable.

Mitigation

Upgrade to remarkable version 1.4.1 or later, which includes the normalization fix [2]. No workarounds are documented in the available references. The vulnerability was disclosed in 2014 and has been considered patched since version 1.4.1 [1].

AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
remarkablenpm
< 1.4.11.4.1

Affected products

2
  • ghsa-coords
    Range: < 1.4.1
  • HackerOne/remarkable node modulev5
    Range: <1.4.1

Patches

1
d54ed887f499

Normalize links before they hit renderer

https://github.com/jonschlinkert/remarkableAlex KocharinNov 13, 2014via ghsa
5 files changed · +44 13
  • lib/helpers/normalize_link.js+9 0 added
    @@ -0,0 +1,9 @@
    +'use strict';
    +
    +
    +var replaceEntities = require('../common/utils').replaceEntities;
    +
    +
    +module.exports = function normalizeLink(url) {
    +  return encodeURI(decodeURI(replaceEntities(url)));
    +};
    
  • lib/helpers/parse_link_destination.js+9 5 modified
    @@ -6,11 +6,12 @@
     'use strict';
     
     
    -var unescapeMd = require('../common/utils').unescapeMd;
    +var normalizeLink = require('./normalize_link');
    +var unescapeMd    = require('../common/utils').unescapeMd;
     
     
     module.exports = function parseLinkDestination(state, pos) {
    -  var code, level,
    +  var code, level, link,
           start = pos,
           max = state.posMax;
     
    @@ -20,8 +21,10 @@ module.exports = function parseLinkDestination(state, pos) {
           code = state.src.charCodeAt(pos);
           if (code === 0x0A /* \n */) { return false; }
           if (code === 0x3E /* > */) {
    +        link = normalizeLink(unescapeMd(state.src.slice(start + 1, pos)));
    +        if (!state.parser.validateLink(link)) { return false; }
             state.pos = pos + 1;
    -        state.linkContent = unescapeMd(state.src.slice(start + 1, pos));
    +        state.linkContent = link;
             return true;
           }
           if (code === 0x5C /* \ */ && pos + 1 < max) {
    @@ -67,9 +70,10 @@ module.exports = function parseLinkDestination(state, pos) {
     
       if (start === pos) { return false; }
     
    -  state.linkContent = unescapeMd(state.src.slice(start, pos));
    -  if (!state.parser.validateLink(state.linkContent)) { return false; }
    +  link = normalizeLink(unescapeMd(state.src.slice(start, pos)));
    +  if (!state.parser.validateLink(link)) { return false; }
     
    +  state.linkContent = link;
       state.pos = pos;
       return true;
     };
    
  • lib/renderer.js+2 2 modified
    @@ -147,15 +147,15 @@ rules.paragraph_close = function (tokens, idx /*, options, env */) {
     
     rules.link_open = function (tokens, idx /*, options, env */) {
       var title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : '';
    -  return '<a href="' + escapeHtml(encodeURI(decodeURI(replaceEntities(tokens[idx].href)))) + '"' + title + '>';
    +  return '<a href="' + escapeHtml(tokens[idx].href) + '"' + title + '>';
     };
     rules.link_close = function (/* tokens, idx, options, env */) {
       return '</a>';
     };
     
     
     rules.image = function (tokens, idx, options /*, env */) {
    -  var src = ' src="' + escapeHtml(encodeURI(tokens[idx].src)) + '"';
    +  var src = ' src="' + escapeHtml(tokens[idx].src) + '"';
       var title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : '';
       var alt = ' alt="' + (tokens[idx].alt ? escapeHtml(replaceEntities(tokens[idx].alt)) : '') + '"';
       var suffix = options.xhtmlOut ? ' /' : '';
    
  • lib/rules_inline/autolink.js+8 6 modified
    @@ -2,7 +2,8 @@
     
     'use strict';
     
    -var url_schemas = require('../common/url_schemas');
    +var url_schemas   = require('../common/url_schemas');
    +var normalizeLink = require('../helpers/normalize_link');
     
     
     /*eslint max-len:0*/
    @@ -11,7 +12,7 @@ var AUTOLINK_RE = /^<([a-zA-Z.\-]{1,25}):([^<>\x00-\x20]*)>/;
     
     
     module.exports = function autolink(state, silent) {
    -  var tail, linkMatch, emailMatch, url, pos = state.pos;
    +  var tail, linkMatch, emailMatch, url, fullUrl, pos = state.pos;
     
       if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; }
     
    @@ -25,13 +26,13 @@ module.exports = function autolink(state, silent) {
         if (url_schemas.indexOf(linkMatch[1].toLowerCase()) < 0) { return false; }
     
         url = linkMatch[0].slice(1, -1);
    -
    +    fullUrl = normalizeLink(url);
         if (!state.parser.validateLink(url)) { return false; }
     
         if (!silent) {
           state.push({
             type: 'link_open',
    -        href: url,
    +        href: fullUrl,
             level: state.level
           });
           state.push({
    @@ -52,12 +53,13 @@ module.exports = function autolink(state, silent) {
     
         url = emailMatch[0].slice(1, -1);
     
    -    if (!state.parser.validateLink('mailto:' + url)) { return false; }
    +    fullUrl = normalizeLink('mailto:' + url);
    +    if (!state.parser.validateLink(fullUrl)) { return false; }
     
         if (!silent) {
           state.push({
             type: 'link_open',
    -        href: 'mailto:' + url,
    +        href: fullUrl,
             level: state.level
           });
           state.push({
    
  • test/fixtures/remarkable/commonmark_extras.txt+16 0 modified
    @@ -107,3 +107,19 @@ a
     <p>a</p>
     <?php
     .
    +
    +Normalize link destination, but not text inside it:
    +
    +.
    +<http://example.com/α%CE%B2γ%CE%B4>
    +.
    +<p><a href="http://example.com/%CE%B1%CE%B2%CE%B3%CE%B4">http://example.com/α%CE%B2γ%CE%B4</a></p>
    +.
    +
    +Autolinks do not allow escaping:
    +
    +.
    +<http://example.com/\[\>
    +.
    +<p><a href="http://example.com/%5C%5B%5C">http://example.com/\[\</a></p>
    +.
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.