sanitize-html
Advanced tools
Comparing version 1.19.2 to 1.19.3
## Changelog | ||
1.19.3: reverted to `postcss` due to a [reported issue with `css-tree` that might or might not have XSS implications](https://github.com/punkave/sanitize-html/issues/269). | ||
1.19.2: | ||
@@ -4,0 +6,0 @@ |
@@ -11,3 +11,3 @@ 'use strict'; | ||
var srcset = require('srcset'); | ||
var csstree = require('css-tree'); | ||
var postcss = require('postcss'); | ||
var url = require('url'); | ||
@@ -342,21 +342,7 @@ | ||
try { | ||
var ast = csstree.parse(name + " {" + value + "}"); | ||
var selectors = rulesForSelector(name, options.allowedStyles || {}); | ||
var abstractSyntaxTree = postcss.parse(name + " {" + value + "}"); | ||
var filteredAST = filterCss(abstractSyntaxTree, options.allowedStyles); | ||
csstree.walk(ast, function (node, item, list) { | ||
if (node.type === 'Declaration' && list) { | ||
var value = csstree.generate(node.value); | ||
var rules = selectors[node.property]; | ||
value = stringifyStyleAttributes(filteredAST); | ||
if (rules !== undefined && rules.every(function (rule) { | ||
return !value.match(rule); | ||
})) { | ||
list.remove(item); | ||
} | ||
} | ||
}); | ||
value = csstree.generate(ast).slice(name.length + 1); | ||
value = value.slice(0, value.length - 1); | ||
if (value.length === 0) { | ||
@@ -366,5 +352,2 @@ delete frame.attribs[a]; | ||
} | ||
// preserve the final semicolon | ||
value += ';'; | ||
} catch (e) { | ||
@@ -526,6 +509,22 @@ delete frame.attribs[a]; | ||
function rulesForSelector(selector, allowedStyles) { | ||
/** | ||
* Filters user input css properties by whitelisted regex attributes. | ||
* | ||
* @param {object} abstractSyntaxTree - Object representation of CSS attributes. | ||
* @property {array[Declaration]} abstractSyntaxTree.nodes[0] - Each object cointains prop and value key, i.e { prop: 'color', value: 'red' }. | ||
* @param {object} allowedStyles - Keys are properties (i.e color), value is list of permitted regex rules (i.e /green/i). | ||
* @return {object} - Abstract Syntax Tree with filtered style attributes. | ||
*/ | ||
function filterCss(abstractSyntaxTree, allowedStyles) { | ||
if (!allowedStyles) { | ||
return abstractSyntaxTree; | ||
} | ||
var filteredAST = cloneDeep(abstractSyntaxTree); | ||
var astRules = abstractSyntaxTree.nodes[0]; | ||
var selectedRule; | ||
// Merge global and tag-specific styles into new AST. | ||
if (allowedStyles[selector] && allowedStyles['*']) { | ||
return mergeWith(cloneDeep(allowedStyles[selector]), allowedStyles['*'], function (objValue, srcValue) { | ||
if (allowedStyles[astRules.selector] && allowedStyles['*']) { | ||
selectedRule = mergeWith(cloneDeep(allowedStyles[astRules.selector]), allowedStyles['*'], function (objValue, srcValue) { | ||
if (Array.isArray(objValue)) { | ||
@@ -535,7 +534,55 @@ return objValue.concat(srcValue); | ||
}); | ||
} else { | ||
selectedRule = allowedStyles[astRules.selector] || allowedStyles['*']; | ||
} | ||
return allowedStyles[selector] || allowedStyles['*'] || {}; | ||
if (selectedRule) { | ||
filteredAST.nodes[0].nodes = astRules.nodes.reduce(filterDeclarations(selectedRule), []); | ||
} | ||
return filteredAST; | ||
} | ||
/** | ||
* Extracts the style attribues from an AbstractSyntaxTree and formats those | ||
* values in the inline style attribute format. | ||
* | ||
* @param {AbstractSyntaxTree} filteredAST | ||
* @return {string} - Example: "color:yellow;text-align:center;font-family:helvetica;" | ||
*/ | ||
function stringifyStyleAttributes(filteredAST) { | ||
return filteredAST.nodes[0].nodes.reduce(function (extractedAttributes, attributeObject) { | ||
extractedAttributes.push(attributeObject.prop + ':' + attributeObject.value + ';'); | ||
return extractedAttributes; | ||
}, []).join(''); | ||
} | ||
/** | ||
* Filters the existing attributes for the given property. Discards any attributes | ||
* which don't match the whitelist. | ||
* | ||
* @param {object} selectedRule - Example: { color: red, font-family: helvetica } | ||
* @param {array} allowedDeclarationsList - List of declarations which pass whitelisting. | ||
* @param {object} attributeObject - Object representing the current css property. | ||
* @property {string} attributeObject.type - Typically 'declaration'. | ||
* @property {string} attributeObject.prop - The CSS property, i.e 'color'. | ||
* @property {string} attributeObject.value - The corresponding value to the css property, i.e 'red'. | ||
* @return {function} - When used in Array.reduce, will return an array of Declaration objects | ||
*/ | ||
function filterDeclarations(selectedRule) { | ||
return function (allowedDeclarationsList, attributeObject) { | ||
// If this property is whitelisted... | ||
if (selectedRule.hasOwnProperty(attributeObject.prop)) { | ||
var matchesRegex = selectedRule[attributeObject.prop].some(function (regularExpression) { | ||
return regularExpression.test(attributeObject.value); | ||
}); | ||
if (matchesRegex) { | ||
allowedDeclarationsList.push(attributeObject); | ||
} | ||
} | ||
return allowedDeclarationsList; | ||
}; | ||
} | ||
function filterClasses(classes, allowed) { | ||
@@ -542,0 +589,0 @@ if (!allowed) { |
{ | ||
"name": "sanitize-html", | ||
"version": "1.19.2", | ||
"version": "1.19.3", | ||
"description": "Clean up user-submitted HTML, preserving whitelisted elements and whitelisted attributes on a per-element basis", | ||
@@ -29,3 +29,2 @@ "main": "dist/index.js", | ||
"chalk": "^2.4.1", | ||
"css-tree": "^1.0.0-alpha.29", | ||
"htmlparser2": "^3.10.0", | ||
@@ -37,2 +36,3 @@ "lodash.clonedeep": "^4.5.0", | ||
"lodash.mergewith": "^4.6.1", | ||
"postcss": "^7.0.5", | ||
"srcset": "^1.0.0", | ||
@@ -39,0 +39,0 @@ "xtend": "^4.0.1" |
102
src/index.js
@@ -9,3 +9,3 @@ var htmlparser = require('htmlparser2'); | ||
var srcset = require('srcset'); | ||
var csstree = require('css-tree'); | ||
var postcss = require('postcss'); | ||
var url = require('url'); | ||
@@ -303,19 +303,7 @@ | ||
try { | ||
var ast = csstree.parse(name + " {" + value + "}"); | ||
var selectors = rulesForSelector(name, options.allowedStyles || {}); | ||
var abstractSyntaxTree = postcss.parse(name + " {" + value + "}"); | ||
var filteredAST = filterCss(abstractSyntaxTree, options.allowedStyles); | ||
csstree.walk(ast, function(node, item, list) { | ||
if (node.type === 'Declaration' && list) { | ||
var value = csstree.generate(node.value); | ||
var rules = selectors[node.property]; | ||
value = stringifyStyleAttributes(filteredAST); | ||
if (rules !== undefined && rules.every(function (rule) { return !value.match(rule); })) { | ||
list.remove(item) | ||
} | ||
} | ||
}) | ||
value = csstree.generate(ast).slice(name.length + 1); | ||
value = value.slice(0, value.length - 1); | ||
if(value.length === 0) { | ||
@@ -325,6 +313,2 @@ delete frame.attribs[a]; | ||
} | ||
// preserve the final semicolon | ||
value += ';'; | ||
} catch (e) { | ||
@@ -487,7 +471,23 @@ delete frame.attribs[a]; | ||
function rulesForSelector(selector, allowedStyles) { | ||
/** | ||
* Filters user input css properties by whitelisted regex attributes. | ||
* | ||
* @param {object} abstractSyntaxTree - Object representation of CSS attributes. | ||
* @property {array[Declaration]} abstractSyntaxTree.nodes[0] - Each object cointains prop and value key, i.e { prop: 'color', value: 'red' }. | ||
* @param {object} allowedStyles - Keys are properties (i.e color), value is list of permitted regex rules (i.e /green/i). | ||
* @return {object} - Abstract Syntax Tree with filtered style attributes. | ||
*/ | ||
function filterCss(abstractSyntaxTree, allowedStyles) { | ||
if (!allowedStyles) { | ||
return abstractSyntaxTree; | ||
} | ||
var filteredAST = cloneDeep(abstractSyntaxTree); | ||
var astRules = abstractSyntaxTree.nodes[0]; | ||
var selectedRule; | ||
// Merge global and tag-specific styles into new AST. | ||
if (allowedStyles[selector] && allowedStyles['*']) { | ||
return mergeWith( | ||
cloneDeep(allowedStyles[selector]), | ||
if (allowedStyles[astRules.selector] && allowedStyles['*']) { | ||
selectedRule = mergeWith( | ||
cloneDeep(allowedStyles[astRules.selector]), | ||
allowedStyles['*'], | ||
@@ -500,7 +500,59 @@ function(objValue, srcValue) { | ||
); | ||
} else { | ||
selectedRule = allowedStyles[astRules.selector] || allowedStyles['*']; | ||
} | ||
return allowedStyles[selector] || allowedStyles['*'] || {}; | ||
if (selectedRule) { | ||
filteredAST.nodes[0].nodes = astRules.nodes.reduce(filterDeclarations(selectedRule), []); | ||
} | ||
return filteredAST; | ||
} | ||
/** | ||
* Extracts the style attribues from an AbstractSyntaxTree and formats those | ||
* values in the inline style attribute format. | ||
* | ||
* @param {AbstractSyntaxTree} filteredAST | ||
* @return {string} - Example: "color:yellow;text-align:center;font-family:helvetica;" | ||
*/ | ||
function stringifyStyleAttributes(filteredAST) { | ||
return filteredAST.nodes[0].nodes | ||
.reduce(function(extractedAttributes, attributeObject) { | ||
extractedAttributes.push( | ||
attributeObject.prop + ':' + attributeObject.value + ';' | ||
); | ||
return extractedAttributes; | ||
}, []) | ||
.join(''); | ||
} | ||
/** | ||
* Filters the existing attributes for the given property. Discards any attributes | ||
* which don't match the whitelist. | ||
* | ||
* @param {object} selectedRule - Example: { color: red, font-family: helvetica } | ||
* @param {array} allowedDeclarationsList - List of declarations which pass whitelisting. | ||
* @param {object} attributeObject - Object representing the current css property. | ||
* @property {string} attributeObject.type - Typically 'declaration'. | ||
* @property {string} attributeObject.prop - The CSS property, i.e 'color'. | ||
* @property {string} attributeObject.value - The corresponding value to the css property, i.e 'red'. | ||
* @return {function} - When used in Array.reduce, will return an array of Declaration objects | ||
*/ | ||
function filterDeclarations(selectedRule) { | ||
return function (allowedDeclarationsList, attributeObject) { | ||
// If this property is whitelisted... | ||
if (selectedRule.hasOwnProperty(attributeObject.prop)) { | ||
var matchesRegex = selectedRule[attributeObject.prop].some(function(regularExpression) { | ||
return regularExpression.test(attributeObject.value); | ||
}); | ||
if (matchesRegex) { | ||
allowedDeclarationsList.push(attributeObject); | ||
} | ||
} | ||
return allowedDeclarationsList; | ||
}; | ||
} | ||
function filterClasses(classes, allowed) { | ||
@@ -507,0 +559,0 @@ if (!allowed) { |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
24740
8
20
1266778
+ Addedpostcss@^7.0.5
+ Addedpicocolors@0.2.1(transitive)
+ Addedpostcss@7.0.39(transitive)
- Removedcss-tree@^1.0.0-alpha.29
- Removedcss-tree@1.1.3(transitive)
- Removedmdn-data@2.0.14(transitive)