aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorache <ache@ache.one>2019-03-03 02:13:07 +0100
committerache <ache@ache.one>2019-03-03 02:14:59 +0100
commit6f054ca562d847b49544d2cb6e24fc91abede082 (patch)
tree4a3db33251fc7c789cb77bb9b7544cdd60c851c1
Init commit
-rw-r--r--.babelrc6
-rw-r--r--README.md3
-rw-r--r--__tests__/index.js22
-rw-r--r--dist/index.js31
-rw-r--r--dist/schemes.js3
-rw-r--r--package.json39
-rw-r--r--src/index.js27
-rw-r--r--src/schemes.js99
8 files changed, 230 insertions, 0 deletions
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: '<http://ache>', result: '<http://ache>\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 <ache@ache.one>",
+ "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',
+];