Last active
August 29, 2015 14:06
-
-
Save benvinegar/3e2c909e31a7c9483978 to your computer and use it in GitHub Desktop.
Proof-of-concept ESLint rule for detecting common XSS pattern
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Proof of concept ESLint rule for warning about potential | |
* XSS vulnerabilities caused by mixing innerHTML/text node | |
* property access. | |
* | |
* More here: http://benv.ca/2012/10/2/you-are-probably-misusing-DOM-text-methods/ | |
*/ | |
'use strict'; | |
var WARNING_MSG = | |
'innerHTML and text node properties used together may result in XSS vulnerabilities'; | |
var TEXT_NODE_PROP_NAMES = ['createTextNode', 'innerText', 'textContent']; | |
module.exports = function(context) { | |
var firstAccess = Object.create(null); | |
// Walks up the AST looking for an ancestor node with the | |
// given type. If none is found, returns the top-most node in | |
// the AST (Program). | |
function findClosestWithType(node, types) { | |
while (node.parent) { | |
node = node.parent; | |
if (types.indexOf(node.type) >= 0) { | |
break; | |
} | |
} | |
return node; | |
} | |
function isInnerHTMLAccess(node) { | |
return node.property.name === 'innerHTML'; | |
} | |
return { | |
MemberExpression: function(node) { | |
var propName = node.property.name, | |
isInnerHTMLExpr = isInnerHTMLAccess(node), | |
isTextNodeExpr = TEXT_NODE_PROP_NAMES.indexOf(propName) >= 0; | |
if (!isInnerHTMLExpr && !isTextNodeExpr) { | |
return; | |
} | |
// Find closest ancestor inside this function block | |
var top = '' + findClosestWithType(node, ['FunctionDeclaration', 'FunctionExpression']).range[0]; | |
if (!(top in firstAccess)) { | |
// We haven't seen this ancestor; store it for later, keyed by prop name. | |
firstAccess[top] = node; | |
} else { | |
// If we've already seen this ancestor, it means we've already processed | |
// an innerHTML/text node member expression. If it's the "bad" member, e.g. | |
// we are currently processing innerHTML but we've already seen textContent | |
// in this function, then report to ESLint. | |
var entry = firstAccess[top]; | |
if (isInnerHTMLExpr && !isInnerHTMLAccess(entry) || isTextNodeExpr && isInnerHTMLAccess(entry)) { | |
context.report(node, WARNING_MSG); | |
} | |
} | |
} | |
}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Ah yeah, I think there's some merit to doing that. The core ESLint rules are all intentionally not fuzzy to avoid confusion by people who might not have enough context to understand the rules on their own.