Skip to content
Merged
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
29 changes: 26 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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:

![Demo for semgrep import](images/demo-semgrep-import.gif)

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)_):

![Extension Settings](images/settings.png)

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
38 changes: 38 additions & 0 deletions src/parsers/bandit.ts
Original file line number Diff line number Diff line change
@@ -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 };
41 changes: 41 additions & 0 deletions src/parsers/brakeman.ts
Original file line number Diff line number Diff line change
@@ -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 };
40 changes: 40 additions & 0 deletions src/parsers/checkov.ts
Original file line number Diff line number Diff line change
@@ -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 };
32 changes: 32 additions & 0 deletions src/parsers/gosec.ts
Original file line number Diff line number Diff line change
@@ -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 };
7 changes: 2 additions & 5 deletions src/parsers/semgrep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import * as vscode from 'vscode';
import { ToolFinding } from '../models/toolFinding';
import { relativePathToFull } from '../utils';

class SemgrepParser {
static parse(fileContent: string) {
Expand All @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion src/webviews/assets/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
34 changes: 27 additions & 7 deletions src/webviews/importToolResultsWebview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
}
}
});
Expand Down Expand Up @@ -85,7 +84,11 @@ export class ImportToolResultsWebview implements vscode.WebviewViewProvider {
<p>Select tool:</p>
<p>
<select id="toolSelect">
<option value="semgrep">semgrep</option>
<option value="bandit">bandit (JSON)</option>
<option value="brakeman">brakeman (JSON)</option>
<option value="checkov">checkov (JSON)</option>
<option value="gosec">gosec (JSON)</option>
<option value="semgrep">semgrep (JSON)</option>
</select>
</p>
<p>Select file:</p>
Expand All @@ -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;
}
}

Expand Down