Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ jobs:

- name: Test
run: yarn run test

- name: Test (integration tests)
run: yarn run test-integration
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ $ npm install --save-dev eslint-plugin-no-unsanitized
```js
import nounsanitized from "eslint-plugin-no-unsanitized";

export default config = [nounsanitized.configs.recommended];
export default [...nounsanitized.configs.recommended];
```

or

```js
import nounsanitized from "eslint-plugin-no-unsanitized";

export default config = [
export default [
{
files: ["**/*.js"],
plugins: { nounsanitized },
Expand Down
13 changes: 13 additions & 0 deletions docs/rules/parsing_method.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# parsing_method

The _parsing_method_ rule in _eslint-plugin-no-unsanitized_ performs basic security
checks for function calls that parse strings into new Document or DocumentFragment
instances. The idea of these checks is to allow developers to opt-in/opt-out of detecting
use of these methods, separately from the `method` rule which is reporting violation
as errors by default.
Comment on lines +3 to +7

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The _parsing_method_ rule in _eslint-plugin-no-unsanitized_ performs basic security
checks for function calls that parse strings into new Document or DocumentFragment
instances. The idea of these checks is to allow developers to opt-in/opt-out of detecting
use of these methods, separately from the `method` rule which is reporting violation
as errors by default.
The _parsing_method_ rule in _eslint-plugin-no-unsanitized_ performs basic security
checks for function calls that parse strings into new Document or DocumentFragment
instances. It is close to impossible to automatically check whether the resulting
Document is actually used insecurely, so this offers a control at the parse step.
Therefore, these checks allow developers to opt-in/opt-out of detecting
use of these methods, separately from the `method` and `property` rule which is
reporting violation as errors by default.


### 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
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ const plugin = {
rules: {
property: require("./lib/rules/property"),
method: require("./lib/rules/method"),
parsing_method: require("./lib/rules/parsing_method"),
},
configs: {},
};

const rules = {
"no-unsanitized/property": "error",
"no-unsanitized/method": "error",
"no-unsanitized/parsing_method": "warn",
Comment thread
rpl marked this conversation as resolved.
};

Object.assign(plugin.configs, {
Expand Down
194 changes: 194 additions & 0 deletions lib/base_method.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/**
* @file ESLint rule to disallow unsanitized method calls
* @author Frederik Braun et al.
* @copyright 2015-2017 Mozilla Corporation. All rights reserved.
*/
"use strict";

const RuleHelper = require("./ruleHelper");

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/**
* On newer parsers, `import(foo)` gets parsed as a keyword.
*
* @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],
};
Object.assign(fakeCall, importExpr);
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
* @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);
}
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 "TypeCastExpression": {
const newCallExpr = Object.assign({}, callExpr);
newCallExpr.callee = node.expression;
checkCallExpression(ruleHelper, newCallExpr, node.expression);
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 "Import":
ruleHelper.checkMethod(callExpr);
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()

const newCallExpr = Object.assign({}, callExpr);
const idx = node.expressions.length - 1;
const called = node.expressions[idx];
newCallExpr.callee = called;
ruleHelper.checkMethod(newCallExpr);
break;
}

case "TSAsExpression":
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;

// 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`
);
}
}

const defaultMeta = {
type: "problem",
// Expected to be filled by the concrete rules through the exported createMethodsRule function.
docs: {},
schema: [
{
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",
},
],
};

module.exports = function createMethodsRule({ meta, defaultRuleChecks }) {
return {
meta: {
...defaultMeta,
...meta,
},
create(context) {
const ruleHelper = new RuleHelper(context, defaultRuleChecks);
return {
CallExpression(node) {
checkCallExpression(ruleHelper, node, node.callee);
},
ImportExpression(node) {
checkImport(ruleHelper, node);
},

// Tagged template expressions pass arguments in a special format we need to
// map to our existing function call logic
// foo`bar${var1}${var2}` will run as foo(['bar', ''], var1, var2)
TaggedTemplateExpression(node) {
const newCallExpr = Object.assign({}, node);
newCallExpr.callee = node.tag;
const expressions = node.quasi.expressions;
const strings = node.quasi.quasis;
newCallExpr.arguments = [strings, ...expressions];
checkCallExpression(ruleHelper, newCallExpr, node.tag);
},
};
},
};
};
10 changes: 8 additions & 2 deletions lib/ruleHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -501,16 +501,22 @@ RuleHelper.prototype = {
) {
// Include the additional details if available (e.g. name of a disallowed variable
// and the position of the expression that made it disallowed).
const baseMessage =
typeof ruleCheck.getCustomMessage === "function"
? ruleCheck.getCustomMessage(
this.getCodeName(node.callee)
)
: `Unsafe call to ${this.getCodeName(node.callee)}`;
if (details.message) {
this.context.report(
node,
`Unsafe call to ${this.getCodeName(node.callee)} for argument ${propertyId} (${details.message})`
`${baseMessage} for argument ${propertyId} (${details.message})`
);
return;
}
this.context.report(
node,
`Unsafe call to ${this.getCodeName(node.callee)} for argument ${propertyId}`
`${baseMessage} for argument ${propertyId}`
);
}
});
Expand Down
Loading