Skip to content

Instantly share code, notes, and snippets.

@michaelficarra
Forked from benvinegar/textnode-innerhtml.js
Last active August 29, 2015 14:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save michaelficarra/d782e02fa36ba3dcbefb to your computer and use it in GitHub Desktop.
Save michaelficarra/d782e02fa36ba3dcbefb to your computer and use it in GitHub Desktop.
/**
* 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