diff --git a/README.md b/README.md index a7d3cac..85c79fc 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Security Notes allows the creation of notes within source files, which can be replied to, reacted to using emojis, and assigned statuses such as "TODO", "Vulnerable" and "Not Vulnerable". -Also, it allows importing the output from SAST tools (currently only [Semgrep](https://semgrep.dev/)) into notes, making the processing of the findings much easier. +Also, it allows importing the output from SAST tools (such as semgrep, bandit and brakeman), into notes, making the processing of the findings much easier. Finally, collaborate with others by using a centralized database for notes that will be automatically synced in **real-time**! Create a note locally, and it will be automatically pushed to whoever is working with you on the project. @@ -64,13 +64,36 @@ Naturally, you will want to collaborate with remote peers. To do so in a secure ## Importing SAST results -The extension allows you to import the output from SAST tools (currently only [Semgrep](https://semgrep.dev/)) into notes, making the processing of the findings much easier: +The extension allows you to import the output from SAST tools into notes, making the processing of the findings much easier:  +Currently supported tools include: + +- bandit (https://bandit.readthedocs.io/en/latest/) +- brakeman (https://brakemanscanner.org/) +- checkov (https://www.checkov.io/) +- gosec (https://github.com/securego/gosec) +- semgrep (https://semgrep.dev/) + +For imports to be successful, we recommend running commands as follows (exporting results as JSON), and making sure to run these tools from the project's folder (so that all relative paths can be processed correctly): + +```bash +# bandit +bandit -f json -o bandit-results.json -r . +# brakeman +brakeman -f json -o brakeman-results.json . +# checkov +checkov -d . -o json --output-file-path checkov-results.json +# gosec +gosec -fmt=json -out=gosec-results.json ./... +# semgrep +semgrep scan --json -o semgrep-results.json --config=auto . +``` + ## Extension Settings -Various settings for the extension can be configured in VSCode's User Settings page (`CMD+Shift+P` / `Ctrl + Shift + P` -> *Preferences: Open Settings (UI)*): +Various settings for the extension can be configured in VSCode's User Settings page (`CMD+Shift+P` / `Ctrl + Shift + P` -> _Preferences: Open Settings (UI)_):  diff --git a/package-lock.json b/package-lock.json index adb2394..14b82dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "security-notes", - "version": "1.1.1", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "security-notes", - "version": "1.1.1", + "version": "1.2.0", "license": "MIT", "dependencies": { "@types/uuid": "^9.0.0", diff --git a/package.json b/package.json index 7455911..62e266b 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Security Notes", "description": "Create notes during a security code review. Import your favorite SAST tool results and collaborate with others.", "icon": "resources/security_notes_logo.png", - "version": "1.1.1", + "version": "1.2.0", "publisher": "refactor-security", "private": false, "license": "MIT", diff --git a/src/parsers/bandit.ts b/src/parsers/bandit.ts new file mode 100644 index 0000000..c8e3d7f --- /dev/null +++ b/src/parsers/bandit.ts @@ -0,0 +1,38 @@ +'use strict'; + +import * as vscode from 'vscode'; +import { ToolFinding } from '../models/toolFinding'; +import { relativePathToFull } from '../utils'; + +class BanditParser { + static parse(fileContent: string) { + const toolFindings: ToolFinding[] = []; + + try { + const banditFindings = JSON.parse(fileContent).results; + banditFindings.map((banditFinding: any) => { + // uri + const uri = vscode.Uri.file(relativePathToFull(banditFinding.filename)); + + // range + const lineRange = banditFinding.line_range; + const range = new vscode.Range( + lineRange[0] - 1, + 0, + (lineRange[1] ? lineRange[1] : lineRange[0]) - 1, + 0, + ); + + // instantiate tool finding and add to list + const toolFinding = new ToolFinding(uri, range, banditFinding.issue_text); + toolFindings.push(toolFinding); + }); + } catch { + /* empty */ + } + + return toolFindings; + } +} + +export { BanditParser }; diff --git a/src/parsers/brakeman.ts b/src/parsers/brakeman.ts new file mode 100644 index 0000000..1899a97 --- /dev/null +++ b/src/parsers/brakeman.ts @@ -0,0 +1,41 @@ +'use strict'; + +import * as vscode from 'vscode'; +import { ToolFinding } from '../models/toolFinding'; +import { relativePathToFull } from '../utils'; + +class BrakemanParser { + static parse(fileContent: string) { + const toolFindings: ToolFinding[] = []; + + try { + const brakemanFindings = JSON.parse(fileContent).warnings; + brakemanFindings.map((brakemanFinding: any) => { + // uri + const uri = vscode.Uri.file(relativePathToFull(brakemanFinding.file)); + + // range + const range = new vscode.Range( + brakemanFinding.line - 1, + 0, + brakemanFinding.line - 1, + 0, + ); + + // instantiate tool finding and add to list + const toolFinding = new ToolFinding( + uri, + range, + `${brakemanFinding.warning_type}: ${brakemanFinding.message}`, + ); + toolFindings.push(toolFinding); + }); + } catch { + /* empty */ + } + + return toolFindings; + } +} + +export { BrakemanParser }; diff --git a/src/parsers/checkov.ts b/src/parsers/checkov.ts new file mode 100644 index 0000000..569450d --- /dev/null +++ b/src/parsers/checkov.ts @@ -0,0 +1,40 @@ +'use strict'; + +import * as vscode from 'vscode'; +import { ToolFinding } from '../models/toolFinding'; +import { relativePathToFull } from '../utils'; + +class CheckovParser { + static parse(fileContent: string) { + const toolFindings: ToolFinding[] = []; + + try { + const checkovCheckTypes = JSON.parse(fileContent); + checkovCheckTypes.map((checkovCheckType: any) => { + const checkovFindings = checkovCheckType.results.failed_checks; + checkovFindings.map((checkovFinding: any) => { + // uri + const uri = vscode.Uri.file(relativePathToFull(checkovFinding.file_path)); + + // range + const range = new vscode.Range( + checkovFinding.file_line_range[0] - 1, + 0, + checkovFinding.file_line_range[1] - 1, + 0, + ); + + // instantiate tool finding and add to list + const toolFinding = new ToolFinding(uri, range, checkovFinding.check_name); + toolFindings.push(toolFinding); + }); + }); + } catch { + /* empty */ + } + + return toolFindings; + } +} + +export { CheckovParser }; diff --git a/src/parsers/gosec.ts b/src/parsers/gosec.ts new file mode 100644 index 0000000..61d1972 --- /dev/null +++ b/src/parsers/gosec.ts @@ -0,0 +1,32 @@ +'use strict'; + +import * as vscode from 'vscode'; +import { ToolFinding } from '../models/toolFinding'; + +class GosecParser { + static parse(fileContent: string) { + const toolFindings: ToolFinding[] = []; + + try { + const gosecFindings = JSON.parse(fileContent).Issues; + gosecFindings.map((gosecFinding: any) => { + // uri + const uri = vscode.Uri.file(gosecFinding.file); + + // range + const line = gosecFinding.line; + const range = new vscode.Range(line - 1, 0, line - 1, 0); + + // instantiate tool finding and add to list + const toolFinding = new ToolFinding(uri, range, gosecFinding.details); + toolFindings.push(toolFinding); + }); + } catch { + /* empty */ + } + + return toolFindings; + } +} + +export { GosecParser }; diff --git a/src/parsers/semgrep.ts b/src/parsers/semgrep.ts index 3271694..295ff8d 100644 --- a/src/parsers/semgrep.ts +++ b/src/parsers/semgrep.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode'; import { ToolFinding } from '../models/toolFinding'; +import { relativePathToFull } from '../utils'; class SemgrepParser { static parse(fileContent: string) { @@ -11,11 +12,7 @@ class SemgrepParser { const semgrepFindings = JSON.parse(fileContent).results; semgrepFindings.map((semgrepFinding: any) => { // uri - let fullPath = ''; - if (vscode.workspace.workspaceFolders) { - fullPath = vscode.workspace.workspaceFolders[0].uri.fsPath + '/'; - } - const uri = vscode.Uri.file(`${fullPath}${semgrepFinding.path}`); + const uri = vscode.Uri.file(relativePathToFull(semgrepFinding.path)); // range const range = new vscode.Range( diff --git a/src/webviews/assets/main.js b/src/webviews/assets/main.js index fd8e43d..85a572f 100644 --- a/src/webviews/assets/main.js +++ b/src/webviews/assets/main.js @@ -11,7 +11,7 @@ function onButtonClicked() { let toolSelect = document.getElementById('toolSelect'); - let toolName = toolSelect.options[toolSelect.selectedIndex].text; + let toolName = toolSelect.options[toolSelect.selectedIndex].value; let selectedFile = document.getElementById('fileInput').files[0]; readFile(selectedFile).then((fileContent) => { diff --git a/src/webviews/importToolResultsWebview.ts b/src/webviews/importToolResultsWebview.ts index 883c166..eabea5b 100644 --- a/src/webviews/importToolResultsWebview.ts +++ b/src/webviews/importToolResultsWebview.ts @@ -2,6 +2,10 @@ import * as vscode from 'vscode'; import { commentController } from '../controllers/comments'; +import { BanditParser } from '../parsers/bandit'; +import { BrakemanParser } from '../parsers/brakeman'; +import { CheckovParser } from '../parsers/checkov'; +import { GosecParser } from '../parsers/gosec'; import { SemgrepParser } from '../parsers/semgrep'; import { ToolFinding } from '../models/toolFinding'; import { saveNoteComment } from '../helpers'; @@ -42,12 +46,7 @@ export class ImportToolResultsWebview implements vscode.WebviewViewProvider { webviewView.webview.onDidReceiveMessage((data) => { switch (data.type) { case 'processToolFile': { - processToolFile( - data.toolName, - data.fileContent, - this.noteMap, - this.remoteDb, - ); + processToolFile(data.toolName, data.fileContent, this.noteMap, this.remoteDb); } } }); @@ -85,7 +84,11 @@ export class ImportToolResultsWebview implements vscode.WebviewViewProvider {
Select tool:
Select file:
@@ -111,8 +114,25 @@ function processToolFile( // parse tool findings switch (toolName) { + case 'bandit': { + toolFindings = BanditParser.parse(fileContent); + break; + } + case 'brakeman': { + toolFindings = BrakemanParser.parse(fileContent); + break; + } + case 'checkov': { + toolFindings = CheckovParser.parse(fileContent); + break; + } + case 'gosec': { + toolFindings = GosecParser.parse(fileContent); + break; + } case 'semgrep': { toolFindings = SemgrepParser.parse(fileContent); + break; } }