Skip to content

Latest commit

 

History

History
142 lines (100 loc) · 6.01 KB

CONTRIBUTING.md

File metadata and controls

142 lines (100 loc) · 6.01 KB

Thank you for considering contributing to our project!

Feel free to participate to this project, as much as you can. Even supporting other users or improving the documentation can make a big difference!

Build & Run

Clone electronegativity and proceed with the following:

$ npm ci # So we have a deterministic, repeatable build
$ npm run build
$ node dist/index.js -h

To debug using Visual Studio Code, you can also setup the following .vscode/launch.json with a specific test case as argument:

{
    "version": "0.2.0",
    "configurations": [
        
        {
            "type": "node",
            "request": "launch",
            "name": "Launch Program",
            "program": "${workspaceFolder}/dist/index.js",
            "args": ["-i","${workspaceFolder}/test/checks/"]
        }
    ]
}

Creating new checks

Electronegativity is build in such a way to easily allow the development of new security checks.

There are three different check types:

Depending on the target file (e.g. evaluating a property in a JavaScript file -> JS), you will need to:

  1. Create a new file in /src/finder/checks/AtomicChecks
  2. Create a new class with a match() function, which should contain the logic of your custom check
    • JS -> match(astNode, astHelper)
    • HTML -> match(cheerioObj, content)
    • JSON -> match(content)
  3. Add a constructor that specifies the check details such as name, description, etc.

For example:

import { sourceTypes } from '../../parser/types';
import { severity, confidence } from '../../attributes';

export default class MyCustomHTMLCheck {
    constructor() {
        this.id = 'MY_CUSTOM_HTML_CHECK';
        this.description = `this is a custom check`;
        this.type = sourceTypes.JAVASCRIPT;
    }

    match(cheerioObj, content){
        // do magic
        // either return an object with row and col, or null meaning no issues were identified
        // also remember to set the following properties in the returned obj: 
        // - "line" of the file where the issue is found
        // - "column" of the file where the issue is found
        // - "id" of the issue (i.e. this.id)
        // - "description" of the issue (i.e. this.description)
        // - "properties" object (optional) with internal information about the findings, useful in combination with a GlobalCheck 
        // - "severity" level from finder/attributes.js (e.g. severity.MEDIUM)
        // - "confidence" level from finder/attributes.js (e.g. confidence.TENTATIVE)
        // - "manualReview" boolean flag to indicate if the finding should be manually reviewed
    }
}

Take a look at the different checks in /src/finder/checks/AtomicChecks to get an idea on how things work.

GlobalChecks

These non-atomic checks are executed after the first round of standard atomic checks and they work on the array of issues generated by the first batch of Checks to determine further vulnerabilities or discard false positives. Using this class of checks provides an improved decisional process, which comes in handy for checks that needs to scan every JS/HTML of the target application before determining the presence or absence of a vulnerability. To achieve this, it is possible for atomic checks to share an additional properties object when added to the initial issues array, that can later be used by any GlobalCheck to perform decisions. These checks are located in src/finder/checks/ComplexChecks/ and share a very similar loading mechanism to that of the atomic checks. Every time you will create a new ComplexChecks you will need to add it in the ComplexChecks loader (src/finder/checks/ComplexChecks/index.js).

Note that if a GlobalCheck needs other checks to be performed before its execution, you may declare this by setting a depends array property in its constructor() function (e.g. this.depends = ["CSPJSCheck", "CSPHTMLCheck"];).

Naming conventions

  • The classname (and file) for each check uses CamelCase notation with the following convention: <NAME><TYPE>Check (e.g. AllowPopupHTMLCheck)

where NAME is a self-descriptive identifier

where TYPE can be HTML, JSON or JS

  • The <CHECK_NAME_IDENTIFIER> (class id) uses uppercase notation with the following convention: <NAME_UNDERSCORE_SEPARATED>_<TYPE>_CHECK (e.g. ALLOWPOPUPS_HTML_CHECK).

  • GlobalChecks also use the uppercase notation with the following convention: <NAME_UNDERSCORE_SEPARATED>_GLOBAL_CHECK (e.g. CSP_GLOBAL_CHECK).

Testing checks with Mocha

Atomic checks

Test cases for simple atomic checks unit testing are placed in test/checks/AtomicChecks.

Filenames for tests should have the following format: <CHECK_NAME_IDENTIFIER>_<test number #>_<number of issues>.<js|htm|html>

For instance, the NODE_INTEGRATION_JS_CHECK_1_0.js will be analyzed using the NODE_INTEGRATION_JS_CHECK check and the test is expected to find 0 issues.

The test number # part should always be progressive, even if two tests share the same structure but a different number of issues.

Global checks

Test cases for global checks unit testing are implemented as directories stored in test/checks/GlobalChecks/. Each directory is named following the same atomic checks naming convention and contains the files necessary for the test to run. E.g. taking CSP global check, the directory structure for positive and negative tests looks like this:

test
    checks
        GlobalChecks
            CSP_GLOBAL_CHECK_1_0
                index.html
                main.js
                renderer.js
            CSP_GLOBAL_CHECK_1_1
                index.html
                main.js
                renderer.js

To run all tests, use the following:

$ npm run test