diff --git a/docs/description/description.json b/docs/description/description.json index 5b3bc2d35..ec0f1993e 100644 --- a/docs/description/description.json +++ b/docs/description/description.json @@ -3094,13 +3094,6 @@ "description": "Ensures component's `changeDetection` is set to `ChangeDetectionStrategy.OnPush`", "timeToFix": 5 }, - { - "parameters": [], - "patternId": "@angular-eslint_prefer-standalone-component", - "title": "@angular eslint: Prefer standalone component", - "description": "Ensures component `standalone` property is set to `true` in the component decorator", - "timeToFix": 5 - }, { "parameters": [], "patternId": "@angular-eslint_prefer-output-readonly", @@ -3884,6 +3877,18 @@ "description": "Disallow duplicate enum member values", "timeToFix": 5 }, + { + "parameters": [ + { + "name": "includeExports", + "description": "includeExports" + } + ], + "patternId": "@typescript-eslint_no-duplicate-imports", + "title": "@typescript eslint: No duplicate imports", + "description": "Disallow duplicate imports", + "timeToFix": 5 + }, { "parameters": [], "patternId": "@typescript-eslint_no-duplicate-type-constituents", @@ -3961,6 +3966,13 @@ "description": "Disallow iterating over an array with a for-in loop", "timeToFix": 5 }, + { + "parameters": [], + "patternId": "@typescript-eslint_no-implicit-any-catch", + "title": "@typescript eslint: No implicit any catch", + "description": "Disallow usage of the implicit `any` type in catch clauses", + "timeToFix": 5 + }, { "parameters": [], "patternId": "@typescript-eslint_no-implied-eval", @@ -4391,7 +4403,7 @@ "parameters": [], "patternId": "@typescript-eslint_prefer-nullish-coalescing", "title": "@typescript eslint: Prefer nullish coalescing", - "description": "Enforce using the nullish coalescing operator instead of logical assignments or chaining", + "description": "Enforce using the nullish coalescing operator instead of logical chaining", "timeToFix": 5 }, { @@ -4513,6 +4525,13 @@ "description": "Enforce constituents of a type union/intersection to be sorted alphabetically", "timeToFix": 5 }, + { + "parameters": [], + "patternId": "@typescript-eslint_sort-type-union-intersection-members", + "title": "@typescript eslint: Sort type union intersection members", + "description": "Enforce members of a type union/intersection to be sorted alphabetically", + "timeToFix": 5 + }, { "parameters": [], "patternId": "@typescript-eslint_space-before-blocks", @@ -7423,7 +7442,7 @@ "parameters": [], "patternId": "eslint-plugin_prefer-message-ids", "title": "Eslint plugin: Prefer message ids", - "description": "Require using `messageId` instead of `message` or `desc` to report rule violations", + "description": "Require using `messageId` instead of `message` to report rule violations", "timeToFix": 5 }, { @@ -13442,6 +13461,12 @@ "title": "Scanjs rules: Assign_to_src", "timeToFix": 5 }, + { + "parameters": [], + "patternId": "scanjs-rules_call__Function", + "title": "Scanjs rules: Call_Function", + "timeToFix": 5 + }, { "parameters": [], "patternId": "scanjs-rules_call__addEventListener", @@ -13478,12 +13503,6 @@ "title": "Scanjs rules: Call_execScript", "timeToFix": 5 }, - { - "parameters": [], - "patternId": "scanjs-rules_call__Function", - "title": "Scanjs rules: Call_Function", - "timeToFix": 5 - }, { "parameters": [], "patternId": "scanjs-rules_call__hide", @@ -14573,6 +14592,13 @@ "description": "Disallow unreadable IIFEs.", "timeToFix": 5 }, + { + "parameters": [], + "patternId": "unicorn_no-unsafe-regex", + "title": "Unicorn: No unsafe regex", + "description": "Disallow unsafe regular expressions.", + "timeToFix": 5 + }, { "parameters": [], "patternId": "unicorn_no-unused-properties", @@ -14718,12 +14744,6 @@ "description": "Require `new` when throwing an error.", "timeToFix": 5 }, - { - "parameters": [], - "patternId": "unicorn_no-unsafe-regex", - "title": "Unicorn: No unsafe regex", - "timeToFix": 5 - }, { "parameters": [], "patternId": "unused-imports_no-unused-vars", @@ -17268,4 +17288,4 @@ "title": "You dont need lodash underscore: Is function", "timeToFix": 5 } -] +] diff --git a/docs/description/react_prefer-read-only-props.md b/docs/description/react_prefer-read-only-props.md index 3e3d25f52..955923582 100644 --- a/docs/description/react_prefer-read-only-props.md +++ b/docs/description/react_prefer-read-only-props.md @@ -10,6 +10,8 @@ Using Flow, one can define types for props. This rule enforces that prop types a Examples of **incorrect** code for this rule: +In Flow: + ```jsx type Props = { name: string, @@ -29,8 +31,32 @@ const Hello = (props: {|name: string|}) => ( ); ``` +In TypeScript: + +```tsx +type Props = { + name: string; +} +class Hello extends React.Component { + render () { + return
Hello {this.props.name}
; + } +} + +interface Props { + name: string; +} +class Hello extends React.Component { + render () { + return
Hello {this.props.name}
; + } +} +``` + Examples of **correct** code for this rule: +In Flow: + ```jsx type Props = { +name: string, @@ -49,3 +75,25 @@ const Hello = (props: {|+name: string|}) => (
Hello {props.name}
); ``` + +In TypeScript: + +```tsx +type Props = { + readonly name: string; +} +class Hello extends React.Component { + render () { + return
Hello {this.props.name}
; + } +} + +interface Props { + readonly name: string; +} +class Hello extends React.Component { + render () { + return
Hello {this.props.name}
; + } +} +``` diff --git a/docs/description/security_detect-bidi-characters.md b/docs/description/security_detect-bidi-characters.md new file mode 100644 index 000000000..dd6d1b282 --- /dev/null +++ b/docs/description/security_detect-bidi-characters.md @@ -0,0 +1,50 @@ +# Detects trojan source attacks that employ unicode bidi attacks to inject malicious code (`security/detect-bidi-characters`) + +⚠️ This rule _warns_ in the ✅ `recommended` config. + + + +Detects cases of [trojan source attacks](https://trojansource.codes) that employ unicode bidi attacks to inject malicious code + +## Why is Trojan Source important? + +The following publication on the topic of unicode characters attacks, dubbed [Trojan Source: Invisible Vulnerabilities](https://trojansource.codes/trojan-source.pdf), has caused a lot of concern from potential supply chain attacks where adversaries are able to inject malicious code into the source code of a project, slipping by unseen in the code review process. + +### An example + +As an example, take the following code where `RLO`, `LRI`, `PDI`, `IRI` are placeholders to visualise the respective dangerous unicode characters: + +```js +#!/usr/bin/env node + +var accessLevel = 'user'; + +if (accessLevel != 'userRLO LRI// Check if adminPDI IRI') { + console.log('You are an admin.'); +} +``` + +The code above, will be rendered by a text editor as follows: + +```js +#!/usr/bin/env node + +var accessLevel = 'user'; + +if (accessLevel != 'user') { + // Check if admin + console.log('You are an admin.'); +} +``` + +By looking at the rendered code above, a user reviewing this code might not notice the injected malicious unicode characters which are actually changing the semantic and the behaviour of the actual code. + +### More information + +For more information on the topic, you're welcome to read on the official website [trojansource.codes](https://trojansource.codes/) and the following [source code repository](https://github.com/nickboucher/trojan-source/) which contains the source code of the publication. + +### References + +- +- +- diff --git a/docs/description/security_detect-buffer-noassert.md b/docs/description/security_detect-buffer-noassert.md new file mode 100644 index 000000000..0220bf30c --- /dev/null +++ b/docs/description/security_detect-buffer-noassert.md @@ -0,0 +1,9 @@ +# Detects calls to "buffer" with "noAssert" flag set (`security/detect-buffer-noassert`) + +⚠️ This rule _warns_ in the ✅ `recommended` config. + + + +Detect calls to [`buffer`](https://nodejs.org/api/buffer.html) with `noAssert` flag set. + +From the Node.js API docs: "Setting `noAssert` to true skips validation of the `offset`. This allows the `offset` to be beyond the end of the `Buffer`." diff --git a/docs/description/security_detect-child-process.md b/docs/description/security_detect-child-process.md new file mode 100644 index 000000000..5fc866d46 --- /dev/null +++ b/docs/description/security_detect-child-process.md @@ -0,0 +1,96 @@ +# Detects instances of "child_process" & non-literal "exec()" calls (`security/detect-child-process`) + +⚠️ This rule _warns_ in the ✅ `recommended` config. + + + +Detect instances of [`child_process`](https://nodejs.org/api/child_process.html) & non-literal [`exec()`](https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback) + +More information: + +# Avoiding Command Injection in Node.js + +In this post we are going to learn about the proper way to call a system command using node.js to avoid a common security flaw, command injection. + +A call that we often see used, due to it's simplicity is `child_process.exec`. It's got a simple pattern; pass in a command string and it calls you back with an error or the command results. + +Here is a very typical way you would call a system command with `child_process.exec.` + +```js +child_process.exec('ls', function (err, data) { + console.log(data); +}); +``` + +What happens though when you need to start getting user input for arguments into your command? The obvious solution is to take the user input and build your command out using string concatenation. But here's something I've learned over the years: When you use string concatenation to send data from one system to another you're probably going to have a bad day. + +```js +var path = 'user input'; +child_process.exec('ls -l ' + path, function (err, data) { + console.log(data); +}); +``` + +## Why is string concatenation a problem? + +Well, because under the hood, `child_process.exec` makes a call to execute /bin/sh rather than the target program. The command that was sent just gets passed along as a shell command in the newly spawned /bin/sh process. `child_process.exec` has a misleading name - it's a bash interpreter, not a program launcher. And that means that all shell metacharacters can have devastating effects if the command is including user input. + +```sh +[pid 25170] execve("/bin/sh", ["/bin/sh", "-c", "ls -l user input"], [/* 16 vars */] +``` + +For example, an attacker could use a ; to end the statement and start another one, they could use backticks or $() to run a subcommand. Lots of potential for abuse. + +## So how do we do this the right way? + +Calls like `spawn` and `execFile` take additional command arguments as an array, are not executed under a shell environment, and do not manipulate the originally intended command to run. + +Let's modify our example to use `execFile` and `spawn` and see how the system calls differ, and why it isn't vulnerable to command injection. + +### `child_process.execFile` + +```js +var child_process = require('child_process'); + +var path = '.'; +child_process.execFile('/bin/ls', ['-l', path], function (err, result) { + console.log(result); +}); +``` + +System call that is run + +```sh +[pid 25565] execve("/bin/ls", ["/bin/ls", "-l", "."], [/* 16 vars */] +``` + +### `child_process.spawn` + +Similar example using `spawn` instead. + +```js +var child_process = require('child_process'); + +var path = '.'; +var ls = child_process.spawn('/bin/ls', ['-l', path]); +ls.stdout.on('data', function (data) { + console.log(data.toString()); +}); +``` + +System call that is run + +```sh +[pid 26883] execve("/bin/ls", ["/bin/ls", "-l", "."], [/* 16 vars */ +``` + +When using `spawn` or `execFile`, our target program is the first argument to execve. This means that a user cannot run subcommands in the shell, because /bin/ls has no idea what to do with backticks or pipes or ;. It's /bin/bash that is going to be interpreting those commands. It's similar to using parameterized vs string-based SQL queries, if you are familiar with that. + +This does however come with a caveat: using `spawn` or `execFile` is not always a safe thing. For example, running /bin/find with `spawn` or `execFile` and passing user input in directly could still lead to complete system takeover. The find command has options that allow for arbitrary file read/write. + +So, here's the collective guidance for running system commands from node.js: + +- Avoid using `child_process.exec`, and never use it if the command contains any input that changes based on user input. +- Try to avoid letting users pass in options to commands if possible. Typically values are okay when using spawn or execfile, but selecting options via a user controlled string is a bad idea. +- If you must allow for user controlled options, look at the options for the command extensively, determine which options are safe, and whitelist only those options. + diff --git a/docs/description/security_detect-disable-mustache-escape.md b/docs/description/security_detect-disable-mustache-escape.md new file mode 100644 index 000000000..af5455cc0 --- /dev/null +++ b/docs/description/security_detect-disable-mustache-escape.md @@ -0,0 +1,9 @@ +# Detects "object.escapeMarkup = false", which can be used with some template engines to disable escaping of HTML entities (`security/detect-disable-mustache-escape`) + +⚠️ This rule _warns_ in the ✅ `recommended` config. + + + +This can lead to Cross-Site Scripting (XSS) vulnerabilities. + +More information: [OWASP XSS]() diff --git a/docs/description/security_detect-eval-with-expression.md b/docs/description/security_detect-eval-with-expression.md new file mode 100644 index 000000000..74333ff67 --- /dev/null +++ b/docs/description/security_detect-eval-with-expression.md @@ -0,0 +1,7 @@ +# Detects "eval(variable)" which can allow an attacker to run arbitrary code inside your process (`security/detect-eval-with-expression`) + +⚠️ This rule _warns_ in the ✅ `recommended` config. + + + +More information: [What are the security issues with eval in JavaScript?](http://security.stackexchange.com/questions/94017/what-are-the-security-issues-with-eval-in-javascript) diff --git a/docs/description/security_detect-new-buffer.md b/docs/description/security_detect-new-buffer.md new file mode 100644 index 000000000..cd8c71c74 --- /dev/null +++ b/docs/description/security_detect-new-buffer.md @@ -0,0 +1,5 @@ +# Detects instances of new Buffer(argument) where argument is any non-literal value (`security/detect-new-buffer`) + +⚠️ This rule _warns_ in the ✅ `recommended` config. + + diff --git a/docs/description/security_detect-no-csrf-before-method-override.md b/docs/description/security_detect-no-csrf-before-method-override.md new file mode 100644 index 000000000..2fd540ecc --- /dev/null +++ b/docs/description/security_detect-no-csrf-before-method-override.md @@ -0,0 +1,53 @@ +# Detects Express "csrf" middleware setup before "method-override" middleware (`security/detect-no-csrf-before-method-override`) + +⚠️ This rule _warns_ in the ✅ `recommended` config. + + + +This can allow `GET` requests (which are not checked by `csrf`) to turn into `POST` requests later. + +More information: + +# Bypass Connect CSRF protection by abusing methodOverride Middleware + +Since our platform isn't setup for advisories that are not specific to a particular module version, but rather a use / configuration of a certain module, we will announce this issue here and get it into the database at a later date. + +This issue was found and reported to us by [Luca Carettoni](http://twitter.com/_ikki) (who we consider one of the node security projects core advisers in many areas) + +## Affected Component + +Connect, methodOverride middleware + +### Description + +**Connect's "methodOverride" middleware allows an HTTP request to override the method of the request with the value of the "\_method" post key or with the header "x-http-method-override".** + +As the declaration order of middlewares determines the execution stack in Connect, it is possible to abuse this functionality in order to bypass the standard Connect's anti-CSRF protection. + +Considering the following code: + +```js +... +app.use express.csrf() +... +app.use express.methodOverride() +``` + +Connect's CSRF middleware does not check csrf tokens in case of idempotent verbs (GET/HEAD/OPTIONS, see lib/middleware/csrf.js). As a result, it is possible to bypass this security control by sending a GET request with a POST MethodOverride header or key. + +### Example + +```sh +GET / HTTP/1.1 +[..] +_method=POST +``` + +### Mitigation Factors + +Disable methodOverride or make sure that it takes precedence over other middleware declarations. + +Thanks to the same origin policy enforced by modern browsers in XMLHttpRequest and other mechanisms, the exploitability of this issue abusing "x-http-method-override" header is significantly reduced. + +There is also an [ESLint plugin](https://github.com/evilpacket/eslint-rules) that you can use to help identify this. + diff --git a/docs/description/security_detect-non-literal-fs-filename.md b/docs/description/security_detect-non-literal-fs-filename.md new file mode 100644 index 000000000..5dae95856 --- /dev/null +++ b/docs/description/security_detect-non-literal-fs-filename.md @@ -0,0 +1,7 @@ +# Detects variable in filename argument of "fs" calls, which might allow an attacker to access anything on your system (`security/detect-non-literal-fs-filename`) + +⚠️ This rule _warns_ in the ✅ `recommended` config. + + + +More information: [OWASP Path Traversal](https://www.owasp.org/index.php/Path_Traversal) diff --git a/docs/description/security_detect-non-literal-regexp.md b/docs/description/security_detect-non-literal-regexp.md new file mode 100644 index 000000000..5d718aa3e --- /dev/null +++ b/docs/description/security_detect-non-literal-regexp.md @@ -0,0 +1,92 @@ +# Detects "RegExp(variable)", which might allow an attacker to DOS your server with a long-running regular expression (`security/detect-non-literal-regexp`) + +⚠️ This rule _warns_ in the ✅ `recommended` config. + + + +More information: + +# Regular Expression DoS and Node.js + +Imagine you are trying to buy a ticket to your favorite JavaScript conference, and instead of getting the ticket page, you instead get `500 Internal Server Error`. For some reason the site is down. You can't do the thing that you want to do most and the conference is losing out on your purchase, all because the application is unavailable. + +Availability is not often treated as a security problem, which it is, and it's impacts are immediate, and deeply felt. + +The attack surface for Node.js in regards to loss of availability is quite large, as we are dealing with a single event loop. If an attacker can control and block that event loop, then nothing else gets done. + +There are many ways to block the event loop, one way an attacker can do that is with [Regular Expression Denial of Service (ReDoS)](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS). + +If user provided input finds it's way into a regular expression, or a regular expression is designed with certain attributes, such as grouping with repetition, you can find yourself in a vulnerable position, as the regular expression match could take a long time to process. [OWASP](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS) has a deeper explanation of why this occurs. + +Let's look at an vulnerable example. Below we are attempting the common task of validating an email address on the server. + +```js +validateEmailFormat: function( string ) { + var emailExpression = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/; + + return emailExpression.test( string ); +} +``` + +With the example above, we can use this test script to show how bad input can impact server responsiveness: + +```js +start = process.hrtime(); +console.log(validateEmailFormat('baldwin@andyet.net')); +console.log(process.hrtime(start)); + +start = process.hrtime(); +console.log(validateEmailFormat('jjjjjjjjjjjjjjjjjjjjjjjjjjjj@ccccccccccccccccccccccccccccc.5555555555555555555555555555555555555555{')); +console.log(process.hrtime(start)); + +start = process.hrtime(); +console.log(validateEmailFormat('jjjjjjjjjjjjjjjjjjjjjjjjjjjj@ccccccccccccccccccccccccccccc.55555555555555555555555555555555555555555{')); +console.log(process.hrtime(start)); + +start = process.hrtime(); +console.log(validateEmailFormat('jjjjjjjjjjjjjjjjjjjjjjjjjjjj@ccccccccccccccccccccccccccccc.555555555555555555555555555555555555555555555555555555{')); +console.log(process.hrtime(start)); +``` + +Here are the results of running that script: + +```sh +true +[ 0, 9694442 ] <- Match on good data takes little time +false +[ 0, 49849962 ] <- Initial bad input baseline +false +[ 0, 55123953 ] <- Added 1 character to the input and you see minimal spike +false +[ 8, 487126563 ] <- Added 12 characters and you see it bumps up significantly +``` + +One way you can check regular expressions for badness in an automated way is by using a module from [substack](https://twitter.com/substack) called [safe-regex](https://www.npmjs.org/package/safe-regex). It's prone to false positives, however, it can be useful to point to potentially vulnerable regular expressions you would have otherwise missed in your code. + +Here is a rule for eslint that you can use to test your JavaScript regular expressions: + +```js +var safe = require('safe-regex'); +module.exports = function (context) { + 'use strict'; + + return { + Literal: function (node) { + var token = context.getTokens(node)[0], + nodeType = token.type, + nodeValue = token.value; + + if (nodeType === 'RegularExpression') { + if (!safe(nodeValue)) { + context.report(node, 'Possible Unsafe Regular Expression'); + } + } + }, + }; +}; +``` + +Additionally, OWASP has a [list of regular expressions](https://www.owasp.org/index.php/OWASP_Validation_Regex_Repository) for common validations that might be useful to you. + +As part of our ongoing effort to increase the overall security of the Node.js ecosystem, we have conducted automated analysis of every module on npm. We did identify 56 unique vulnerable regular expressions and over 120 modules containing vulnerable regular expressions. Considering that there are now over 100k modules, the results were not alarming. We're working closely with the maintainers of each module to get the issues resolved, once that's done, advisories will be published to the [npm Security advisories](https://www.npmjs.com/advisories) site. + diff --git a/docs/description/security_detect-non-literal-require.md b/docs/description/security_detect-non-literal-require.md new file mode 100644 index 000000000..30b54ade7 --- /dev/null +++ b/docs/description/security_detect-non-literal-require.md @@ -0,0 +1,7 @@ +# Detects "require(variable)", which might allow an attacker to load and run arbitrary code, or access arbitrary files on disk (`security/detect-non-literal-require`) + +⚠️ This rule _warns_ in the ✅ `recommended` config. + + + +More information: [Where does Node.js and require look for modules?](http://www.bennadel.com/blog/2169-where-does-node-js-and-require-look-for-modules.htm) diff --git a/docs/description/security_detect-object-injection.md b/docs/description/security_detect-object-injection.md new file mode 100644 index 000000000..f965f08a4 --- /dev/null +++ b/docs/description/security_detect-object-injection.md @@ -0,0 +1,116 @@ +# Detects "variable[key]" as a left- or right-hand assignment operand (`security/detect-object-injection`) + +⚠️ This rule _warns_ in the ✅ `recommended` config. + + + +More information: + +# The Dangers of Square Bracket Notation + +We are going to be looking at some peculiar and potentially dangerous implications of JavaScript's square bracket notation in this post: where you shouldn't use this style of object access and why, as well how to use it safely when needed. + +Square bracket notation for objects in JavaScript provides a very convenient way to dynamically access a specific property or method based on the contents of a variable. The end result of this feature is something that is very similar to Ruby's Mass Assignment: Given an object, you are able to dynamically assign and retrieve properties of this object without specifying this property should be accessible. + +_Note: These examples are simple, and seemingly obvious - we will take a look at that later. For now, disregard the practicality of the examples and focus on the dangerous patterns that they reveal._ + +Let's take a look at why this could be a problem. + +## Issue #1: Bracket object notation with user input grants access to every property available on the object + +```js +exampleClass[userInput[0]] = userInput[1]; +``` + +I won't spend much time here, as I believe this is fairly well known. If exampleClass contains a sensitive property, the above code will allow it to be edited. + +## Issue #2: Bracket object notation with user input grants access to every property available on the object, **_including prototypes._** + +```js +userInput = ['constructor', '{}']; +exampleClass[userInput[0]] = userInput[1]; +``` + +This looks pretty innocuous, even if it is an uncommon pattern. The problem here is that we can access or overwrite prototypes such as `constructor` or `__defineGetter__`, which may be used later on. The most likely outcome of this scenario would be an application crash, when a string is attempted to be called as a function. + +## Issue #3: Bracket object notation with user input grants access to every property available on the object, including prototypes, **_which can lead to Remote Code Execution._** + +Now here's where things get really dangerous. It's also where example code gets really implausible - bear with me. + +```js +var user = function () { + this.name = 'jon'; + //An empty user constructor. +}; + +function handler(userInput) { + var anyVal = 'anyVal'; // This can be any attribute, and does not need to be user controlled. + user[anyVal] = user[userInput[0]](userInput[1]); +} +``` + +In the previous section, I mentioned that constructor can be accessed from square brackets. In this case, since we are dealing with a function, the constructor we get back is the `Function` Constructor, which compiles a string of code into a function. + +## Exploitation + +In order to exploit the above code, we need a two stage exploit function. + +```js +function exploit(cmd) { + var userInputStageOne = ['constructor', 'require("child_process").exec(arguments[0],console.log)']; + var userInputStageTwo = ['anyVal', cmd]; + + handler(userInputStageOne); + handler(userInputStageTwo); +} +``` + +Let's break it down. + +The first time handler is run, it looks something like this: + +```js +userInput[0] = 'constructor'; +userInput[1] = 'require("child_process").exec(arguments[0],console.log)'; + +user['anyVal'] = user['constructor'](userInput[1]); +``` + +Executing this code creates a function containing the payload, and assigns it to `user['anyVal']`: + +```js +user['anyVal'] = function () { + require('child_process').exec(arguments[0], console.log); +}; +``` + +And when handler is run a second time: + +```js +user.anyVal = user.anyVal('date'); +``` + +What we end up with is this: + +![Exploiting date screenshot](https://cldup.com/lR_Xp0PwU9.png) + +Remote Code Execution. The biggest problem here is that there is very little indication in the code that this is what is going on. With something so serious, method calls tend to be very explicit - eval, child_process, etc. It's pretty difficult in node to accidentally introduce one of those into your application. Here though, without having either deep knowledge of JavaScript builtins or having done previous research, it is very easy to accidentally introduce this into your application. + +## Isn't this so obscure that it doesn't matter a whole lot? + +Well, yes and no. Is this particular vector a widespread problem? No, because current JavaScript style guides don't advocate programming this way. Might it become a widespread problem in the future? Absolutely. This pattern is avoided because it isn't common, and therefore not learned and taken up as habit, not because it's a known insecure pattern. + +Yes, we are talking about some fairly extreme edge cases, but don't make the assumption that your code doesn't have problems because of that - I have seen this issue in production code with some regularity. And, for the majority of node developers, a large portion of application code was not written by them, but rather included through required modules which may contain peculiar flaws like this one. + +Edge cases are uncommon, but because they are uncommon the problems with them are not well known, and they frequently go un-noticed during code review. If the code works, these types of problems tend to disappear. If the code works, and the problems are buried in a module nested n-levels deep, it's likely it won't be found until it causes problems, and by then it's too late. A blind require is essentially running untrusted code in your application. Be aware of the code you're requiring. + +## How do I fix it? + +The most direct fix here is going to be to **avoid the use of user input in property name fields**. This isn't reasonable in all circumstances, however, and there should be a way to safely use core language features. + +Another option is to create a allowlist of allowed property names, and filter each user input through a helper function to check before allowing it to be used. This is a great option in situations where you know specifically what property names to allow. + +In cases where you don't have a strictly defined data model ( which isn't ideal, but there are cases where it has to be so ) then using the same method as above, but with a denylist of disallowed properties instead is a valid choice. + +If you are using the `--harmony` flag or [io.js](https://iojs.org/), you also have the option of using [ECMAScript 6 direct proxies](http://wiki.ecmascript.org/doku.php?id=harmony:direct_proxies), which can stand in front of your real object ( private API ) and expose a limited subset of the object ( public API ). This is probably the best approach if you are using this pattern, as it is most consistent with typical object oriented programming paradigms. + diff --git a/docs/description/security_detect-possible-timing-attacks.md b/docs/description/security_detect-possible-timing-attacks.md new file mode 100644 index 000000000..ebeb931a5 --- /dev/null +++ b/docs/description/security_detect-possible-timing-attacks.md @@ -0,0 +1,5 @@ +# Detects insecure comparisons (`==`, `!=`, `!==` and `===`), which check input sequentially (`security/detect-possible-timing-attacks`) + +⚠️ This rule _warns_ in the ✅ `recommended` config. + + diff --git a/docs/description/security_detect-pseudoRandomBytes.md b/docs/description/security_detect-pseudoRandomBytes.md new file mode 100644 index 000000000..acbe11969 --- /dev/null +++ b/docs/description/security_detect-pseudoRandomBytes.md @@ -0,0 +1,5 @@ +# Detects if "pseudoRandomBytes()" is in use, which might not give you the randomness you need and expect (`security/detect-pseudoRandomBytes`) + +⚠️ This rule _warns_ in the ✅ `recommended` config. + + diff --git a/docs/description/security_detect-unsafe-regex.md b/docs/description/security_detect-unsafe-regex.md new file mode 100644 index 000000000..0c7d19c1f --- /dev/null +++ b/docs/description/security_detect-unsafe-regex.md @@ -0,0 +1,92 @@ +# Detects potentially unsafe regular expressions, which may take a very long time to run, blocking the event loop (`security/detect-unsafe-regex`) + +⚠️ This rule _warns_ in the ✅ `recommended` config. + + + +More information: + +# Regular Expression DoS and Node.js + +Imagine you are trying to buy a ticket to your favorite JavaScript conference, and instead of getting the ticket page, you instead get `500 Internal Server Error`. For some reason the site is down. You can't do the thing that you want to do most and the conference is losing out on your purchase, all because the application is unavailable. + +Availability is not often treated as a security problem, which it is, and it's impacts are immediate, and deeply felt. + +The attack surface for Node.js in regards to loss of availability is quite large, as we are dealing with a single event loop. If an attacker can control and block that event loop, then nothing else gets done. + +There are many ways to block the event loop, one way an attacker can do that is with [Regular Expression Denial of Service (ReDoS)](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS). + +If user provided input finds it's way into a regular expression, or a regular expression is designed with certain attributes, such as grouping with repetition, you can find yourself in a vulnerable position, as the regular expression match could take a long time to process. [OWASP](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS) has a deeper explanation of why this occurs. + +Let's look at an vulnerable example. Below we are attempting the common task of validating an email address on the server. + +```js +validateEmailFormat: function( string ) { + var emailExpression = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/; + + return emailExpression.test( string ); +} +``` + +With the example above, we can use this test script to show how bad input can impact server responsiveness: + +```js +start = process.hrtime(); +console.log(validateEmailFormat('baldwin@andyet.net')); +console.log(process.hrtime(start)); + +start = process.hrtime(); +console.log(validateEmailFormat('jjjjjjjjjjjjjjjjjjjjjjjjjjjj@ccccccccccccccccccccccccccccc.5555555555555555555555555555555555555555{')); +console.log(process.hrtime(start)); + +start = process.hrtime(); +console.log(validateEmailFormat('jjjjjjjjjjjjjjjjjjjjjjjjjjjj@ccccccccccccccccccccccccccccc.55555555555555555555555555555555555555555{')); +console.log(process.hrtime(start)); + +start = process.hrtime(); +console.log(validateEmailFormat('jjjjjjjjjjjjjjjjjjjjjjjjjjjj@ccccccccccccccccccccccccccccc.555555555555555555555555555555555555555555555555555555{')); +console.log(process.hrtime(start)); +``` + +Here are the results of running that script: + +```sh +true +[ 0, 9694442 ] <- Match on good data takes little time +false +[ 0, 49849962 ] <- Initial bad input baseline +false +[ 0, 55123953 ] <- Added 1 character to the input and you see minimal spike +false +[ 8, 487126563 ] <- Added 12 characters and you see it bumps up significantly +``` + +One way you can check regular expressions for badness in an automated way is by using a module from [substack](https://twitter.com/substack) called [safe-regex](https://www.npmjs.org/package/safe-regex). It's prone to false positives, however, it can be useful to point to potentially vulnerable regular expressions you would have otherwise missed in your code. + +Here is a rule for eslint that you can use to test your JavaScript regular expressions: + +```js +var safe = require('safe-regex'); +module.exports = function (context) { + 'use strict'; + + return { + Literal: function (node) { + var token = context.getTokens(node)[0], + nodeType = token.type, + nodeValue = token.value; + + if (nodeType === 'RegularExpression') { + if (!safe(nodeValue)) { + context.report(node, 'Possible Unsafe Regular Expression'); + } + } + }, + }; +}; +``` + +Additionally, OWASP has a [list of regular expressions](https://www.owasp.org/index.php/OWASP_Validation_Regex_Repository) for common validations that might be useful to you. + +As part of our ongoing effort to increase the overall security of the Node.js ecosystem, we have conducted automated analysis of every module on npm. We did identify 56 unique vulnerable regular expressions and over 120 modules containing vulnerable regular expressions. Considering that there are now over 100k modules, the results were not alarming. We're working closely with the maintainers of each module to get the issues resolved, once that's done, advisories will be published to the [npm Security advisories](https://www.npmjs.com/advisories) site. + diff --git a/docs/multiple-tests/all-patterns-typescript/patterns.xml b/docs/multiple-tests/all-patterns-typescript/patterns.xml index a048cd91a..caea37322 100644 --- a/docs/multiple-tests/all-patterns-typescript/patterns.xml +++ b/docs/multiple-tests/all-patterns-typescript/patterns.xml @@ -317,7 +317,6 @@ - @@ -413,6 +412,7 @@ + @@ -424,6 +424,7 @@ + @@ -496,6 +497,7 @@ + @@ -1695,13 +1697,13 @@ + - @@ -1846,6 +1848,7 @@ + @@ -1866,7 +1869,6 @@ - diff --git a/docs/multiple-tests/all-patterns/patterns.xml b/docs/multiple-tests/all-patterns/patterns.xml index a048cd91a..caea37322 100644 --- a/docs/multiple-tests/all-patterns/patterns.xml +++ b/docs/multiple-tests/all-patterns/patterns.xml @@ -317,7 +317,6 @@ - @@ -413,6 +412,7 @@ + @@ -424,6 +424,7 @@ + @@ -496,6 +497,7 @@ + @@ -1695,13 +1697,13 @@ + - @@ -1846,6 +1848,7 @@ + @@ -1866,7 +1869,6 @@ - diff --git a/docs/patterns.json b/docs/patterns.json index 136b343bb..ccd2de07c 100644 --- a/docs/patterns.json +++ b/docs/patterns.json @@ -3105,13 +3105,6 @@ "parameters": [], "enabled": false }, - { - "patternId": "@angular-eslint_prefer-standalone-component", - "level": "Info", - "category": "CodeStyle", - "parameters": [], - "enabled": false - }, { "patternId": "@angular-eslint_prefer-output-readonly", "level": "Info", @@ -3895,6 +3888,18 @@ "parameters": [], "enabled": false }, + { + "patternId": "@typescript-eslint_no-duplicate-imports", + "level": "Info", + "category": "CodeStyle", + "parameters": [ + { + "name": "includeExports", + "default": false + } + ], + "enabled": false + }, { "patternId": "@typescript-eslint_no-duplicate-type-constituents", "level": "Info", @@ -3972,6 +3977,13 @@ "parameters": [], "enabled": false }, + { + "patternId": "@typescript-eslint_no-implicit-any-catch", + "level": "Info", + "category": "CodeStyle", + "parameters": [], + "enabled": false + }, { "patternId": "@typescript-eslint_no-implied-eval", "level": "Info", @@ -4524,6 +4536,13 @@ "parameters": [], "enabled": false }, + { + "patternId": "@typescript-eslint_sort-type-union-intersection-members", + "level": "Info", + "category": "CodeStyle", + "parameters": [], + "enabled": false + }, { "patternId": "@typescript-eslint_space-before-blocks", "level": "Info", @@ -13751,7 +13770,7 @@ "enabled": false }, { - "patternId": "scanjs-rules_call__addEventListener", + "patternId": "scanjs-rules_call__Function", "level": "Info", "category": "Security", "subcategory": "CommandInjection", @@ -13759,7 +13778,7 @@ "enabled": false }, { - "patternId": "scanjs-rules_call__addEventListener__deviceproximity", + "patternId": "scanjs-rules_call__addEventListener", "level": "Info", "category": "Security", "subcategory": "CommandInjection", @@ -13767,7 +13786,7 @@ "enabled": false }, { - "patternId": "scanjs-rules_call__addEventListener__message", + "patternId": "scanjs-rules_call__addEventListener__deviceproximity", "level": "Info", "category": "Security", "subcategory": "CommandInjection", @@ -13775,7 +13794,7 @@ "enabled": false }, { - "patternId": "scanjs-rules_call__connect", + "patternId": "scanjs-rules_call__addEventListener__message", "level": "Info", "category": "Security", "subcategory": "CommandInjection", @@ -13783,7 +13802,7 @@ "enabled": false }, { - "patternId": "scanjs-rules_call__eval", + "patternId": "scanjs-rules_call__connect", "level": "Info", "category": "Security", "subcategory": "CommandInjection", @@ -13791,7 +13810,7 @@ "enabled": false }, { - "patternId": "scanjs-rules_call__execScript", + "patternId": "scanjs-rules_call__eval", "level": "Info", "category": "Security", "subcategory": "CommandInjection", @@ -13799,7 +13818,7 @@ "enabled": false }, { - "patternId": "scanjs-rules_call__Function", + "patternId": "scanjs-rules_call__execScript", "level": "Info", "category": "Security", "subcategory": "CommandInjection", @@ -14358,7 +14377,7 @@ }, { "name": "langDir", - "default": "C:\\Users\\Vítor Pires\\repos\\codacy\\codacy-eslint\\node_modules\\eslint-plugin-spellcheck\\rules/utils/dicts" + "default": "/Users/lorenzo/codacy/github/codacy-eslint/node_modules/eslint-plugin-spellcheck/rules/utils/dicts" }, { "name": "skipWords", @@ -14942,6 +14961,13 @@ "parameters": [], "enabled": false }, + { + "patternId": "unicorn_no-unsafe-regex", + "level": "Info", + "category": "CodeStyle", + "parameters": [], + "enabled": false + }, { "patternId": "unicorn_no-unused-properties", "level": "Info", @@ -15087,13 +15113,6 @@ "parameters": [], "enabled": false }, - { - "patternId": "unicorn_no-unsafe-regex", - "level": "Info", - "category": "CodeStyle", - "parameters": [], - "enabled": false - }, { "patternId": "unused-imports_no-unused-vars", "level": "Info", @@ -17708,4 +17727,4 @@ "enabled": false } ] -} +} diff --git a/src/docGenerator.ts b/src/docGenerator.ts index df31b7092..28a66b9d6 100644 --- a/src/docGenerator.ts +++ b/src/docGenerator.ts @@ -145,20 +145,44 @@ export class DocGenerator { return this.getPatternIds().filter((e) => !e.includes("/")) } + private async inlineLinkedMarkdownFiles(text: string, baseUrl: string) { + let newText = text; + + const elements = newText.match(/\[.*?\)/g); + + if (elements) { + await Promise.all(elements.map(async (elem) => { + const urlMatch = elem.match(/\((.*?)\)/) + if (urlMatch && urlMatch.length === 2) { + const url = elem.match(/\((.*?)\)/)[1]; + if (url.startsWith("../") && url.endsWith(".md")) { + const fullUrl = `${baseUrl}${url}` + const response = await fetch(fullUrl); + const content = await response.text() + newText = newText.replace(elem, () => `\n\n${content}`) + } + } + })) + } + return newText; + } + downloadDocs( - urlFromPatternId: (patternId: string) => string, + baseUrl: string, prefix: string = "", - rejectOnError: boolean = true + rejectOnError: boolean = true, + patternIdModifier: (patternId: string) => string = s => s, ) { const patterns = prefix.length > 0 ? this.patternIdsWithoutPrefix(prefix) : this.eslintPatternIds() const promises: Promise[] = patterns.map(async (pattern) => { - const url: string = urlFromPatternId(pattern) + const url: string = `${baseUrl}${patternIdModifier(pattern)}.md` const result = await fetch(url) if (result.ok) { - const text = await result.text() + const textInput = await result.text() + const text = await this.inlineLinkedMarkdownFiles(textInput, baseUrl) const filename = "docs/description/" + patternIdToCodacy((prefix.length > 0 ? prefix + "/" : "") + pattern) + @@ -186,10 +210,10 @@ export class DocGenerator { ${this.rules - .map( - ([patternId, _]) => ` ` - ) - .join("\n")} + .map( + ([patternId, _]) => ` ` + ) + .join("\n")} ` await writeFile(patternsFilename, patternsXml) diff --git a/src/docGeneratorMain.ts b/src/docGeneratorMain.ts index 6699f5a2f..b3bb46322 100644 --- a/src/docGeneratorMain.ts +++ b/src/docGeneratorMain.ts @@ -24,401 +24,407 @@ async function main() { console.log("Generate @angular-eslint description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/angular-eslint/angular-eslint/master/packages/eslint-plugin/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/angular-eslint/angular-eslint/master/packages/eslint-plugin/docs/rules/`, "@angular-eslint" ) console.log("Generate @salesforce/aura description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/forcedotcom/eslint-plugin-aura/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/forcedotcom/eslint-plugin-aura/master/docs/rules/`, "@salesforce/aura" ) console.log("Generate @salesforce/lightning description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/salesforce/eslint-plugin-lightning/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/salesforce/eslint-plugin-lightning/master/docs/rules/`, "@salesforce/lightning" ) console.log("Generate @shopify description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/Shopify/web-configs/main/packages/eslint-plugin/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/Shopify/web-configs/main/packages/eslint-plugin/docs/rules/`, "@shopify" ) console.log("Generate @typescript-eslint description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/typescript-eslint/typescript-eslint/master/packages/eslint-plugin/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/typescript-eslint/typescript-eslint/master/packages/eslint-plugin/docs/rules/`, "@typescript-eslint", false ) console.log("Generate angular description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/EmmanuelDemey/eslint-plugin-angular/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/EmmanuelDemey/eslint-plugin-angular/master/docs/rules/`, "angular" ) console.log("Generate backbone description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/ilyavolodin/eslint-plugin-backbone/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/ilyavolodin/eslint-plugin-backbone/master/docs/rules/`, "backbone" ) console.log("Generate better-styled-components description files") await docGenerator.downloadDocs( - (pattern) => - // not the best way on how to deal with wrong name of rule ¯\_(ツ)_/¯ - `${githubBaseUrl}/tinloof/eslint-plugin-better-styled-components/master/docs/rules/sort-rules-alphabetically.md`, - "better-styled-components" + `${githubBaseUrl}/tinloof/eslint-plugin-better-styled-components/master/docs/rules/`, + "better-styled-components", + true, + // not the best way on how to deal with wrong name of rule ¯\_(ツ)_/¯ + pattern => "sort-rules-alphabetically" ) console.log("Generate compat description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/amilajack/eslint-plugin-compat/main/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/amilajack/eslint-plugin-compat/main/docs/rules/`, "compat" ) console.log("Generate cypress description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/cypress-io/eslint-plugin-cypress/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/cypress-io/eslint-plugin-cypress/master/docs/rules/`, "cypress" ) console.log("Generate ember description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/ember-cli/eslint-plugin-ember/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/ember-cli/eslint-plugin-ember/master/docs/rules/`, "ember" ) console.log("Generate ember-suave description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/DockYard/eslint-plugin-ember-suave/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/DockYard/eslint-plugin-ember-suave/master/docs/rules/`, "ember-suave" ) console.log("Generate es-x description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/eslint-community/eslint-plugin-es-x/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/eslint-community/eslint-plugin-es-x/master/docs/rules/`, "es-x" ) console.log("Generate eslint description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/eslint/eslint/main/docs/src/rules/${pattern}.md` + + `${githubBaseUrl}/eslint/eslint/main/docs/src/rules/` ) console.log("Generate eslint-plugin description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/eslint-community/eslint-plugin-eslint-plugin/main/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/eslint-community/eslint-plugin-eslint-plugin/main/docs/rules/`, "eslint-plugin" ) console.log("Generate functional description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/jonaskello/eslint-plugin-functional/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/jonaskello/eslint-plugin-functional/master/docs/rules/`, "functional", false ) console.log("Generate i18next description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/edvardchen/eslint-plugin-i18next/next/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/edvardchen/eslint-plugin-i18next/next/docs/rules/`, "i18next", false ) console.log("Generate import description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/un-es/eslint-plugin-i/fork-release/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/un-es/eslint-plugin-i/fork-release/docs/rules/`, "import", false ) console.log("Generate jasmine description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/tlvince/eslint-plugin-jasmine/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/tlvince/eslint-plugin-jasmine/master/docs/rules/`, "jasmine" ) console.log("Generate jest description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/jest-community/eslint-plugin-jest/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/jest-community/eslint-plugin-jest/master/docs/rules/`, "jest" ) console.log("Generate jest-dom description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/testing-library/eslint-plugin-jest-dom/main/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/testing-library/eslint-plugin-jest-dom/main/docs/rules/`, "jest-dom" ) console.log("Generate jest-extended description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/jest-community/eslint-plugin-jest-extended/main/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/jest-community/eslint-plugin-jest-extended/main/docs/rules/`, "jest-extended" ) console.log("Generate jest-formating description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/dangreenisrael/eslint-plugin-jest-formatting/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/dangreenisrael/eslint-plugin-jest-formatting/master/docs/rules/`, "jest-formating" ) console.log("Generate jsdoc description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/gajus/eslint-plugin-jsdoc/main/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/gajus/eslint-plugin-jsdoc/main/docs/rules/`, "jsdoc" ) console.log("Generate jsonc description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/ota-meshi/eslint-plugin-jsonc/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/ota-meshi/eslint-plugin-jsonc/master/docs/rules/`, "jsonc" ) console.log("Generate jsx-a11y description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/jsx-eslint/eslint-plugin-jsx-a11y/main/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/jsx-eslint/eslint-plugin-jsx-a11y/main/docs/rules/`, "jsx-a11y" ) console.log("Generate lit description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/43081j/eslint-plugin-lit/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/43081j/eslint-plugin-lit/master/docs/rules/`, "lit" ) console.log("Generate lodash description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/wix-incubator/eslint-plugin-lodash/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/wix-incubator/eslint-plugin-lodash/master/docs/rules/`, "lodash" ) console.log("Generate lodash-fp description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/jfmengels/eslint-plugin-lodash-fp/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/jfmengels/eslint-plugin-lodash-fp/master/docs/rules/`, "lodash-fp" ) console.log("Generate meteor description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/dferber90/eslint-plugin-meteor/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/dferber90/eslint-plugin-meteor/master/docs/rules/`, "meteor" ) console.log("Generate mocha description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/lo1tuma/eslint-plugin-mocha/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/lo1tuma/eslint-plugin-mocha/master/docs/rules/`, "mocha" ) console.log("Generate no-unsanitized description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/mozilla/eslint-plugin-no-unsanitized/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/mozilla/eslint-plugin-no-unsanitized/master/docs/rules/`, "no-unsanitized" ) console.log("Generate node description files") await docGenerator.downloadDocs( - (pattern) => { - const patternFoldered = pattern.split("_").join("/") - return `${githubBaseUrl}/mysticatea/eslint-plugin-node/master/docs/rules/${patternFoldered}.md` - }, - "node" + `${githubBaseUrl}/mysticatea/eslint-plugin-node/master/docs/rules/`, + "node", + true, + pattern => pattern.split("_").join("/") ) console.log("Generate nuxt description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/nuxt/eslint-plugin-nuxt/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/nuxt/eslint-plugin-nuxt/master/docs/rules/`, "nuxt", false ) console.log("Generate perfectionist description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/azat-io/eslint-plugin-perfectionist/main/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/azat-io/eslint-plugin-perfectionist/main/docs/rules/`, "perfectionist" ) console.log("Generate promise description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/xjamundx/eslint-plugin-promise/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/xjamundx/eslint-plugin-promise/master/docs/rules/`, "promise" ) console.log("Generate react description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/jsx-eslint/eslint-plugin-react/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/jsx-eslint/eslint-plugin-react/master/docs/rules/`, "react" ) console.log("Generate react-native description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/Intellicode/eslint-plugin-react-native/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/Intellicode/eslint-plugin-react-native/master/docs/rules/`, "react-native" ) console.log("Generate redux-saga description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/pke/eslint-plugin-redux-saga/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/pke/eslint-plugin-redux-saga/master/docs/rules/`, "redux-saga" ) console.log("Generate regexp description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/ota-meshi/eslint-plugin-regexp/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/ota-meshi/eslint-plugin-regexp/master/docs/rules/`, "regexp" ) console.log("Generate rxjs description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/cartant/eslint-plugin-rxjs/main/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/cartant/eslint-plugin-rxjs/main/docs/rules/`, "rxjs" ) console.log("Generate rxjs-angular description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/cartant/eslint-plugin-rxjs-angular/main/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/cartant/eslint-plugin-rxjs-angular/main/docs/rules/`, "rxjs-angular" ) + console.log("Generate security description files") + await docGenerator.downloadDocs( + + `${githubBaseUrl}/eslint-community/eslint-plugin-security/main/docs/rules/`, + "security" + ) + console.log("Generate sonarjs description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/SonarSource/eslint-plugin-sonarjs/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/SonarSource/eslint-plugin-sonarjs/master/docs/rules/`, "sonarjs" ) console.log("Generate sort-destructure-keys description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/mthadley/eslint-plugin-sort-destructure-keys/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/mthadley/eslint-plugin-sort-destructure-keys/master/docs/rules/`, "sort-destructure-keys" ) console.log("Generate storybook description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/storybookjs/eslint-plugin-storybook/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/storybookjs/eslint-plugin-storybook/master/docs/rules/`, "storybook" ) console.log("Generate tailwindcss description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/francoismassart/eslint-plugin-tailwindcss/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/francoismassart/eslint-plugin-tailwindcss/master/docs/rules/`, "tailwindcss" ) console.log("Generate test-selectors description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/davidcalhoun/eslint-plugin-test-selectors/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/davidcalhoun/eslint-plugin-test-selectors/master/docs/rules/`, "test-selectors" ) console.log("Generate test-library description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/testing-library/eslint-plugin-testing-library/main/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/testing-library/eslint-plugin-testing-library/main/docs/rules/`, "test-library" ) console.log("Generate typescript-sort-keys description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/infctr/eslint-plugin-typescript-sort-keys/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/infctr/eslint-plugin-typescript-sort-keys/master/docs/rules/`, "typescript-sort-keys" ) console.log("Generate unicorn description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/sindresorhus/eslint-plugin-unicorn/main/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/sindresorhus/eslint-plugin-unicorn/main/docs/rules/`, "unicorn" ) console.log("Generate unused-imports description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/sweepline/eslint-plugin-unused-imports/master/docs/rules/${pattern}.md`, + `${githubBaseUrl}/sweepline/eslint-plugin-unused-imports/master/docs/rules/`, "unused-imports", false ) console.log("Generate vue description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/vuejs/eslint-plugin-vue/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/vuejs/eslint-plugin-vue/master/docs/rules/`, "vue" ) console.log("Generate vue-scoped-css description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/future-architect/eslint-plugin-vue-scoped-css/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/future-architect/eslint-plugin-vue-scoped-css/master/docs/rules/`, "vue-scoped-css" ) console.log("Generate wdio description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/webdriverio/webdriverio/main/packages/eslint-plugin-wdio/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/webdriverio/webdriverio/main/packages/eslint-plugin-wdio/docs/rules/`, "wdio" ) console.log("Generate xss description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/Rantanen/eslint-plugin-xss/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/Rantanen/eslint-plugin-xss/master/docs/rules/`, "xss" ) console.log("Generate yml description files") await docGenerator.downloadDocs( - (pattern) => - `${githubBaseUrl}/ota-meshi/eslint-plugin-yml/master/docs/rules/${pattern}.md`, + + `${githubBaseUrl}/ota-meshi/eslint-plugin-yml/master/docs/rules/`, "yml" )