secure-review-extension 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +304 -0
- package/bin/secure-review.js +269 -0
- package/extension.js +368 -0
- package/media/shield.png +0 -0
- package/media/shield.svg +6 -0
- package/package.json +323 -0
- package/scripts/bootstrap-review-tools.js +54 -0
- package/src/code-actions.js +47 -0
- package/src/constants.js +20 -0
- package/src/diagnostics.js +41 -0
- package/src/findings-provider.js +78 -0
- package/src/report.js +837 -0
- package/src/scanners/bootstrap-tools.js +303 -0
- package/src/scanners/dynamic-scan.js +224 -0
- package/src/scanners/static-rules.js +497 -0
- package/src/scanners/static-scan.js +341 -0
- package/src/scanners/tool-integrations.js +666 -0
- package/src/scanners/workspace-profile.js +316 -0
- package/src/store.js +49 -0
- package/src/utils.js +24 -0
package/package.json
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "secure-review-extension",
|
|
3
|
+
"displayName": "Secure Review",
|
|
4
|
+
"description": "Run deep static and Docker-based dynamic secure code reviews directly inside VS Code.",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"publisher": "your-publisher-id",
|
|
7
|
+
"icon": "media/shield.png",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"homepage": "https://bitbucket.org/macehand/secure-review-extension",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://bitbucket.org/macehand/secure-review-extension.git"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin/",
|
|
16
|
+
"src/",
|
|
17
|
+
"scripts/",
|
|
18
|
+
"media/",
|
|
19
|
+
"extension.js",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE",
|
|
22
|
+
"CHANGELOG.md"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://bitbucket.org/macehand/secure-review-extension.git"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"security",
|
|
32
|
+
"appsec",
|
|
33
|
+
"sast",
|
|
34
|
+
"dast",
|
|
35
|
+
"code-review",
|
|
36
|
+
"vscode-extension",
|
|
37
|
+
"secure-review"
|
|
38
|
+
],
|
|
39
|
+
"galleryBanner": {
|
|
40
|
+
"color": "#1F4E79",
|
|
41
|
+
"theme": "light"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"vscode": "^1.85.0"
|
|
45
|
+
},
|
|
46
|
+
"categories": [
|
|
47
|
+
"Linters",
|
|
48
|
+
"Testing",
|
|
49
|
+
"Other"
|
|
50
|
+
],
|
|
51
|
+
"activationEvents": [
|
|
52
|
+
"onStartupFinished",
|
|
53
|
+
"onCommand:secureReview.inspectRequirements",
|
|
54
|
+
"onCommand:secureReview.scanWorkspace",
|
|
55
|
+
"onCommand:secureReview.scanCurrentFile",
|
|
56
|
+
"onCommand:secureReview.dynamicScan",
|
|
57
|
+
"onCommand:secureReview.openReport",
|
|
58
|
+
"onView:secureReview.findingsView"
|
|
59
|
+
],
|
|
60
|
+
"main": "./extension.js",
|
|
61
|
+
"bin": {
|
|
62
|
+
"secure-review": "./bin/secure-review.js"
|
|
63
|
+
},
|
|
64
|
+
"contributes": {
|
|
65
|
+
"commands": [
|
|
66
|
+
{
|
|
67
|
+
"command": "secureReview.inspectRequirements",
|
|
68
|
+
"title": "Check Workspace Requirements",
|
|
69
|
+
"category": "Secure Review"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"command": "secureReview.scanWorkspace",
|
|
73
|
+
"title": "Scan Workspace",
|
|
74
|
+
"category": "Secure Review"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"command": "secureReview.scanCurrentFile",
|
|
78
|
+
"title": "Scan Current File",
|
|
79
|
+
"category": "Secure Review"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"command": "secureReview.dynamicScan",
|
|
83
|
+
"title": "Dynamic Scan Local App",
|
|
84
|
+
"category": "Secure Review"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"command": "secureReview.openReport",
|
|
88
|
+
"title": "Open Review Report",
|
|
89
|
+
"category": "Secure Review"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"command": "secureReview.exportReport",
|
|
93
|
+
"title": "Export Findings Report",
|
|
94
|
+
"category": "Secure Review"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"command": "secureReview.clearFindings",
|
|
98
|
+
"title": "Clear Findings",
|
|
99
|
+
"category": "Secure Review"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"command": "secureReview.ignoreFinding",
|
|
103
|
+
"title": "Ignore Finding",
|
|
104
|
+
"category": "Secure Review"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"command": "secureReview.openFinding",
|
|
108
|
+
"title": "Open Finding",
|
|
109
|
+
"category": "Secure Review"
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
"viewsContainers": {
|
|
113
|
+
"activitybar": [
|
|
114
|
+
{
|
|
115
|
+
"id": "secureReview",
|
|
116
|
+
"title": "Secure Review",
|
|
117
|
+
"icon": "media/shield.svg"
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
},
|
|
121
|
+
"views": {
|
|
122
|
+
"secureReview": [
|
|
123
|
+
{
|
|
124
|
+
"id": "secureReview.findingsView",
|
|
125
|
+
"name": "Findings"
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
"menus": {
|
|
130
|
+
"view/title": [
|
|
131
|
+
{
|
|
132
|
+
"command": "secureReview.inspectRequirements",
|
|
133
|
+
"when": "view == secureReview.findingsView",
|
|
134
|
+
"group": "navigation"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"command": "secureReview.scanWorkspace",
|
|
138
|
+
"when": "view == secureReview.findingsView",
|
|
139
|
+
"group": "navigation"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"command": "secureReview.dynamicScan",
|
|
143
|
+
"when": "view == secureReview.findingsView",
|
|
144
|
+
"group": "navigation"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"command": "secureReview.openReport",
|
|
148
|
+
"when": "view == secureReview.findingsView",
|
|
149
|
+
"group": "navigation"
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
"view/item/context": [
|
|
153
|
+
{
|
|
154
|
+
"command": "secureReview.openFinding",
|
|
155
|
+
"when": "view == secureReview.findingsView && viewItem == finding",
|
|
156
|
+
"group": "inline"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"command": "secureReview.ignoreFinding",
|
|
160
|
+
"when": "view == secureReview.findingsView && viewItem == finding",
|
|
161
|
+
"group": "inline"
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
},
|
|
165
|
+
"configuration": {
|
|
166
|
+
"title": "Secure Review",
|
|
167
|
+
"properties": {
|
|
168
|
+
"secureReview.runOnSave": {
|
|
169
|
+
"type": "boolean",
|
|
170
|
+
"default": false,
|
|
171
|
+
"description": "Automatically rescan the current file after it is saved."
|
|
172
|
+
},
|
|
173
|
+
"secureReview.excludeGlobs": {
|
|
174
|
+
"type": "array",
|
|
175
|
+
"default": [
|
|
176
|
+
"**/node_modules/**",
|
|
177
|
+
"**/.git/**",
|
|
178
|
+
"**/dist/**",
|
|
179
|
+
"**/build/**",
|
|
180
|
+
"**/.next/**",
|
|
181
|
+
"**/coverage/**"
|
|
182
|
+
],
|
|
183
|
+
"items": {
|
|
184
|
+
"type": "string"
|
|
185
|
+
},
|
|
186
|
+
"description": "Glob patterns excluded from static scanning."
|
|
187
|
+
},
|
|
188
|
+
"secureReview.minimumSeverity": {
|
|
189
|
+
"type": "string",
|
|
190
|
+
"enum": [
|
|
191
|
+
"low",
|
|
192
|
+
"medium",
|
|
193
|
+
"high",
|
|
194
|
+
"critical"
|
|
195
|
+
],
|
|
196
|
+
"default": "low",
|
|
197
|
+
"description": "Lowest severity shown in the findings panel."
|
|
198
|
+
},
|
|
199
|
+
"secureReview.maxFiles": {
|
|
200
|
+
"type": "number",
|
|
201
|
+
"default": 400,
|
|
202
|
+
"minimum": 1,
|
|
203
|
+
"description": "Maximum number of workspace files scanned in one run."
|
|
204
|
+
},
|
|
205
|
+
"secureReview.enableBuiltInRules": {
|
|
206
|
+
"type": "boolean",
|
|
207
|
+
"default": true,
|
|
208
|
+
"description": "Run the built-in static review rule packs."
|
|
209
|
+
},
|
|
210
|
+
"secureReview.enableSemgrep": {
|
|
211
|
+
"type": "boolean",
|
|
212
|
+
"default": true,
|
|
213
|
+
"description": "If Semgrep is installed locally, include its results in the workspace review."
|
|
214
|
+
},
|
|
215
|
+
"secureReview.enableEslint": {
|
|
216
|
+
"type": "boolean",
|
|
217
|
+
"default": true,
|
|
218
|
+
"description": "If ESLint is installed locally, include JavaScript, TypeScript, and frontend lint findings."
|
|
219
|
+
},
|
|
220
|
+
"secureReview.enableNpmAudit": {
|
|
221
|
+
"type": "boolean",
|
|
222
|
+
"default": true,
|
|
223
|
+
"description": "If package.json exists and npm is installed locally, include npm audit results."
|
|
224
|
+
},
|
|
225
|
+
"secureReview.enableBandit": {
|
|
226
|
+
"type": "boolean",
|
|
227
|
+
"default": true,
|
|
228
|
+
"description": "If Bandit is installed locally, include Python security analysis results."
|
|
229
|
+
},
|
|
230
|
+
"secureReview.enablePipAudit": {
|
|
231
|
+
"type": "boolean",
|
|
232
|
+
"default": true,
|
|
233
|
+
"description": "If Python dependency files exist and pip-audit is installed locally, include its results."
|
|
234
|
+
},
|
|
235
|
+
"secureReview.enableSpotBugs": {
|
|
236
|
+
"type": "boolean",
|
|
237
|
+
"default": true,
|
|
238
|
+
"description": "If SpotBugs is installed locally and compiled Java classes are available, include Java bytecode findings."
|
|
239
|
+
},
|
|
240
|
+
"secureReview.enableGosec": {
|
|
241
|
+
"type": "boolean",
|
|
242
|
+
"default": true,
|
|
243
|
+
"description": "If gosec is installed locally, include Go security findings."
|
|
244
|
+
},
|
|
245
|
+
"secureReview.enableGovulncheck": {
|
|
246
|
+
"type": "boolean",
|
|
247
|
+
"default": true,
|
|
248
|
+
"description": "If govulncheck is installed locally, include Go vulnerability findings."
|
|
249
|
+
},
|
|
250
|
+
"secureReview.enableCargoAudit": {
|
|
251
|
+
"type": "boolean",
|
|
252
|
+
"default": true,
|
|
253
|
+
"description": "If cargo-audit is available, include Rust dependency advisories."
|
|
254
|
+
},
|
|
255
|
+
"secureReview.enableClippy": {
|
|
256
|
+
"type": "boolean",
|
|
257
|
+
"default": true,
|
|
258
|
+
"description": "If cargo clippy is available, include Rust lint findings."
|
|
259
|
+
},
|
|
260
|
+
"secureReview.enableCppcheck": {
|
|
261
|
+
"type": "boolean",
|
|
262
|
+
"default": true,
|
|
263
|
+
"description": "If cppcheck is installed locally, include C and C++ static analysis findings."
|
|
264
|
+
},
|
|
265
|
+
"secureReview.dynamicBaseUrl": {
|
|
266
|
+
"type": "string",
|
|
267
|
+
"default": "http://127.0.0.1:3000",
|
|
268
|
+
"description": "Local or test application URL used for Docker-based ZAP scanning."
|
|
269
|
+
},
|
|
270
|
+
"secureReview.dynamicScanMode": {
|
|
271
|
+
"type": "string",
|
|
272
|
+
"enum": [
|
|
273
|
+
"baseline",
|
|
274
|
+
"full"
|
|
275
|
+
],
|
|
276
|
+
"default": "baseline",
|
|
277
|
+
"description": "ZAP Docker scan mode. Baseline is passive-focused; full scan is more intrusive."
|
|
278
|
+
},
|
|
279
|
+
"secureReview.dynamicSpiderMinutes": {
|
|
280
|
+
"type": "number",
|
|
281
|
+
"default": 1,
|
|
282
|
+
"minimum": 1,
|
|
283
|
+
"description": "Minutes ZAP should spend spidering the target before reporting."
|
|
284
|
+
},
|
|
285
|
+
"secureReview.dynamicUseAjaxSpider": {
|
|
286
|
+
"type": "boolean",
|
|
287
|
+
"default": false,
|
|
288
|
+
"description": "Enable the ZAP Ajax spider in addition to the traditional spider."
|
|
289
|
+
},
|
|
290
|
+
"secureReview.dynamicAllowHosts": {
|
|
291
|
+
"type": "array",
|
|
292
|
+
"default": [
|
|
293
|
+
"127.0.0.1",
|
|
294
|
+
"localhost"
|
|
295
|
+
],
|
|
296
|
+
"items": {
|
|
297
|
+
"type": "string"
|
|
298
|
+
},
|
|
299
|
+
"description": "Explicit hostnames allowed for dynamic scans."
|
|
300
|
+
},
|
|
301
|
+
"secureReview.zapDockerImage": {
|
|
302
|
+
"type": "string",
|
|
303
|
+
"default": "ghcr.io/zaproxy/zaproxy:stable",
|
|
304
|
+
"description": "Docker image used to run OWASP ZAP scans."
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
"scripts": {
|
|
310
|
+
"cli": "node bin/secure-review.js",
|
|
311
|
+
"bootstrap:tools": "node scripts/bootstrap-review-tools.js",
|
|
312
|
+
"bootstrap:tools:run": "node scripts/bootstrap-review-tools.js --run",
|
|
313
|
+
"verify": "node --check extension.js && node --check bin/secure-review.js && node --check scripts/bootstrap-review-tools.js && node --check src/report.js && node --check src/scanners/static-scan.js && node --check src/scanners/tool-integrations.js && node --check src/scanners/workspace-profile.js && node --check src/scanners/bootstrap-tools.js && node --check src/scanners/dynamic-scan.js",
|
|
314
|
+
"pack:cli": "npm pack",
|
|
315
|
+
"pack:cli:dry-run": "npm pack --dry-run",
|
|
316
|
+
"publish:npm": "npm publish",
|
|
317
|
+
"package": "npx @vscode/vsce package",
|
|
318
|
+
"publish:precheck": "npm run verify && npx @vscode/vsce package --allow-missing-repository",
|
|
319
|
+
"publish:marketplace": "npx @vscode/vsce publish",
|
|
320
|
+
"lint": "echo \"No lint tool configured\"",
|
|
321
|
+
"test": "echo \"No test runner configured\""
|
|
322
|
+
}
|
|
323
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require("node:child_process");
|
|
4
|
+
const { detectWorkspace, buildInstallPlan, formatInstallPlan } = require("../src/scanners/bootstrap-tools");
|
|
5
|
+
|
|
6
|
+
const cwd = process.cwd();
|
|
7
|
+
const args = new Set(process.argv.slice(2));
|
|
8
|
+
const shouldRun = args.has("--run");
|
|
9
|
+
|
|
10
|
+
function main() {
|
|
11
|
+
const profile = detectWorkspace(cwd);
|
|
12
|
+
const plan = buildInstallPlan(profile);
|
|
13
|
+
|
|
14
|
+
console.log(formatInstallPlan(profile, plan));
|
|
15
|
+
|
|
16
|
+
if (!shouldRun) {
|
|
17
|
+
console.log("");
|
|
18
|
+
console.log("Dry run only. Use `node scripts/bootstrap-review-tools.js --run` to execute supported install commands.");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log("");
|
|
23
|
+
executePlan(plan);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function executePlan(plan) {
|
|
27
|
+
for (const item of plan) {
|
|
28
|
+
if (item.install.kind === "already-installed") {
|
|
29
|
+
console.log(`Skipping ${item.tool}: already available.`);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (item.install.kind !== "command") {
|
|
34
|
+
console.log(`Skipping ${item.tool}: ${item.install.note}`);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const command = item.install.command;
|
|
39
|
+
console.log(`Running ${command.join(" ")}`);
|
|
40
|
+
const result = spawnSync(command[0], command.slice(1), {
|
|
41
|
+
stdio: "inherit",
|
|
42
|
+
cwd
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (result.status !== 0) {
|
|
46
|
+
console.error(`Install failed for ${item.tool}.`);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(`Installed ${item.tool}.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
main();
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const vscode = require("vscode");
|
|
2
|
+
|
|
3
|
+
class SecureReviewCodeActionProvider {
|
|
4
|
+
constructor(store) {
|
|
5
|
+
this.store = store;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
provideCodeActions(document, range) {
|
|
9
|
+
const actions = [];
|
|
10
|
+
const findings = this.store.findings.filter(
|
|
11
|
+
(finding) =>
|
|
12
|
+
finding.filePath === document.uri.fsPath &&
|
|
13
|
+
typeof finding.line === "number" &&
|
|
14
|
+
finding.line - 1 === range.start.line
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
for (const finding of findings) {
|
|
18
|
+
const openAction = new vscode.CodeAction(
|
|
19
|
+
`Open Secure Review finding: ${finding.title}`,
|
|
20
|
+
vscode.CodeActionKind.QuickFix
|
|
21
|
+
);
|
|
22
|
+
openAction.command = {
|
|
23
|
+
command: "secureReview.openFinding",
|
|
24
|
+
title: "Open Finding",
|
|
25
|
+
arguments: [finding]
|
|
26
|
+
};
|
|
27
|
+
actions.push(openAction);
|
|
28
|
+
|
|
29
|
+
const ignoreAction = new vscode.CodeAction(
|
|
30
|
+
`Ignore Secure Review finding: ${finding.code}`,
|
|
31
|
+
vscode.CodeActionKind.QuickFix
|
|
32
|
+
);
|
|
33
|
+
ignoreAction.command = {
|
|
34
|
+
command: "secureReview.ignoreFinding",
|
|
35
|
+
title: "Ignore Finding",
|
|
36
|
+
arguments: [finding]
|
|
37
|
+
};
|
|
38
|
+
actions.push(ignoreAction);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return actions;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
SecureReviewCodeActionProvider
|
|
47
|
+
};
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const vscode = require("vscode");
|
|
2
|
+
|
|
3
|
+
const severityOrder = {
|
|
4
|
+
low: 0,
|
|
5
|
+
medium: 1,
|
|
6
|
+
high: 2,
|
|
7
|
+
critical: 3
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const diagnosticSeverityMap = {
|
|
11
|
+
low: vscode.DiagnosticSeverity.Information,
|
|
12
|
+
medium: vscode.DiagnosticSeverity.Warning,
|
|
13
|
+
high: vscode.DiagnosticSeverity.Warning,
|
|
14
|
+
critical: vscode.DiagnosticSeverity.Error
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
module.exports = {
|
|
18
|
+
severityOrder,
|
|
19
|
+
diagnosticSeverityMap
|
|
20
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const vscode = require("vscode");
|
|
2
|
+
const { diagnosticSeverityMap } = require("./constants");
|
|
3
|
+
|
|
4
|
+
function applyDiagnostics(collection, findings) {
|
|
5
|
+
collection.clear();
|
|
6
|
+
|
|
7
|
+
const grouped = new Map();
|
|
8
|
+
|
|
9
|
+
for (const finding of findings) {
|
|
10
|
+
if (!finding.filePath || typeof finding.line !== "number") {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const uri = vscode.Uri.file(finding.filePath);
|
|
15
|
+
const list = grouped.get(uri.toString()) || [];
|
|
16
|
+
const range = new vscode.Range(
|
|
17
|
+
Math.max(0, finding.line - 1),
|
|
18
|
+
Math.max(0, (finding.column || 1) - 1),
|
|
19
|
+
Math.max(0, finding.line - 1),
|
|
20
|
+
Math.max(0, (finding.column || 1) + 8)
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
list.push(
|
|
24
|
+
new vscode.Diagnostic(
|
|
25
|
+
range,
|
|
26
|
+
`${finding.title}. ${finding.remediation}`,
|
|
27
|
+
diagnosticSeverityMap[finding.severity]
|
|
28
|
+
)
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
grouped.set(uri.toString(), list);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const [uriString, diagnostics] of grouped.entries()) {
|
|
35
|
+
collection.set(vscode.Uri.parse(uriString), diagnostics);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
applyDiagnostics
|
|
41
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const vscode = require("vscode");
|
|
2
|
+
const { severityOrder } = require("./constants");
|
|
3
|
+
|
|
4
|
+
class SeverityNode extends vscode.TreeItem {
|
|
5
|
+
constructor(label, count) {
|
|
6
|
+
super(`${label} (${count})`, vscode.TreeItemCollapsibleState.Expanded);
|
|
7
|
+
this.contextValue = "severityGroup";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class FindingNode extends vscode.TreeItem {
|
|
12
|
+
constructor(finding) {
|
|
13
|
+
super(finding.title, vscode.TreeItemCollapsibleState.None);
|
|
14
|
+
this.finding = finding;
|
|
15
|
+
this.contextValue = "finding";
|
|
16
|
+
this.description = finding.source === "dynamic"
|
|
17
|
+
? `${finding.category} • ${finding.targetUrl || finding.reviewDomain}`
|
|
18
|
+
: `${finding.category} • ${finding.reviewDomain}`;
|
|
19
|
+
this.tooltip = `${finding.title}\nSeverity: ${finding.severity}\nConfidence: ${finding.confidence}\n${finding.message}`;
|
|
20
|
+
this.command = {
|
|
21
|
+
command: "secureReview.openFinding",
|
|
22
|
+
title: "Open Finding",
|
|
23
|
+
arguments: [finding]
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class FindingsProvider {
|
|
29
|
+
constructor(store, configAccessor) {
|
|
30
|
+
this.store = store;
|
|
31
|
+
this.configAccessor = configAccessor;
|
|
32
|
+
this._emitter = new vscode.EventEmitter();
|
|
33
|
+
this.onDidChangeTreeData = this._emitter.event;
|
|
34
|
+
this.store.onDidChange(() => this.refresh());
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
refresh() {
|
|
38
|
+
this._emitter.fire();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getTreeItem(element) {
|
|
42
|
+
return element;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getChildren(element) {
|
|
46
|
+
const minSeverity = this.configAccessor().get("minimumSeverity", "low");
|
|
47
|
+
const findings = this.store.getVisibleFindings(minSeverity, severityOrder);
|
|
48
|
+
|
|
49
|
+
if (!element) {
|
|
50
|
+
const counts = ["critical", "high", "medium", "low"]
|
|
51
|
+
.map((severity) => ({
|
|
52
|
+
severity,
|
|
53
|
+
count: findings.filter((finding) => finding.severity === severity).length
|
|
54
|
+
}))
|
|
55
|
+
.filter((entry) => entry.count > 0);
|
|
56
|
+
|
|
57
|
+
if (!counts.length) {
|
|
58
|
+
return [new SeverityNode("No findings", 0)];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return counts.map((entry) => new SeverityNode(entry.severity, entry.count));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (element instanceof SeverityNode && element.label !== "No findings (0)") {
|
|
65
|
+
const severity = String(element.label).split(" ")[0];
|
|
66
|
+
return findings
|
|
67
|
+
.filter((finding) => finding.severity === severity)
|
|
68
|
+
.sort((a, b) => (a.relativePath || a.targetUrl || "").localeCompare(b.relativePath || b.targetUrl || ""))
|
|
69
|
+
.map((finding) => new FindingNode(finding));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = {
|
|
77
|
+
FindingsProvider
|
|
78
|
+
};
|