-
-
Save michaelficarra/d782e02fa36ba3dcbefb to your computer and use it in GitHub Desktop.
This file contains hidden or 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