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!
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/"]
}
]
}
Electronegativity is build in such a way to easily allow the development of new security checks.
There are three different check types:
- JS (using a combination of Esprima, Babel, TypeScript ESTree)
- HTML (using Cheerio)
- JSON (using the native
JSON.parse()
)
Depending on the target file (e.g. evaluating a property in a JavaScript file -> JS), you will need to:
- Create a new file in
/src/finder/checks/AtomicChecks
- 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)
- JS ->
- 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.
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"];
).
- 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).
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.
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