Socket
Socket
Sign inDemoInstall

eslint-plugin-no-unsanitized

Package Overview
Dependencies
Maintainers
1
Versions
20
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint-plugin-no-unsanitized - npm Package Compare versions

Comparing version 4.0.2 to 4.1.0

26

docs/rules/customization.md

@@ -7,6 +7,7 @@ # Customization

This will be used to check code like here:
```js
const greeting_template = `<p>Hello World!</p>`;
// ... lots of other code in between ...
someElemenet.innerHTML = greeting_template;
someElement.innerHTML = greeting_template;
```

@@ -23,18 +24,19 @@

## Customization Examples
You can customize the way this rule works in various ways.
* Add to the list of properties or functions to be checked for potentially
dangers variable input
* Add to the list of allowed escaping functions to mitigate security concerns
* Besides adding to the list, you may override the defaults and provide an exhaustive list yourself
- Add to the list of properties or functions to be checked for potentially
dangers variable input
- Add to the list of allowed escaping functions to mitigate security concerns
- Besides adding to the list, you may override the defaults and provide an exhaustive list yourself
### Disallow the `html` function by specifically checking input for the first function parameter
```json
{
"rules": {
"rules": {
"no-unsanitized/method": [
"error",
{},
{
},
{
"html": {

@@ -45,3 +47,3 @@ "properties": [0]

]
}
}
}

@@ -87,8 +89,8 @@ ```

### Override list of escaping functions for property assignments only
### Override list of escaping functions for property assignments only
TBD
#### More
* See [our rule schema definition](SCHEMA.md).
- See [our rule schema definition](https://github.com/mozilla/eslint-plugin-no-unsanitized/blob/main/SCHEMA.md).
# Fixing Rule Violations
The default configuration will allow your code to pass if it ensures
that all user input is properly escaped.
Using the [sanitizer.js](https://github.com/fxos-eng/sanitizer)
Using the [sanitizer.js](https://github.com/fxos-eng/sanitizer)
library your code would look like this:

@@ -9,9 +10,13 @@

// example for no-unsanitized/property
foo.innerHTML = Sanitizer.escapeHTML`<a href="${link}">click</a>`
foo.innerHTML = Sanitizer.escapeHTML`<a href="${link}">click</a>`;
// example for no-unsanitized/method
node.insertAdjacentHTML('afterend', Sanitizer.escapeHTML`<a href="${link}">click</a>`);
node.insertAdjacentHTML(
"afterend",
Sanitizer.escapeHTML`<a href="${link}">click</a>`
);
```
## Wrapping & Unwrapping
If you need to generate your HTML somewhere else and e.g. cache it,

@@ -26,7 +31,8 @@ you won't be able to run `escapeHTML` on a string that knows no

Example:
```js
// create the HTML object for later usage
function storeGreeting(username) {
var safeHTML = Sanitizer.createSafeHTML`<p>Hello ${username}</p>`;
cache.store('greeting', safeHTML)
var safeHTML = Sanitizer.createSafeHTML`<p>Hello ${username}</p>`;
cache.store("greeting", safeHTML);
}

@@ -36,4 +42,4 @@

function useGreeting(domNode) {
var htmlObj = cache.retrieve('greeting');
domNode.innerHTML = Sanitizer.unwrapSafeHTML(htmlObj);
var htmlObj = cache.retrieve("greeting");
domNode.innerHTML = Sanitizer.unwrapSafeHTML(htmlObj);
}

@@ -43,4 +49,5 @@ ```

# That still does not solve my problem
It might very well be the case that there's a bug in our linter rule.
[Please file an issue.](https://github.com/mozilla/eslint-plugin-no-unsanitized/issues/new)
# method
The *method* rule in *eslint-plugin-no-unsanitized* perform basic security
The _method_ rule in _eslint-plugin-no-unsanitized_ perform basic security
checks for function calls. The idea of these checks is to ensure that certain insecure

@@ -8,2 +9,3 @@ coding patterns are avoided in your codebase. We encourage developers

## Unsafe call to insertAdjacentHTML, document.write or document.writeln
This error message suggests that you are using an unsafe coding

@@ -16,4 +18,5 @@ pattern. Please do not simply call functions like `insertAdjacentHTML` with a

### Further Reading
* Advanced guidance on [Fixing rule violations](fixing-violations.md)
* This rule has some [customization](customization.md) options that allow you
to add or remove functions that should not be called
- Advanced guidance on [Fixing rule violations](fixing-violations.md)
- This rule has some [customization](customization.md) options that allow you
to add or remove functions that should not be called
# property
The *property* rule in *eslint-plugin-no-unsanitized* perform basic security
The _property_ rule in _eslint-plugin-no-unsanitized_ perform basic security
checks for property assignments. The idea of these checks is to ensure that

@@ -8,3 +9,4 @@ certain insecure coding patterns are avoided in your codebase. We encourage

## Unsafe assignment to innerHTML or outerHTML
## Unsafe assignment to innerHTML or outerHTML
This error message suggests that you are using an unsafe coding

@@ -17,4 +19,5 @@ pattern. Please do not simply assign variables to `innertHTML`,

### Further Reading
* Advanced guidance on [Fixing rule violations](fixing-violations.md)
* This rule has some [customization](customization.md) options that allow you
to add or remove functions that should not be called
- Advanced guidance on [Fixing rule violations](fixing-violations.md)
- This rule has some [customization](customization.md) options that allow you
to add or remove functions that should not be called

@@ -1,54 +0,36 @@

module.exports = {
const { readFileSync } = require("fs");
const data = readFileSync("./package.json");
const packageJSON = JSON.parse(data);
const plugin = {
meta: {
name: "eslint-plugin-no-unsanitized",
version: packageJSON.version,
},
rules: {
"property": require("./lib/rules/property"),
"method": require("./lib/rules/method")
property: require("./lib/rules/property"),
method: require("./lib/rules/method"),
},
configs: {
DOM: {
plugins: ["no-unsanitized"],
rules: {
"no-unsanitized/property": [
"error",
{
},
{
configs: {},
};
// Check unsafe assignment to innerHTML
innerHTML: {},
const rules = {
"no-unsanitized/property": "error",
"no-unsanitized/method": "error",
};
// Check unsafe assignment to outerHTML
outerHTML: {},
}
],
"no-unsanitized/method": [
"error",
{
},
{
Object.assign(plugin.configs, {
"recommended-legacy": {
plugins: ["no-unsanitized"],
rules,
},
recommended: [
{
plugins: { "no-unsanitized": plugin },
rules,
},
],
});
// check second parameter to .insertAdjacentHTML()
insertAdjacentHTML: {
properties: [1]
},
// check first parameter to .write(), as long as the preceeding object matches the regex "document"
write: {
objectMatches: [
"document"
],
properties: [0]
},
// check first parameter to .writeLn(), as long as the preceeding object matches the regex "document"
writeln: {
objectMatches: [
"document"
],
properties: [0]
}
}
]
}
}
}
};
module.exports = plugin;
/**
* @fileoverview ESLint helpers for checking sanitization
* @file ESLint helpers for checking sanitization
* @author Frederik Braun et al.

@@ -12,9 +12,26 @@ * @copyright 2015-2017 Mozilla Corporation. All rights reserved.

/** Change this to class RuleHelper when <4.2.6 is no longer an issue
/**
* Gets the scope for a node taking account of where the scope function
* is available (supports node versions earlier than 8.37.0).
*
* @constructor
* @param {Object} context ESLint configuration context
* @param {Object} defaultRuleChecks Default rules to merge with
* @param {object} context
* The context passed from ESLint.
* @param {object} node
* The node to get the scope for.
* @returns {Function}
* The getScope function object.
*/
function getScope(context, node) {
return context.sourceCode?.getScope
? context.sourceCode.getScope(node)
: context.getScope(node);
}
/**
* Change this to class RuleHelper when <4.2.6 is no longer an issue
*
* @class
* @param {object} context ESLint configuration context
* @param {object} defaultRuleChecks Default rules to merge with
* this.context
*
*/

@@ -33,9 +50,8 @@ function RuleHelper(context, defaultRuleChecks) {

*
* @param {Object} expression Checks whether this node is an allowed expression.
* @param {Object} escapeObject contains keys "methods" and "taggedTemplates" which are arrays of strings
* @param {object} expression Checks whether this node is an allowed expression.
* @param {object} escapeObject contains keys "methods" and "taggedTemplates" which are arrays of strings
* of matching escaping function names.
* @param {Object} details Additional linter violation state information, in case this function was called
* @param {object} details Additional linter violation state information, in case this function was called
* recursively.
* @returns {boolean} Returns whether the expression is allowed.
*
*/

@@ -54,6 +70,5 @@ allowedExpression(expression, escapeObject, details) {

switch(expression.type) {
case"Literal":
/* surely, someone could have an evil literal in there, but that"s malice
switch (expression.type) {
case "Literal":
/* surely, someone could have an evil literal in there, but that"s malice
we can just check for unsafe coding practice, not outright malice

@@ -63,41 +78,72 @@ example literal "<script>eval(location.hash.slice(1)</script>"

*/
allowed = true;
break;
case "TemplateElement":
// Raw text from a template
allowed = true;
break;
case "TemplateLiteral":
// check only the ${..} expressions
allowed = this.allowedExpression(expression.expressions, escapeObject, details);
break;
case "TaggedTemplateExpression":
allowed = this.isAllowedCallExpression(expression.tag, escapeObject.taggedTemplates || VALID_ESCAPERS);
break;
case "CallExpression":
allowed = this.isAllowedCallExpression(expression.callee, escapeObject.methods || VALID_UNWRAPPERS);
break;
case "BinaryExpression":
allowed = ((this.allowedExpression(expression.left, escapeObject, details))
&& (this.allowedExpression(expression.right, escapeObject, details)));
break;
case "TSAsExpression":
// TSAsExpressions contain the raw javascript value in 'expression'
allowed = this.allowedExpression(expression.expression, escapeObject, details);
break;
case "TypeCastExpression":
allowed = this.allowedExpression(expression.expression, escapeObject, details);
break;
case "Identifier":
allowed = this.isAllowedIdentifier(expression, escapeObject, details);
break;
default:
// everything that doesn't match is considered unsafe:
allowed = false;
break;
allowed = true;
break;
case "TemplateElement":
// Raw text from a template
allowed = true;
break;
case "TemplateLiteral":
// check only the ${..} expressions
allowed = this.allowedExpression(
expression.expressions,
escapeObject,
details
);
break;
case "TaggedTemplateExpression":
allowed = this.isAllowedCallExpression(
expression.tag,
escapeObject.taggedTemplates || VALID_ESCAPERS
);
break;
case "CallExpression":
allowed = this.isAllowedCallExpression(
expression.callee,
escapeObject.methods || VALID_UNWRAPPERS
);
break;
case "BinaryExpression":
allowed =
this.allowedExpression(
expression.left,
escapeObject,
details
) &&
this.allowedExpression(
expression.right,
escapeObject,
details
);
break;
case "TSAsExpression":
// TSAsExpressions contain the raw javascript value in 'expression'
allowed = this.allowedExpression(
expression.expression,
escapeObject,
details
);
break;
case "TypeCastExpression":
allowed = this.allowedExpression(
expression.expression,
escapeObject,
details
);
break;
case "Identifier":
allowed = this.isAllowedIdentifier(
expression,
escapeObject,
details
);
break;
default:
// everything that doesn't match is considered unsafe:
allowed = false;
break;
}
if (Array.isArray(expression)) {
allowed = expression.every((e) => this.allowedExpression(e, escapeObject, details));
allowed = expression.every((e) =>
this.allowedExpression(e, escapeObject, details)
);
}

@@ -114,6 +160,6 @@ return allowed;

*
* @param {Object} expression Identifier expression
* @param {Object} escapeObject contains keys "methods" and "taggedTemplates" which are arrays of strings
* @param {object} expression Identifier expression
* @param {object} escapeObject contains keys "methods" and "taggedTemplates" which are arrays of strings
* of matching escaping function names.
* @param {Object} details Additional linter violation state information, in case this function was called
* @param {object} details Additional linter violation state information, in case this function was called
* recursively.

@@ -123,3 +169,2 @@ * @returns {boolean} Returns whether the Identifier is deemed safe.

isAllowedIdentifier(expression, escapeObject, details) {
// respect the custom config property `variableTracing`:

@@ -131,12 +176,14 @@ if (!this.ruleChecks["variableTracing"]) {

// find declared variables and see which are literals
const scope = this.context.getScope(expression);
const variableInfo = scope.set.get(expression.name);
const scope = getScope(this.context, expression);
const variableInfo = scope.variableScope.set.get(expression.name);
let allowed = false;
// If we can't get info on the variable, we just can't allow it
if (!variableInfo ||
if (
!variableInfo ||
!variableInfo.defs ||
variableInfo.defs.length == 0 ||
!variableInfo.references ||
variableInfo.references.length == 0) {
variableInfo.references.length == 0
) {
// FIXME Fix/Adjust towards a helpful message here and update tests accordingly.

@@ -154,5 +201,8 @@ // details.message = `Variable ${expression.name} considered unsafe: variable initialization not found`;

// ArrowFunctionExpression, FunctionDeclaration or FunctionExpression
const {line, column} = def.node.loc.start;
if ((def.node.type === "FunctionDeclaration") || (def.node.type == "ArrowFunctionExpression") || (def.node.type === "FunctionExpression"))
{
const { line, column } = def.node.loc.start;
if (
def.node.type === "FunctionDeclaration" ||
def.node.type == "ArrowFunctionExpression" ||
def.node.type === "FunctionExpression"
) {
details.message = `Variable '${expression.name}' declared as function parameter, which is considered unsafe. '${def.node.type}' at ${line}:${column}`;

@@ -165,3 +215,3 @@ } else {

}
if ((def.kind !== "let") && (def.kind !== "const")) {
if (def.kind !== "let" && def.kind !== "const") {
// We do not allow for identifiers declared with "var", as they can be overridden in a

@@ -177,7 +227,10 @@ // way that is hard for us to follow (e.g., assignments to globalThis[theirNameAsString]).

// When the variable is only declared but not initialized, `init` is `null`.
if (varInitAs && !this.allowedExpression(varInitAs, escapeObject, details)) {
if (
varInitAs &&
!this.allowedExpression(varInitAs, escapeObject, details)
) {
// if one variable definition is considered unsafe, all are.
// NB: order of definition is unclear. See issue #168.
if (!details.message) {
const {line, column} = varInitAs.loc.start;
const { line, column } = varInitAs.loc.start;
details.message = `Variable '${expression.name}' initialized with unsafe value at ${line}:${column}`;

@@ -199,3 +252,6 @@ }

// then we should consider it safe.
if (variableInfo.references.filter(ref => ref.isWrite()).length === 0) {
if (
variableInfo.references.filter((ref) => ref.isWrite())
.length === 0
) {
allWritingRefsAllowed = true;

@@ -212,5 +268,11 @@ }

// due to dynamic behavior if-conditions and such
if (!this.allowedExpression(writeExpr, escapeObject, details)) {
if (
!this.allowedExpression(
writeExpr,
escapeObject,
details
)
) {
if (!details.message) {
const {line, column} = writeExpr.loc.start;
const { line, column } = writeExpr.loc.start;
details.message = `Variable '${expression.name}' reassigned with unsafe value at ${line}:${column}`;

@@ -234,3 +296,3 @@ }

*
* @param {Object} callee Function that is being called expression.tag
* @param {object} callee Function that is being called expression.tag
* or expression.callee

@@ -253,4 +315,4 @@ * @param {Array} allowedSanitizers List of valid wrapping expressions

*
* @param {Object} node A callable expression to be simplified
* @returns {Object} Method and (if applicable) object name
* @param {object} node A callable expression to be simplified
* @returns {object} Method and (if applicable) object name
*/

@@ -261,26 +323,34 @@ normalizeMethodCall(node) {

switch (node.type) {
case "Identifier":
methodName = node.name;
break;
case "MemberExpression":
methodName = node.property.name;
objectName = node.object.name || this.context.getSource(node.object);
break;
case "ConditionalExpression":
case "CallExpression":
case "ArrowFunctionExpression":
methodName = "";
break;
case "AssignmentExpression":
methodName = this.normalizeMethodCall(node.right);
break;
case "Import":
methodName = "import";
break;
default:
this.reportUnsupported(node, "Unexpected callable", `unexpected ${node.type} in normalizeMethodCall`);
case "Identifier":
methodName = node.name;
break;
case "MemberExpression":
methodName = node.property.name;
objectName =
node.object.name ||
(this.context.sourceCode?.getText
? this.context.sourceCode.getText(node.object)
: this.context.getSource(node.object));
break;
case "ConditionalExpression":
case "CallExpression":
case "ArrowFunctionExpression":
methodName = "";
break;
case "AssignmentExpression":
methodName = this.normalizeMethodCall(node.right);
break;
case "Import":
methodName = "import";
break;
default:
this.reportUnsupported(
node,
"Unexpected callable",
`unexpected ${node.type} in normalizeMethodCall`
);
}
return {
objectName,
methodName
methodName,
};

@@ -292,4 +362,4 @@ },

*
* @param {Object} node A callable expression
* @returns {String} A nice name to expression call
* @param {object} node A callable expression
* @returns {string} A nice name to expression call
*/

@@ -311,6 +381,7 @@ getCodeName(node) {

* Checks if there are objectMatches we need to apply
* @param {Object} node Call expression node
* @param {Object} objectMatches Strings that are checked as regex to
*
* @param {object} node Call expression node
* @param {object} objectMatches Strings that are checked as regex to
* match an object name
* @returns {Boolean} Returns whether to run checks expression
* @returns {boolean} Returns whether to run checks expression
*/

@@ -322,4 +393,7 @@ shouldCheckMethodCall(node, objectMatches) {

// Allow methods named "import":
if (normalizedMethodCall.methodName === "import"
&& node.callee && node.callee.type === "MemberExpression") {
if (
normalizedMethodCall.methodName === "import" &&
node.callee &&
node.callee.type === "MemberExpression"
) {
return false;

@@ -340,3 +414,6 @@ }

// If we do have object filters and the call is a function then it should not be checked
if ("objectName" in normalizedMethodCall && normalizedMethodCall.objectName) {
if (
"objectName" in normalizedMethodCall &&
normalizedMethodCall.objectName
) {
for (const objectMatch of objectMatches) {

@@ -358,4 +435,5 @@ const match = new RegExp(objectMatch, "gi");

* Algorithm used to decide on merging ruleChecks with this.context
* @param {Object} defaultRuleChecks Object containing default rules
* @returns {Object} The merged ruleChecks
*
* @param {object} defaultRuleChecks Object containing default rules
* @returns {object} The merged ruleChecks
*/

@@ -367,5 +445,11 @@ combineRuleChecks(defaultRuleChecks) {

if (!("defaultDisable" in parentRuleChecks)
|| !parentRuleChecks.defaultDisable) {
childRuleChecks = Object.assign({}, defaultRuleChecks, childRuleChecks);
if (
!("defaultDisable" in parentRuleChecks) ||
!parentRuleChecks.defaultDisable
) {
childRuleChecks = Object.assign(
{},
defaultRuleChecks,
childRuleChecks
);
}

@@ -376,3 +460,4 @@

if ("variableTracing" in parentRuleChecks) {
ruleCheckOutput["variableTracing"] = !!parentRuleChecks["variableTracing"];
ruleCheckOutput["variableTracing"] =
!!parentRuleChecks["variableTracing"];
}

@@ -384,12 +469,20 @@

const ruleCheck = Object.assign(
"defaultDisable" in parentRuleChecks ? {} :
{
escape: {
taggedTemplates: ["Sanitizer.escapeHTML", "escapeHTML"],
methods: ["Sanitizer.unwrapSafeHTML", "unwrapSafeHTML"]
}
},
"defaultDisable" in parentRuleChecks
? {}
: {
escape: {
taggedTemplates: [
"Sanitizer.escapeHTML",
"escapeHTML",
],
methods: [
"Sanitizer.unwrapSafeHTML",
"unwrapSafeHTML",
],
},
},
defaultRuleChecks[ruleCheckKey],
parentRuleChecks,
childRuleChecks[ruleCheckKey]);
childRuleChecks[ruleCheckKey]
);
ruleCheckOutput[ruleCheckKey] = ruleCheck;

@@ -403,3 +496,4 @@ });

* Runs the checks against a CallExpression
* @param {Object} node Call expression node
*
* @param {object} node Call expression node
* @returns {undefined} Does not return

@@ -414,3 +508,6 @@ */

if (!Array.isArray(ruleCheck.properties)) {
this.context.report(node, `Method check requires properties array in eslint rule ${methodName}`);
this.context.report(
node,
`Method check requires properties array in eslint rule ${methodName}`
);
return;

@@ -426,11 +523,19 @@ }

const details = {};
if (this.shouldCheckMethodCall(node, ruleCheck.objectMatches)
&& !this.allowedExpression(argument, ruleCheck.escape, details)) {
if (
this.shouldCheckMethodCall(node, ruleCheck.objectMatches) &&
!this.allowedExpression(argument, ruleCheck.escape, details)
) {
// Include the additional details if available (e.g. name of a disallowed variable
// and the position of the expression that made it disallowed).
if (details.message) {
this.context.report(node, `Unsafe call to ${this.getCodeName(node.callee)} for argument ${propertyId} (${details.message})`);
this.context.report(
node,
`Unsafe call to ${this.getCodeName(node.callee)} for argument ${propertyId} (${details.message})`
);
return;
}
this.context.report(node, `Unsafe call to ${this.getCodeName(node.callee)} for argument ${propertyId}`);
this.context.report(
node,
`Unsafe call to ${this.getCodeName(node.callee)} for argument ${propertyId}`
);
}

@@ -443,17 +548,31 @@ });

* Runs the checks against an assignment expression
* @param {Object} node Assignment expression node
*
* @param {object} node Assignment expression node
* @returns {undefined} Does not return
*/
checkProperty(node) {
if (Object.prototype.hasOwnProperty.call(this.ruleChecks, node.left.property.name)) {
if (
Object.prototype.hasOwnProperty.call(
this.ruleChecks,
node.left.property.name
)
) {
const ruleCheck = this.ruleChecks[node.left.property.name];
const details = {};
if (!this.allowedExpression(node.right, ruleCheck.escape, details)) {
if (
!this.allowedExpression(node.right, ruleCheck.escape, details)
) {
// Include the additional details if available (e.g. name of a disallowed variable
// and the position of the expression that made it disallowed).
if (details.message) {
this.context.report(node, `Unsafe assignment to ${node.left.property.name} (${details.message})`);
this.context.report(
node,
`Unsafe assignment to ${node.left.property.name} (${details.message})`
);
return;
}
this.context.report(node, `Unsafe assignment to ${node.left.property.name}`);
this.context.report(
node,
`Unsafe assignment to ${node.left.property.name}`
);
}

@@ -465,7 +584,9 @@ }

const bugPath = `https://github.com/mozilla/eslint-plugin-no-unsanitized/issues/new?title=${encodeURIComponent(errorTitle)}`;
this.context.report(node, `Error in no-unsanitized: ${reason}. Please report a minimal code snippet to the developers at ${bugPath}`);
}
this.context.report(
node,
`Error in no-unsanitized: ${reason}. Please report a minimal code snippet to the developers at ${bugPath}`
);
},
};
module.exports = RuleHelper;
/**
* @fileoverview ESLint rule to disallow unsanitized method calls
* @file ESLint rule to disallow unsanitized method calls
* @author Frederik Braun et al.

@@ -14,14 +14,11 @@ * @copyright 2015-2017 Mozilla Corporation. All rights reserved.

const defaultRuleChecks = {
// check second parameter to .insertAdjacentHTML()
insertAdjacentHTML: {
properties: [1]
properties: [1],
},
// check first parameter of import()
import: {
properties: [0]
properties: [0],
},

@@ -31,3 +28,3 @@

createContextualFragment: {
properties: [0]
properties: [0],
},

@@ -37,6 +34,4 @@

write: {
objectMatches: [
"document"
],
properties: [0]
objectMatches: ["document"],
properties: [0],
},

@@ -46,7 +41,10 @@

writeln: {
objectMatches: [
"document"
],
properties: [0]
}
objectMatches: ["document"],
properties: [0],
},
// check first parameter to `setHTMLUnsafe()`
setHTMLUnsafe: {
properties: [0],
},
};

@@ -56,8 +54,12 @@

* On newer parsers, `import(foo)` gets parsed as a keyword.
* @param {Object} ruleHelper a RuleHelper instance
* @param {Object} importExpr The ImportExpression we triggered on
*
* @param {object} ruleHelper a RuleHelper instance
* @param {object} importExpr The ImportExpression we triggered on
* @returns {undefined} Does not return
*/
function checkImport(ruleHelper, importExpr) {
const fakeCall = {callee: {type: "Import"}, arguments: [importExpr.source]};
const fakeCall = {
callee: { type: "Import" },
arguments: [importExpr.source],
};
Object.assign(fakeCall, importExpr);

@@ -69,86 +71,91 @@ ruleHelper.checkMethod(fakeCall);

* Run ruleHelper.checkMethod for all but irrelevant callees (FunctionExpression, etc.)
* @param {Object} ruleHelper a RuleHelper instance
* @param {Object} callExpr The CallExpression we triggered on
* @param {Object} node The callee node
*
* @param {object} ruleHelper a RuleHelper instance
* @param {object} callExpr The CallExpression we triggered on
* @param {object} node The callee node
* @returns {undefined} Does not return
*/
function checkCallExpression(ruleHelper, callExpr, node) {
switch(node.type) {
case "Identifier":
case "MemberExpression":
if (callExpr.arguments && callExpr.arguments.length > 0) {
ruleHelper.checkMethod(callExpr);
switch (node.type) {
case "Identifier":
case "MemberExpression":
if (callExpr.arguments && callExpr.arguments.length > 0) {
ruleHelper.checkMethod(callExpr);
}
break;
case "TSNonNullExpression": {
const newCallExpr = Object.assign({}, callExpr);
newCallExpr.callee = node.expression;
checkCallExpression(ruleHelper, newCallExpr, node.expression);
break;
}
break;
case "TSNonNullExpression": {
const newCallExpr = Object.assign({}, callExpr);
newCallExpr.callee = node.expression;
checkCallExpression(ruleHelper, newCallExpr, node.expression);
break;
}
case "TaggedTemplateExpression": {
const newCallExpr = Object.assign({}, callExpr);
newCallExpr.callee = node.tag;
const expressions = node.quasi.expressions;
const strings = node.quasi.quasis;
newCallExpr.arguments = [strings, ...expressions];
checkCallExpression(ruleHelper, newCallExpr, node.tag);
break;
}
case "TaggedTemplateExpression": {
const newCallExpr = Object.assign({}, callExpr);
newCallExpr.callee = node.tag;
const expressions = node.quasi.expressions;
const strings = node.quasi.quasis;
newCallExpr.arguments = [strings, ...expressions];
checkCallExpression(ruleHelper, newCallExpr, node.tag);
break;
}
case "TypeCastExpression": {
const newCallExpr = Object.assign({}, callExpr);
newCallExpr.callee = node.expression;
checkCallExpression(ruleHelper, newCallExpr, node.expression);
break;
}
case "AssignmentExpression":
if (node.right.type === "MemberExpression") {
case "TypeCastExpression": {
const newCallExpr = Object.assign({}, callExpr);
newCallExpr.callee = node.right;
checkCallExpression(ruleHelper, newCallExpr, node.right);
newCallExpr.callee = node.expression;
checkCallExpression(ruleHelper, newCallExpr, node.expression);
break;
}
checkCallExpression(ruleHelper, callExpr, node.right);
break;
case "Import":
ruleHelper.checkMethod(callExpr);
break;
case "AssignmentExpression":
if (node.right.type === "MemberExpression") {
const newCallExpr = Object.assign({}, callExpr);
newCallExpr.callee = node.right;
checkCallExpression(ruleHelper, newCallExpr, node.right);
break;
}
checkCallExpression(ruleHelper, callExpr, node.right);
break;
case "SequenceExpression": {
// the return value of a SequenceExpression is the last expression.
// So, we create a new mock CallExpression with the actually called
// ... expression as the callee node and pass it to checkMethod()
case "Import":
ruleHelper.checkMethod(callExpr);
break;
const newCallExpr = Object.assign({}, callExpr);
const idx = node.expressions.length - 1;
const called = node.expressions[idx];
newCallExpr.callee = called;
ruleHelper.checkMethod(newCallExpr);
break;
}
case "SequenceExpression": {
// the return value of a SequenceExpression is the last expression.
// So, we create a new mock CallExpression with the actually called
// ... expression as the callee node and pass it to checkMethod()
case "TSAsExpression":
break;
const newCallExpr = Object.assign({}, callExpr);
const idx = node.expressions.length - 1;
const called = node.expressions[idx];
newCallExpr.callee = called;
ruleHelper.checkMethod(newCallExpr);
break;
}
// those are fine:
case "LogicalExpression": // Should we scan these? issue #62.
case "ConditionalExpression":
case "ArrowFunctionExpression":
case "FunctionExpression":
case "Super":
case "CallExpression":
case "ThisExpression":
case "NewExpression":
case "TSTypeAssertion":
case "AwaitExpression": // see issue #122
break;
case "TSAsExpression":
break;
// If we don't cater for this expression throw an error
default:
ruleHelper.reportUnsupported(node, "Unexpected Callee", `Unsupported Callee of type ${node.type} for CallExpression`);
// those are fine:
case "LogicalExpression": // Should we scan these? issue #62.
case "ConditionalExpression":
case "ArrowFunctionExpression":
case "FunctionExpression":
case "Super":
case "CallExpression":
case "ThisExpression":
case "NewExpression":
case "TSTypeAssertion":
case "AwaitExpression": // see issue #122
break;
// If we don't cater for this expression throw an error
default:
ruleHelper.reportUnsupported(
node,
"Unexpected Callee",
`Unsupported Callee of type ${node.type} for CallExpression`
);
}

@@ -163,10 +170,38 @@ }

category: "possible-errors",
url: "https://github.com/mozilla/eslint-plugin-no-unsanitized/tree/master/docs/rules/method.md"
url: "https://github.com/mozilla/eslint-plugin-no-unsanitized/tree/master/docs/rules/method.md",
},
/* schema statement TBD until we have options
schema: [
{
type: array
}
]*/
type: "object",
properties: {
defaultDisable: {
type: "boolean",
},
escape: {
type: "object",
properties: {
taggedTemplates: {
type: "array",
items: [{ type: "string" }],
},
methods: {
type: "array",
items: [{ type: "string" }],
},
},
},
objectMatches: {
type: "array",
},
properties: {
type: "array",
},
variableTracing: { type: "boolean" },
},
additionalProperties: false,
},
{
type: "object",
},
],
},

@@ -195,3 +230,3 @@ create(context) {

};
}
},
};
/**
* @fileoverview ESLint rule to disallow unsanitized property assignment
* @file ESLint rule to disallow unsanitized property assignment
* @author Frederik Braun et al.

@@ -14,12 +14,8 @@ * @copyright 2015-2017 Mozilla Corporation. All rights reserved.

const defaultRuleChecks = {
// Check unsafe assignment to innerHTML
innerHTML: {
},
innerHTML: {},
// Check unsafe assignment to outerHTML
outerHTML: {
}
outerHTML: {},
};

@@ -31,12 +27,32 @@

docs: {
description: "ESLint rule to disallow unsanitized property assignment",
description:
"ESLint rule to disallow unsanitized property assignment",
category: "possible-errors",
url: "https://github.com/mozilla/eslint-plugin-no-unsanitized/tree/master/docs/rules/property.md"
url: "https://github.com/mozilla/eslint-plugin-no-unsanitized/tree/master/docs/rules/property.md",
},
/* schema statement TBD until we have options
schema: [
{
type: array
}
]*/
type: "object",
properties: {
escape: {
type: "object",
properties: {
taggedTemplates: {
type: "array",
items: [{ type: "string" }],
},
methods: {
type: "array",
items: [{ type: "string" }],
},
},
},
variableTracing: { type: "boolean" },
},
additionalProperties: false,
},
{
type: "object",
},
],
},

@@ -50,3 +66,15 @@ create(context) {

// - https://github.com/estree/estree/blob/master/es2016.md#assignmentoperator
const PERMITTED_OPERATORS = ["-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "|=", "^=", "&=", "**="];
const PERMITTED_OPERATORS = [
"-=",
"*=",
"/=",
"%=",
"<<=",
">>=",
">>>=",
"|=",
"^=",
"&=",
"**=",
];

@@ -63,4 +91,11 @@ // operators to match against, such as X.innerHTML += foo

if (PERMITTED_OPERATORS.indexOf(node.operator) === -1) {
if (CHECK_REQUIRED_OPERATORS.indexOf(node.operator) === -1) {
ruleHelper.reportUnsupported(node, "Unexpected Assignment", `Unsupported Operator ${encodeURIComponent(node.operator)} for AssignmentExpression`);
if (
CHECK_REQUIRED_OPERATORS.indexOf(node.operator) ===
-1
) {
ruleHelper.reportUnsupported(
node,
"Unexpected Assignment",
`Unsupported Operator ${encodeURIComponent(node.operator)} for AssignmentExpression`
);
}

@@ -70,5 +105,5 @@ ruleHelper.checkProperty(node);

}
}
},
};
}
},
};
{
"name": "eslint-plugin-no-unsanitized",
"description": "ESLint rule to disallow unsanitized code",
"version": "4.0.2",
"version": "4.1.0",
"author": {

@@ -14,13 +14,15 @@ "name": "Frederik Braun et al."

"@babel/eslint-parser": "^7.15.8",
"@babel/plugin-proposal-logical-assignment-operators": "^7.11.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4",
"@babel/plugin-syntax-flow": "^7.16.0",
"@typescript-eslint/parser": "^5.2.0",
"eslint": "^8.1.0",
"@typescript-eslint/parser": "^8.0.0",
"eslint": "9.0.0 || ^8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-jsdoc": "48.2.3",
"globals": "^15.0.0",
"mocha": "^9.2.0",
"nyc": "^15.1.0",
"typescript": "^3.9.7"
"prettier": "3.2.5",
"typescript": ">=4.7.4"
},
"peerDependencies": {
"eslint": "^6 || ^7 || ^8"
"eslint": "^8 || ^9"
},

@@ -45,3 +47,3 @@ "homepage": "https://github.com/mozilla/eslint-plugin-no-unsanitized/",

"test": "nyc mocha tests/rules/",
"lint": "eslint ."
"lint": "eslint . && prettier --check ."
},

@@ -56,3 +58,4 @@ "files": [

"lib"
]
],
"dependencies": {}
}
[![Build Status](https://travis-ci.org/mozilla/eslint-plugin-no-unsanitized.svg?branch=master)](https://travis-ci.org/mozilla/eslint-plugin-no-unsanitized)
# Disallow unsanitized code (no-unsanitized)

@@ -19,3 +20,4 @@

## method
The *method* rule disallows certain function calls.
The _method_ rule disallows certain function calls.
E.g., `document.write()` or `insertAdjacentHTML()`.

@@ -25,7 +27,7 @@ See [docs/rules/method.md](docs/rules/method.md) for more.

## property
The *property* rule disallows certain assignment expressions, e.g., to `innerHTML`.
The _property_ rule disallows certain assignment expressions, e.g., to `innerHTML`.
See [docs/rules/property.md](docs/rules/property.md) for more.
## Examples

@@ -37,3 +39,3 @@

foo.innerHTML = input.value;
bar.innerHTML = "<a href='"+url+"'>About</a>";
bar.innerHTML = "<a href='" + url + "'>About</a>";
```

@@ -49,9 +51,7 @@

# Install
With **yarn** or **npm**:
```
```bash
$ yarn add -D eslint-plugin-no-unsanitized

@@ -63,7 +63,34 @@ $ npm install --save-dev eslint-plugin-no-unsanitized

### Flat config
```js
import nounsanitized from "eslint-plugin-no-unsanitized";
export default config = [nounsanitized.configs.recommended];
```
or
```js
import nounsanitized from "eslint-plugin-no-unsanitized";
export default config = [
{
files: ["**/*.js"],
plugins: { nounsanitized },
rules: {
"no-unsanitized/method": "error",
"no-unsanitized/property": "error",
},
},
];
```
### eslintrc
In your `.eslintrc.json` file enable this rule with the following:
```
```json
{
"extends": ["plugin:no-unsanitized/DOM"]
"extends": ["plugin:no-unsanitized/recommended-legacy"]
}

@@ -73,3 +100,4 @@ ```

Or:
```
```json
{

@@ -85,2 +113,3 @@ "plugins": ["no-unsanitized"],

# Documentation
See [docs/](docs/).

@@ -9,3 +9,5 @@ # Security Policy

## Our Threat Model
### What is considered a vulnerability?
We assume that a project which makes use of this plugin to apply

@@ -15,3 +17,3 @@ a reasonable amount of scrutiny to all patches that are accepted.

judgement and sensitivity to security issues will vary between
projects and reviewers.
projects and reviewers.

@@ -25,2 +27,3 @@ JavaScript static analysis is always very limited. On top we are

Here's a a couple of examples which we do **not** consider a bug in the linter:
```js

@@ -34,4 +37,4 @@ foo['inner'+'HTML'] = evil; // no way to resolve concatenation

## Reporting a Vulnerability
## Reporting a Vulnerability
### Bug Bounty / Vulnerability Rewards

@@ -50,10 +53,11 @@

### Sneaking your *obfuscated* code past this linter
### Sneaking your _obfuscated_ code past this linter
Hey, did you read "What is considered a vulnerability", above?
### Sneaking your innocent looking code past the linter
If what you found constitues in a code pattern that eslint should complain about, but doesn't,
[please file a private bug Bugzilla using this form][bugzilla-enter-private-issue] (requires creating an account).
[bugzilla-enter-private-issue]: https://bugzilla.mozilla.org/enter_bug.cgi?assigned_to=nobody%40mozilla.org&bug_ignored=0&bug_severity=--&bug_status=UNCONFIRMED&bug_type=defect&cf_fission_milestone=---&cf_fx_iteration=---&cf_fx_points=---&cf_root_cause=---&cf_status_firefox77=---&cf_status_firefox78=---&cf_status_firefox79=---&cf_status_firefox_esr68=---&cf_status_firefox_esr78=---&cf_status_thunderbird_esr60=---&cf_status_thunderbird_esr68=---&cf_tracking_firefox77=---&cf_tracking_firefox78=---&cf_tracking_firefox79=---&cf_tracking_firefox_esr68=---&cf_tracking_firefox_esr78=---&cf_tracking_firefox_relnote=---&cf_tracking_thunderbird_esr60=---&cf_tracking_thunderbird_esr68=---&component=Lint%20and%20Formatting&contenttypemethod=list&contenttypeselection=text%2Fplain&defined_groups=1&filed_via=standard_form&flag_type-203=X&flag_type-37=X&flag_type-4=X&flag_type-41=X&flag_type-607=X&flag_type-721=X&flag_type-737=X&flag_type-787=X&flag_type-799=X&flag_type-800=X&flag_type-803=X&flag_type-846=X&flag_type-855=X&flag_type-864=X&flag_type-913=X&flag_type-930=X&flag_type-936=X&flag_type-941=X&flag_type-945=X&form_name=enter_bug&groups=firefox-core-security&maketemplate=Remember%20values%20as%20bookmarkable%20template&op_sys=Unspecified&priority=--&product=Firefox%20Build%20System&rep_platform=Unspecified&status_whiteboard=%5Beslint-plugin-no-unsanitized%5D&target_milestone=---&version=unspecified
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc