From 6f054ca562d847b49544d2cb6e24fc91abede082 Mon Sep 17 00:00:00 2001 From: ache Date: Sun, 3 Mar 2019 02:13:07 +0100 Subject: Init commit --- .babelrc | 6 ++++ README.md | 3 ++ __tests__/index.js | 22 ++++++++++++ dist/index.js | 31 +++++++++++++++++ dist/schemes.js | 3 ++ package.json | 39 +++++++++++++++++++++ src/index.js | 27 +++++++++++++++ src/schemes.js | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 230 insertions(+) create mode 100644 .babelrc create mode 100644 README.md create mode 100644 __tests__/index.js create mode 100644 dist/index.js create mode 100644 dist/schemes.js create mode 100644 package.json create mode 100644 src/index.js create mode 100644 src/schemes.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..cf18521 --- /dev/null +++ b/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": ["@babel/env"], + "plugins": [ + ["@babel/plugin-proposal-object-rest-spread"] + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..68dd629 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# remark-seclink + +This plugin try to prevent XSS based on URI schemes. diff --git a/__tests__/index.js b/__tests__/index.js new file mode 100644 index 0000000..d1bb986 --- /dev/null +++ b/__tests__/index.js @@ -0,0 +1,22 @@ +'use strict'; + +import remark from 'remark'; +import plugin from '..'; +import test from 'ava'; + +const processor = str => remark().use(plugin).processSync(str); + +const testInfos = [ + {name: 'example', base: '[link](url)', result: '[link](/url)\n'}, + {name: 'local link', base: '[link](/url)', result: '[link](/url)\n'}, + {name: 'auto link', base: '', result: '\n'}, +]; + +testInfos.forEach(testInfo => { + test(testInfo.name, t => { + const {base} = testInfo; + const {result} = testInfo; + t.is(processor(base).contents, result); + }); +}); + diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..d3a9509 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,31 @@ +'use strict'; + +var visit = require('unist-util-visit'); + +var schemes = require('./schemes.js'); + +function plugin() { + return transformer; + + function transformer(tree) { + visit(tree, 'link', function (link) { + if (link.url) { + if (link.url[0] === '/') { + // Local link + return; + } + + if (schemes.some(function (scheme) { + return link.url.startsWith(scheme + ':'); + })) { + // Valide scheme + return; + } + + link.url = '/' + link.url; + } + }); + } +} + +module.exports = plugin; \ No newline at end of file diff --git a/dist/schemes.js b/dist/schemes.js new file mode 100644 index 0000000..717eca8 --- /dev/null +++ b/dist/schemes.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = ['aaa', 'aaas', 'about', 'acap', 'acct', 'cap', 'cid', 'coap', 'coap+tcp', 'coap+ws', 'coaps', 'coaps+tcp', 'coaps+ws', 'crid', 'data', 'dav', 'dict', 'dns', 'example', 'file', 'ftp', 'geo', 'go', 'gopher', 'h323', 'http', 'https', 'iax', 'icap', 'im', 'imap', 'info', 'ipp', 'ipps', 'iris', 'iris.beep', 'iris.lwz', 'iris.xpc', 'iris.xpcs', 'jabber', 'ldap', 'leaptofrogans', 'mailto', 'mid', 'msrp', 'msrps', 'mtqp', 'mupdate', 'news', 'nfs', 'ni', 'nih', 'nntp', 'opaquelocktoken', 'pkcs11', 'pop', 'pres', 'reload', 'rtsp', 'rtsps', 'rtspu', 'service', 'session', 'shttp', 'sieve', 'sip', 'sips', 'sms', 'snmp', 'soap.beep', 'soap.beeps', 'stun', 'stuns', 'tag', 'tel', 'telnet', 'tftp', 'thismessage', 'tip', 'tn3270', 'turn', 'turns', 'tv', 'urn', 'vemmi', 'vnc', 'ws', 'wss', 'xcon', 'xcon-userid', 'xmlrpc.beep', 'xmlrpc.beeps', 'xmpp', 'z39.50r', 'z39.50s']; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..302afaf --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "remark-seclink", + "version": "1.0.0", + "description": "A remark plugin to secure URI of link from JS XSS", + "main": "dist/index.js", + "scripts": { + "pretest": "xo", + "prepare": "del-cli dist && cross-env BABEL_ENV=production babel src --out-dir dist", + "test": "ava" + }, + "repository": { + "type": "git", + "url": "https://git.ache.one/remark-seclink" + }, + "author": "ache ", + "license": "ISC", + "dependencies": { + "unist-util-visit": "^1.4.0" + }, + "xo": { + "space": true, + "rules": { + "comma-dangle": [ + "error", + "always-multiline" + ] + } + }, + "devDependencies": { + "@babel/cli": "^7.2.3", + "@babel/core": "^7.3.4", + "@babel/preset-env": "^7.3.4", + "ava": "^1.2.1", + "cross-env": "^5.2.0", + "del-cli": "^1.1.0", + "remark": "^10.0.1", + "xo": "^0.24.0" + } +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..f1f0ce5 --- /dev/null +++ b/src/index.js @@ -0,0 +1,27 @@ +'use strict'; + +const visit = require('unist-util-visit'); +const schemes = require('./schemes.js'); + +function plugin() { + return transformer; + function transformer(tree) { + visit(tree, 'link', link => { + if (link.url) { + if (link.url[0] === '/') { // Local link + return; + } + + if (schemes.some(scheme => link.url.startsWith(scheme + ':'))) { + // Valide scheme + return; + } + + link.url = '/' + link.url; + } + }); + } +} + +module.exports = plugin; + diff --git a/src/schemes.js b/src/schemes.js new file mode 100644 index 0000000..fecc1dd --- /dev/null +++ b/src/schemes.js @@ -0,0 +1,99 @@ +'use strict'; + +module.exports = [ + 'aaa', + 'aaas', + 'about', + 'acap', + 'acct', + 'cap', + 'cid', + 'coap', + 'coap+tcp', + 'coap+ws', + 'coaps', + 'coaps+tcp', + 'coaps+ws', + 'crid', + 'data', + 'dav', + 'dict', + 'dns', + 'example', + 'file', + 'ftp', + 'geo', + 'go', + 'gopher', + 'h323', + 'http', + 'https', + 'iax', + 'icap', + 'im', + 'imap', + 'info', + 'ipp', + 'ipps', + 'iris', + 'iris.beep', + 'iris.lwz', + 'iris.xpc', + 'iris.xpcs', + 'jabber', + 'ldap', + 'leaptofrogans', + 'mailto', + 'mid', + 'msrp', + 'msrps', + 'mtqp', + 'mupdate', + 'news', + 'nfs', + 'ni', + 'nih', + 'nntp', + 'opaquelocktoken', + 'pkcs11', + 'pop', + 'pres', + 'reload', + 'rtsp', + 'rtsps', + 'rtspu', + 'service', + 'session', + 'shttp', + 'sieve', + 'sip', + 'sips', + 'sms', + 'snmp', + 'soap.beep', + 'soap.beeps', + 'stun', + 'stuns', + 'tag', + 'tel', + 'telnet', + 'tftp', + 'thismessage', + 'tip', + 'tn3270', + 'turn', + 'turns', + 'tv', + 'urn', + 'vemmi', + 'vnc', + 'ws', + 'wss', + 'xcon', + 'xcon-userid', + 'xmlrpc.beep', + 'xmlrpc.beeps', + 'xmpp', + 'z39.50r', + 'z39.50s', +]; -- cgit v1.2.3