secure-review-extension 1.0.5 → 1.0.6
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/bin/secure-review.js +317 -15
- package/extension.js +5 -1
- package/package.json +24 -3
- package/src/findings-provider.js +3 -3
- package/src/report.js +47 -2
- package/src/scanners/bootstrap-tools.js +58 -0
- package/src/scanners/dynamic-scan.js +133 -10
- package/src/scanners/static-rules.js +297 -10
- package/src/scanners/static-scan.js +762 -16
- package/src/scanners/tool-integrations.js +230 -1
- package/src/scanners/workspace-profile.js +44 -3
package/bin/secure-review.js
CHANGED
|
@@ -147,6 +147,7 @@ async function scanWorkspace(workspaceArg, flags) {
|
|
|
147
147
|
workspaceRoot,
|
|
148
148
|
target: flags["dynamic-url"],
|
|
149
149
|
scanMode: flags["dynamic-mode"] || "baseline",
|
|
150
|
+
apiDefinitionUrl: flags["dynamic-api-url"] || "",
|
|
150
151
|
allowHosts: splitCsv(flags["dynamic-allow-hosts"]) || ["127.0.0.1", "localhost"],
|
|
151
152
|
spiderMinutes: Number(flags["dynamic-spider-minutes"] || 1),
|
|
152
153
|
useAjaxSpider: Boolean(flags["dynamic-ajax"]),
|
|
@@ -187,12 +188,15 @@ function buildStaticConfig(flags) {
|
|
|
187
188
|
enableNpmAudit: !isFalse(flags["npm-audit"]),
|
|
188
189
|
enableBandit: !isFalse(flags.bandit),
|
|
189
190
|
enablePipAudit: !isFalse(flags["pip-audit"]),
|
|
191
|
+
enableBrakeman: !isFalse(flags.brakeman),
|
|
192
|
+
enablePhpStan: !isFalse(flags.phpstan),
|
|
190
193
|
enableSpotBugs: !isFalse(flags.spotbugs),
|
|
191
194
|
enableGosec: !isFalse(flags.gosec),
|
|
192
195
|
enableGovulncheck: !isFalse(flags.govulncheck),
|
|
193
196
|
enableCargoAudit: !isFalse(flags["cargo-audit"]),
|
|
194
197
|
enableClippy: !isFalse(flags.clippy),
|
|
195
|
-
enableCppcheck: !isFalse(flags.cppcheck)
|
|
198
|
+
enableCppcheck: !isFalse(flags.cppcheck),
|
|
199
|
+
enableDotnetVuln: !isFalse(flags["dotnet-vuln"])
|
|
196
200
|
};
|
|
197
201
|
}
|
|
198
202
|
|
|
@@ -238,14 +242,18 @@ function buildScanConfiguration(flags) {
|
|
|
238
242
|
`Bandit: ${isFalse(flags.bandit) ? "Disabled" : "Enabled"}`,
|
|
239
243
|
`npm audit: ${isFalse(flags["npm-audit"]) ? "Disabled" : "Enabled"}`,
|
|
240
244
|
`pip-audit: ${isFalse(flags["pip-audit"]) ? "Disabled" : "Enabled"}`,
|
|
245
|
+
`Brakeman: ${isFalse(flags.brakeman) ? "Disabled" : "Enabled"}`,
|
|
246
|
+
`PHPStan: ${isFalse(flags.phpstan) ? "Disabled" : "Enabled"}`,
|
|
241
247
|
`SpotBugs: ${isFalse(flags.spotbugs) ? "Disabled" : "Enabled"}`,
|
|
242
248
|
`gosec: ${isFalse(flags.gosec) ? "Disabled" : "Enabled"}`,
|
|
243
249
|
`govulncheck: ${isFalse(flags.govulncheck) ? "Disabled" : "Enabled"}`,
|
|
244
250
|
`cargo-audit: ${isFalse(flags["cargo-audit"]) ? "Disabled" : "Enabled"}`,
|
|
245
251
|
`Clippy: ${isFalse(flags.clippy) ? "Disabled" : "Enabled"}`,
|
|
246
252
|
`cppcheck: ${isFalse(flags.cppcheck) ? "Disabled" : "Enabled"}`,
|
|
253
|
+
`.NET package audit: ${isFalse(flags["dotnet-vuln"]) ? "Disabled" : "Enabled"}`,
|
|
247
254
|
flags["dynamic-url"] ? `Dynamic URL: ${flags["dynamic-url"]}` : null,
|
|
248
|
-
flags["dynamic-mode"] ? `Dynamic Mode: ${flags["dynamic-mode"]}` : null
|
|
255
|
+
flags["dynamic-mode"] ? `Dynamic Mode: ${flags["dynamic-mode"]}` : null,
|
|
256
|
+
flags["dynamic-api-url"] ? `Dynamic API Definition URL: ${flags["dynamic-api-url"]}` : null
|
|
249
257
|
].filter(Boolean).join("; ");
|
|
250
258
|
}
|
|
251
259
|
|
|
@@ -263,7 +271,7 @@ function printScanSummary(findings, workspaceRoot) {
|
|
|
263
271
|
const location = finding.relativePath
|
|
264
272
|
? `${finding.relativePath}:${finding.line || 1}`
|
|
265
273
|
: finding.targetUrl || "unknown-location";
|
|
266
|
-
console.log(`[${String(finding.severity || "low").toUpperCase()}] ${finding.title} :: ${location}`);
|
|
274
|
+
console.log(`[${String(finding.severity || "low").toUpperCase()}][${String(finding.findingType || "contextual-warning")}] ${finding.title} :: ${location} (${String(finding.evidenceStrength || "medium")} evidence)`);
|
|
267
275
|
}
|
|
268
276
|
|
|
269
277
|
if (findings.length > 25) {
|
|
@@ -313,21 +321,315 @@ function defaultProjectName(workspaceLabel) {
|
|
|
313
321
|
function printHelp() {
|
|
314
322
|
console.log(`secure-review
|
|
315
323
|
|
|
316
|
-
|
|
324
|
+
Secure Review is a local security-focused code review CLI for:
|
|
325
|
+
- local workspaces
|
|
326
|
+
- Bitbucket repositories
|
|
327
|
+
- static analysis
|
|
328
|
+
- dependency review
|
|
329
|
+
- runtime/config review
|
|
330
|
+
- optional Docker-based OWASP ZAP dynamic scans
|
|
331
|
+
- exportable reports in text, json, html, md, and docx
|
|
332
|
+
|
|
333
|
+
General syntax:
|
|
334
|
+
secure-review <command> [workspace-or-bitbucket-url] [flags]
|
|
335
|
+
|
|
336
|
+
Workspace input supports:
|
|
337
|
+
- local paths
|
|
338
|
+
- Bitbucket HTTPS repo URLs
|
|
339
|
+
- Bitbucket browser URLs like /src/<branch>/
|
|
340
|
+
- Bitbucket SSH clone URLs
|
|
341
|
+
|
|
342
|
+
Examples of valid workspace inputs:
|
|
343
|
+
/home/user/project
|
|
344
|
+
.
|
|
345
|
+
https://bitbucket.org/acme/payments-service
|
|
346
|
+
https://bitbucket.org/acme/payments-service/src/main/
|
|
347
|
+
git@bitbucket.org:acme/payments-service.git
|
|
348
|
+
|
|
349
|
+
Commands
|
|
350
|
+
|
|
351
|
+
1. inspect
|
|
352
|
+
Purpose:
|
|
353
|
+
Detect the workspace stack and show which scanner tools are relevant.
|
|
354
|
+
|
|
355
|
+
Syntax:
|
|
317
356
|
secure-review inspect [workspace-or-bitbucket-url] [--branch <name>]
|
|
318
|
-
secure-review bootstrap [workspace-or-bitbucket-url] [--run] [--branch <name>]
|
|
319
|
-
secure-review scan [workspace-or-bitbucket-url] [--severity low|medium|high|critical] [--tool-timeout-ms 120000] [--format text|json|html|md|docx] [--out <path>] [--branch <name>]
|
|
320
|
-
secure-review scan [workspace-or-bitbucket-url] --dynamic-url <url> [--dynamic-mode baseline|full]
|
|
321
357
|
|
|
322
|
-
|
|
323
|
-
|
|
358
|
+
Typical use cases:
|
|
359
|
+
- understand what languages/frameworks were detected
|
|
360
|
+
- see which external scanner tools should be installed
|
|
361
|
+
- inspect a local repo before first scan
|
|
362
|
+
- inspect a remote Bitbucket repo without cloning it manually
|
|
363
|
+
|
|
364
|
+
Examples:
|
|
365
|
+
secure-review inspect .
|
|
366
|
+
secure-review inspect /home/user/my-api
|
|
324
367
|
secure-review inspect https://bitbucket.org/acme/payments-service
|
|
325
|
-
secure-review inspect https://bitbucket.org/acme/payments-service/src/
|
|
326
|
-
secure-review
|
|
368
|
+
secure-review inspect https://bitbucket.org/acme/payments-service/src/release-1.2/
|
|
369
|
+
secure-review inspect git@bitbucket.org:acme/payments-service.git --branch develop
|
|
370
|
+
|
|
371
|
+
2. bootstrap
|
|
372
|
+
Purpose:
|
|
373
|
+
Show or execute the recommended scanner-tool installation plan for the target workspace.
|
|
374
|
+
|
|
375
|
+
Syntax:
|
|
376
|
+
secure-review bootstrap [workspace-or-bitbucket-url] [--branch <name>] [--run]
|
|
377
|
+
|
|
378
|
+
Behavior:
|
|
379
|
+
- without --run:
|
|
380
|
+
dry run only, prints planned installs
|
|
381
|
+
- with --run:
|
|
382
|
+
executes supported install commands for detected tools
|
|
383
|
+
|
|
384
|
+
Typical use cases:
|
|
385
|
+
- prepare a machine before scanning a new language stack
|
|
386
|
+
- check whether Python / Go / Rust / Java / Ruby / PHP / .NET helpers are needed
|
|
387
|
+
- bootstrap tools for a remote Bitbucket repo you do not have locally
|
|
388
|
+
|
|
389
|
+
Examples:
|
|
390
|
+
secure-review bootstrap .
|
|
391
|
+
secure-review bootstrap /home/user/service --run
|
|
327
392
|
secure-review bootstrap git@bitbucket.org:acme/payments-service.git
|
|
328
|
-
secure-review
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
393
|
+
secure-review bootstrap https://bitbucket.org/acme/payments-service/src/main/ --run
|
|
394
|
+
|
|
395
|
+
3. scan
|
|
396
|
+
Purpose:
|
|
397
|
+
Run static review, optional dynamic review, and optional report export.
|
|
398
|
+
|
|
399
|
+
Syntax:
|
|
400
|
+
secure-review scan [workspace-or-bitbucket-url] [flags]
|
|
401
|
+
|
|
402
|
+
Core scan flags:
|
|
403
|
+
--severity <low|medium|high|critical>
|
|
404
|
+
Minimum severity shown in the final output. Default: low
|
|
405
|
+
|
|
406
|
+
--max-files <number>
|
|
407
|
+
Maximum number of files scanned from the workspace. Default: 400
|
|
408
|
+
|
|
409
|
+
--tool-timeout-ms <milliseconds>
|
|
410
|
+
Per-tool timeout for external scanners. Default: 120000
|
|
411
|
+
|
|
412
|
+
--format <text|json|html|md|docx>
|
|
413
|
+
Output format. Default: text
|
|
414
|
+
|
|
415
|
+
--out <path>
|
|
416
|
+
Output report path when using json/html/md/docx
|
|
417
|
+
|
|
418
|
+
--branch <name>
|
|
419
|
+
Branch name to use when the target is a Bitbucket repository
|
|
420
|
+
|
|
421
|
+
Static scanner toggle flags:
|
|
422
|
+
--built-in-rules <true|false>
|
|
423
|
+
--semgrep <true|false>
|
|
424
|
+
--eslint <true|false>
|
|
425
|
+
--npm-audit <true|false>
|
|
426
|
+
--bandit <true|false>
|
|
427
|
+
--pip-audit <true|false>
|
|
428
|
+
--brakeman <true|false>
|
|
429
|
+
--phpstan <true|false>
|
|
430
|
+
--spotbugs <true|false>
|
|
431
|
+
--gosec <true|false>
|
|
432
|
+
--govulncheck <true|false>
|
|
433
|
+
--cargo-audit <true|false>
|
|
434
|
+
--clippy <true|false>
|
|
435
|
+
--cppcheck <true|false>
|
|
436
|
+
--dotnet-vuln <true|false>
|
|
437
|
+
|
|
438
|
+
Dynamic scan flags:
|
|
439
|
+
--dynamic-url <url>
|
|
440
|
+
Base URL of the running local/test application to scan with ZAP
|
|
441
|
+
|
|
442
|
+
--dynamic-mode <baseline|full|api>
|
|
443
|
+
baseline:
|
|
444
|
+
passive-oriented runtime scan
|
|
445
|
+
full:
|
|
446
|
+
active/intrusive runtime scan
|
|
447
|
+
api:
|
|
448
|
+
API-definition-driven ZAP scan
|
|
449
|
+
|
|
450
|
+
--dynamic-api-url <url>
|
|
451
|
+
API definition URL used when --dynamic-mode api
|
|
452
|
+
Supported practical inputs include OpenAPI/Swagger, GraphQL, and WSDL definition URLs
|
|
453
|
+
|
|
454
|
+
--dynamic-allow-hosts <csv>
|
|
455
|
+
Comma-separated allowlist of target hosts
|
|
456
|
+
Example: --dynamic-allow-hosts 127.0.0.1,localhost,dev.internal
|
|
457
|
+
|
|
458
|
+
--dynamic-spider-minutes <number>
|
|
459
|
+
ZAP spider duration in minutes. Default: 1
|
|
460
|
+
|
|
461
|
+
--dynamic-ajax
|
|
462
|
+
Enable the ZAP Ajax spider in addition to the traditional spider
|
|
463
|
+
|
|
464
|
+
--zap-image <image>
|
|
465
|
+
Override the Docker image used for ZAP
|
|
466
|
+
|
|
467
|
+
Report metadata flags:
|
|
468
|
+
--project <name>
|
|
469
|
+
Override report project name
|
|
470
|
+
|
|
471
|
+
--date <value>
|
|
472
|
+
Override report date
|
|
473
|
+
|
|
474
|
+
--notes <text>
|
|
475
|
+
Extra methodology/limitation notes included in exports
|
|
476
|
+
|
|
477
|
+
Common scenarios
|
|
478
|
+
|
|
479
|
+
Local static-only scan:
|
|
480
|
+
secure-review scan /home/user/project --severity medium
|
|
481
|
+
|
|
482
|
+
Local static scan with JSON output:
|
|
483
|
+
secure-review scan /home/user/project --severity low --format json --out findings.json
|
|
484
|
+
|
|
485
|
+
Local scan with HTML report:
|
|
486
|
+
secure-review scan /home/user/project --format html --out secure-review-report.html
|
|
487
|
+
|
|
488
|
+
Local scan with DOCX report:
|
|
489
|
+
secure-review scan /home/user/project --format docx --out secure-review-report.docx
|
|
490
|
+
|
|
491
|
+
Remote Bitbucket scan over SSH:
|
|
492
|
+
secure-review scan git@bitbucket.org:acme/payments-service.git --severity medium
|
|
493
|
+
|
|
494
|
+
Remote Bitbucket scan for a specific branch:
|
|
495
|
+
secure-review scan git@bitbucket.org:acme/payments-service.git --branch release-1.2 --severity high
|
|
496
|
+
|
|
497
|
+
Remote Bitbucket browser URL scan:
|
|
498
|
+
secure-review scan https://bitbucket.org/acme/payments-service/src/main/ --severity medium
|
|
499
|
+
|
|
500
|
+
Quick built-in-rules-only scan:
|
|
501
|
+
secure-review scan /home/user/project --semgrep false --eslint false --bandit false --pip-audit false --npm-audit false
|
|
502
|
+
|
|
503
|
+
Scan with slower tools given more time:
|
|
504
|
+
secure-review scan /home/user/project --tool-timeout-ms 600000
|
|
505
|
+
|
|
506
|
+
Scan with selected tools disabled:
|
|
507
|
+
secure-review scan /home/user/project --semgrep false --bandit false --gosec false
|
|
508
|
+
|
|
509
|
+
Baseline dynamic scan against a local app:
|
|
510
|
+
secure-review scan /home/user/project --dynamic-url http://127.0.0.1:3001 --dynamic-mode baseline
|
|
511
|
+
|
|
512
|
+
Baseline dynamic scan plus HTML report:
|
|
513
|
+
secure-review scan /home/user/project --dynamic-url http://127.0.0.1:3001 --dynamic-mode baseline --format html --out report.html
|
|
514
|
+
|
|
515
|
+
Full dynamic scan against a developer-controlled test app:
|
|
516
|
+
secure-review scan /home/user/project --dynamic-url http://127.0.0.1:3001 --dynamic-mode full --dynamic-spider-minutes 3
|
|
517
|
+
|
|
518
|
+
API-definition-driven dynamic scan:
|
|
519
|
+
secure-review scan /home/user/project --dynamic-url http://127.0.0.1:3001 --dynamic-mode api --dynamic-api-url http://127.0.0.1:3001/openapi.json
|
|
520
|
+
|
|
521
|
+
Remote repo + dynamic scan together:
|
|
522
|
+
secure-review scan git@bitbucket.org:acme/payments-service.git --dynamic-url http://127.0.0.1:3001 --dynamic-mode baseline --format docx --out remote-review.docx
|
|
523
|
+
|
|
524
|
+
Language-specific examples
|
|
525
|
+
|
|
526
|
+
Python-heavy repo:
|
|
527
|
+
secure-review bootstrap /home/user/python-service --run
|
|
528
|
+
secure-review scan /home/user/python-service --bandit true --pip-audit true --severity medium
|
|
529
|
+
|
|
530
|
+
Node / React repo:
|
|
531
|
+
secure-review bootstrap /home/user/frontend --run
|
|
532
|
+
secure-review scan /home/user/frontend --eslint true --npm-audit true --format html --out frontend-review.html
|
|
533
|
+
|
|
534
|
+
Java repo:
|
|
535
|
+
secure-review scan /home/user/java-service --spotbugs true --semgrep true
|
|
536
|
+
|
|
537
|
+
Go repo:
|
|
538
|
+
secure-review scan /home/user/go-service --gosec true --govulncheck true
|
|
539
|
+
|
|
540
|
+
Rust repo:
|
|
541
|
+
secure-review scan /home/user/rust-service --cargo-audit true --clippy true
|
|
542
|
+
|
|
543
|
+
PHP repo:
|
|
544
|
+
secure-review bootstrap /home/user/php-app --run
|
|
545
|
+
secure-review scan /home/user/php-app --phpstan true
|
|
546
|
+
|
|
547
|
+
Ruby / Rails repo:
|
|
548
|
+
secure-review bootstrap /home/user/rails-app --run
|
|
549
|
+
secure-review scan /home/user/rails-app --brakeman true
|
|
550
|
+
|
|
551
|
+
.NET repo:
|
|
552
|
+
secure-review scan /home/user/dotnet-api --dotnet-vuln true
|
|
553
|
+
|
|
554
|
+
How severity filtering works:
|
|
555
|
+
--severity low
|
|
556
|
+
show everything
|
|
557
|
+
--severity medium
|
|
558
|
+
hide low
|
|
559
|
+
--severity high
|
|
560
|
+
show only high and critical
|
|
561
|
+
--severity critical
|
|
562
|
+
show only critical
|
|
563
|
+
|
|
564
|
+
How output formats behave:
|
|
565
|
+
text
|
|
566
|
+
prints a summary and first 25 findings to terminal
|
|
567
|
+
json
|
|
568
|
+
prints JSON to terminal if --out is omitted
|
|
569
|
+
writes JSON to file if --out is provided
|
|
570
|
+
html
|
|
571
|
+
writes an HTML report
|
|
572
|
+
md
|
|
573
|
+
writes a Markdown report
|
|
574
|
+
docx
|
|
575
|
+
writes a Word report
|
|
576
|
+
|
|
577
|
+
Bitbucket notes:
|
|
578
|
+
- public repos can often be scanned with HTTPS URLs
|
|
579
|
+
- private repos are best scanned with SSH URLs
|
|
580
|
+
- example SSH form:
|
|
581
|
+
git@bitbucket.org:workspace/repository.git
|
|
582
|
+
- if HTTPS auth is required, use a Bitbucket app password rather than a normal login password
|
|
583
|
+
|
|
584
|
+
Dynamic scan notes:
|
|
585
|
+
- dynamic scan requires Docker
|
|
586
|
+
- scans should only target developer-controlled local/test apps
|
|
587
|
+
- full mode is intrusive
|
|
588
|
+
- localhost targets are rewritten for Docker container access
|
|
589
|
+
- api mode requires both --dynamic-url and --dynamic-api-url
|
|
590
|
+
|
|
591
|
+
Exit behavior:
|
|
592
|
+
- prints findings to terminal for text mode
|
|
593
|
+
- writes files for json/html/md/docx modes
|
|
594
|
+
- returns non-zero on command failure
|
|
595
|
+
|
|
596
|
+
Troubleshooting
|
|
597
|
+
|
|
598
|
+
If a scan looks stuck:
|
|
599
|
+
- increase --tool-timeout-ms
|
|
600
|
+
- disable slower tools one by one
|
|
601
|
+
- use inspect/bootstrap first to confirm relevant tools
|
|
602
|
+
|
|
603
|
+
If a Bitbucket scan fails:
|
|
604
|
+
- prefer SSH URLs for private repos
|
|
605
|
+
- test SSH separately with:
|
|
606
|
+
ssh -T git@bitbucket.org
|
|
607
|
+
|
|
608
|
+
If a global npm install fails with EACCES:
|
|
609
|
+
- use sudo for system-wide install
|
|
610
|
+
- or configure a user-local npm global directory
|
|
611
|
+
|
|
612
|
+
If dynamic scan fails:
|
|
613
|
+
- confirm Docker is running
|
|
614
|
+
- confirm the app is reachable in a browser or curl
|
|
615
|
+
- confirm the host is in the allowed host list
|
|
616
|
+
- confirm the API definition URL is valid when using api mode
|
|
617
|
+
|
|
618
|
+
Quick start
|
|
619
|
+
1. secure-review inspect /path/to/repo
|
|
620
|
+
2. secure-review bootstrap /path/to/repo --run
|
|
621
|
+
3. secure-review scan /path/to/repo --severity medium
|
|
622
|
+
4. secure-review scan /path/to/repo --format docx --out review.docx
|
|
623
|
+
|
|
624
|
+
More examples
|
|
625
|
+
secure-review inspect .
|
|
626
|
+
secure-review bootstrap . --run
|
|
627
|
+
secure-review scan . --severity medium
|
|
628
|
+
secure-review scan . --format json
|
|
629
|
+
secure-review scan . --format html --out report.html
|
|
630
|
+
secure-review scan . --format md --out report.md
|
|
631
|
+
secure-review scan . --format docx --out report.docx
|
|
632
|
+
secure-review scan . --dynamic-url http://127.0.0.1:3001 --dynamic-mode baseline
|
|
633
|
+
secure-review scan . --dynamic-url http://127.0.0.1:3001 --dynamic-mode api --dynamic-api-url http://127.0.0.1:3001/openapi.json
|
|
332
634
|
`);
|
|
333
635
|
}
|
package/extension.js
CHANGED
|
@@ -107,6 +107,10 @@ function activate(context) {
|
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
+
if (mode === "api" && !configAccessor().get("dynamicApiDefinitionUrl", "")) {
|
|
111
|
+
vscode.window.showWarningMessage("Set Secure Review > Dynamic API Definition URL before running api mode scans.");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
110
114
|
|
|
111
115
|
await vscode.window.withProgress(
|
|
112
116
|
{
|
|
@@ -275,7 +279,7 @@ function activate(context) {
|
|
|
275
279
|
reportDate: lastReportMetadata?.reportDate || (lastRun || new Date()),
|
|
276
280
|
scanType: lastReportMetadata?.scanType || defaultScanType || (store.findings.some((item) => item.source === "dynamic") ? "Static + Dynamic Analysis" : "Static Analysis"),
|
|
277
281
|
notes: lastReportMetadata?.notes || "",
|
|
278
|
-
scanConfiguration: `Minimum Severity: ${configAccessor().get("minimumSeverity", "low")}; Run On Save: ${configAccessor().get("runOnSave", false) ? "Enabled" : "Disabled"}; Built-In Rules: ${configAccessor().get("enableBuiltInRules", true) ? "Enabled" : "Disabled"}; Semgrep: ${configAccessor().get("enableSemgrep", true) ? "Enabled" : "Disabled"}; ESLint: ${configAccessor().get("enableEslint", true) ? "Enabled" : "Disabled"}; Bandit: ${configAccessor().get("enableBandit", true) ? "Enabled" : "Disabled"}; npm audit: ${configAccessor().get("enableNpmAudit", true) ? "Enabled" : "Disabled"}; pip-audit: ${configAccessor().get("enablePipAudit", true) ? "Enabled" : "Disabled"}; SpotBugs: ${configAccessor().get("enableSpotBugs", true) ? "Enabled" : "Disabled"}; gosec: ${configAccessor().get("enableGosec", true) ? "Enabled" : "Disabled"}; govulncheck: ${configAccessor().get("enableGovulncheck", true) ? "Enabled" : "Disabled"}; cargo-audit: ${configAccessor().get("enableCargoAudit", true) ? "Enabled" : "Disabled"}; Clippy: ${configAccessor().get("enableClippy", true) ? "Enabled" : "Disabled"}; cppcheck: ${configAccessor().get("enableCppcheck", true) ? "Enabled" : "Disabled"}; Dynamic URL: ${configAccessor().get("dynamicBaseUrl", "http://127.0.0.1:3000")}; Dynamic Mode: ${configAccessor().get("dynamicScanMode", "baseline")}; Ajax Spider: ${configAccessor().get("dynamicUseAjaxSpider", false) ? "Enabled" : "Disabled"}`
|
|
282
|
+
scanConfiguration: `Minimum Severity: ${configAccessor().get("minimumSeverity", "low")}; Run On Save: ${configAccessor().get("runOnSave", false) ? "Enabled" : "Disabled"}; Built-In Rules: ${configAccessor().get("enableBuiltInRules", true) ? "Enabled" : "Disabled"}; Semgrep: ${configAccessor().get("enableSemgrep", true) ? "Enabled" : "Disabled"}; ESLint: ${configAccessor().get("enableEslint", true) ? "Enabled" : "Disabled"}; Bandit: ${configAccessor().get("enableBandit", true) ? "Enabled" : "Disabled"}; npm audit: ${configAccessor().get("enableNpmAudit", true) ? "Enabled" : "Disabled"}; pip-audit: ${configAccessor().get("enablePipAudit", true) ? "Enabled" : "Disabled"}; Brakeman: ${configAccessor().get("enableBrakeman", true) ? "Enabled" : "Disabled"}; PHPStan: ${configAccessor().get("enablePhpStan", true) ? "Enabled" : "Disabled"}; SpotBugs: ${configAccessor().get("enableSpotBugs", true) ? "Enabled" : "Disabled"}; gosec: ${configAccessor().get("enableGosec", true) ? "Enabled" : "Disabled"}; govulncheck: ${configAccessor().get("enableGovulncheck", true) ? "Enabled" : "Disabled"}; cargo-audit: ${configAccessor().get("enableCargoAudit", true) ? "Enabled" : "Disabled"}; Clippy: ${configAccessor().get("enableClippy", true) ? "Enabled" : "Disabled"}; cppcheck: ${configAccessor().get("enableCppcheck", true) ? "Enabled" : "Disabled"}; .NET package audit: ${configAccessor().get("enableDotnetVuln", true) ? "Enabled" : "Disabled"}; Dynamic URL: ${configAccessor().get("dynamicBaseUrl", "http://127.0.0.1:3000")}; Dynamic Mode: ${configAccessor().get("dynamicScanMode", "baseline")}; Dynamic API Definition URL: ${configAccessor().get("dynamicApiDefinitionUrl", "") || "n/a"}; Ajax Spider: ${configAccessor().get("dynamicUseAjaxSpider", false) ? "Enabled" : "Disabled"}`
|
|
279
283
|
};
|
|
280
284
|
}
|
|
281
285
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "secure-review-extension",
|
|
3
3
|
"displayName": "Secure Review",
|
|
4
4
|
"description": "Run deep static and Docker-based dynamic secure code reviews directly inside VS Code.",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.6",
|
|
6
6
|
"publisher": "your-publisher-id",
|
|
7
7
|
"icon": "media/shield.png",
|
|
8
8
|
"license": "MIT",
|
|
@@ -232,6 +232,16 @@
|
|
|
232
232
|
"default": true,
|
|
233
233
|
"description": "If Python dependency files exist and pip-audit is installed locally, include its results."
|
|
234
234
|
},
|
|
235
|
+
"secureReview.enableBrakeman": {
|
|
236
|
+
"type": "boolean",
|
|
237
|
+
"default": true,
|
|
238
|
+
"description": "If Brakeman is installed locally, include Ruby and Rails security findings."
|
|
239
|
+
},
|
|
240
|
+
"secureReview.enablePhpStan": {
|
|
241
|
+
"type": "boolean",
|
|
242
|
+
"default": true,
|
|
243
|
+
"description": "If PHPStan is installed locally, include PHP static analysis findings."
|
|
244
|
+
},
|
|
235
245
|
"secureReview.enableSpotBugs": {
|
|
236
246
|
"type": "boolean",
|
|
237
247
|
"default": true,
|
|
@@ -262,6 +272,11 @@
|
|
|
262
272
|
"default": true,
|
|
263
273
|
"description": "If cppcheck is installed locally, include C and C++ static analysis findings."
|
|
264
274
|
},
|
|
275
|
+
"secureReview.enableDotnetVuln": {
|
|
276
|
+
"type": "boolean",
|
|
277
|
+
"default": true,
|
|
278
|
+
"description": "If the .NET SDK is installed locally, include NuGet vulnerability findings from dotnet package audit."
|
|
279
|
+
},
|
|
265
280
|
"secureReview.dynamicBaseUrl": {
|
|
266
281
|
"type": "string",
|
|
267
282
|
"default": "http://127.0.0.1:3000",
|
|
@@ -271,10 +286,16 @@
|
|
|
271
286
|
"type": "string",
|
|
272
287
|
"enum": [
|
|
273
288
|
"baseline",
|
|
274
|
-
"full"
|
|
289
|
+
"full",
|
|
290
|
+
"api"
|
|
275
291
|
],
|
|
276
292
|
"default": "baseline",
|
|
277
|
-
"description": "ZAP Docker scan mode. Baseline is passive-focused
|
|
293
|
+
"description": "ZAP Docker scan mode. Baseline is passive-focused, full scan is more intrusive, and api uses an API definition URL."
|
|
294
|
+
},
|
|
295
|
+
"secureReview.dynamicApiDefinitionUrl": {
|
|
296
|
+
"type": "string",
|
|
297
|
+
"default": "",
|
|
298
|
+
"description": "OpenAPI, GraphQL, or WSDL definition URL used when Dynamic Scan Mode is set to api."
|
|
278
299
|
},
|
|
279
300
|
"secureReview.dynamicSpiderMinutes": {
|
|
280
301
|
"type": "number",
|
package/src/findings-provider.js
CHANGED
|
@@ -14,9 +14,9 @@ class FindingNode extends vscode.TreeItem {
|
|
|
14
14
|
this.finding = finding;
|
|
15
15
|
this.contextValue = "finding";
|
|
16
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}`;
|
|
17
|
+
? `${finding.findingType || finding.category} • ${finding.targetUrl || finding.reviewDomain}`
|
|
18
|
+
: `${finding.findingType || finding.category} • ${finding.reviewDomain}`;
|
|
19
|
+
this.tooltip = `${finding.title}\nSeverity: ${finding.severity}\nConfidence: ${finding.confidence}\nEvidence strength: ${finding.evidenceStrength || "medium"}\nType: ${finding.findingType || "contextual-warning"}\n${finding.message}`;
|
|
20
20
|
this.command = {
|
|
21
21
|
command: "secureReview.openFinding",
|
|
22
22
|
title: "Open Finding",
|
package/src/report.js
CHANGED
|
@@ -105,14 +105,15 @@ function buildReportModel(findings, metadata = {}) {
|
|
|
105
105
|
|
|
106
106
|
function normalizeFinding(finding) {
|
|
107
107
|
const isDynamic = finding.source === "dynamic";
|
|
108
|
+
const dynamicLocation = formatDynamicLocation(finding);
|
|
108
109
|
const locationDisplay = isDynamic
|
|
109
|
-
?
|
|
110
|
+
? dynamicLocation
|
|
110
111
|
: finding.relativePath && finding.line
|
|
111
112
|
? `${finding.relativePath} (line ${finding.line})`
|
|
112
113
|
: finding.relativePath || finding.filePath || "Unknown file";
|
|
113
114
|
|
|
114
115
|
const overviewLocationDisplay = isDynamic
|
|
115
|
-
?
|
|
116
|
+
? dynamicLocation
|
|
116
117
|
: finding.relativePath && finding.line
|
|
117
118
|
? `${finding.relativePath}:${finding.line}`
|
|
118
119
|
: finding.relativePath || finding.filePath || "Unknown file";
|
|
@@ -125,6 +126,10 @@ function normalizeFinding(finding) {
|
|
|
125
126
|
subcategory: finding.subcategory || "General",
|
|
126
127
|
reviewDomain: finding.reviewDomain || "security",
|
|
127
128
|
confidence: finding.confidence || "medium",
|
|
129
|
+
findingType: finding.findingType || inferReportFindingType(finding),
|
|
130
|
+
evidenceStrength: finding.evidenceStrength || inferReportEvidenceStrength(finding),
|
|
131
|
+
flaggedBy: finding.flaggedBy || inferFlaggedBy(finding),
|
|
132
|
+
manualReviewDisplay: finding.manualReviewRecommended === false ? "No" : "Yes",
|
|
128
133
|
locationDisplay,
|
|
129
134
|
overviewLocationDisplay,
|
|
130
135
|
remediationSummary: shorten(stripHtml(finding.remediation || "No remediation provided."), 100),
|
|
@@ -136,6 +141,42 @@ function normalizeFinding(finding) {
|
|
|
136
141
|
};
|
|
137
142
|
}
|
|
138
143
|
|
|
144
|
+
function inferReportFindingType(finding) {
|
|
145
|
+
if (finding.source === "dynamic") {
|
|
146
|
+
return "runtime-vulnerability";
|
|
147
|
+
}
|
|
148
|
+
if (finding.reviewDomain === "dependency-risk") {
|
|
149
|
+
return "dependency-risk";
|
|
150
|
+
}
|
|
151
|
+
if (finding.reviewDomain === "maintainability" || finding.reviewDomain === "code-quality") {
|
|
152
|
+
return "recommendation";
|
|
153
|
+
}
|
|
154
|
+
return "contextual-warning";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function inferReportEvidenceStrength(finding) {
|
|
158
|
+
if (finding.source === "dynamic") {
|
|
159
|
+
return "medium";
|
|
160
|
+
}
|
|
161
|
+
return "medium";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function inferFlaggedBy(finding) {
|
|
165
|
+
if (finding.source === "dynamic") {
|
|
166
|
+
return "owasp-zap";
|
|
167
|
+
}
|
|
168
|
+
return "secure-review";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function formatDynamicLocation(finding) {
|
|
172
|
+
const primary = finding.targetUrl || "Unknown URL";
|
|
173
|
+
const extraCount = Math.max(0, Number(finding.relatedTargetCount || 0) - 1);
|
|
174
|
+
if (extraCount === 0) {
|
|
175
|
+
return primary;
|
|
176
|
+
}
|
|
177
|
+
return `${primary} (+${extraCount} more endpoint${extraCount === 1 ? "" : "s"})`;
|
|
178
|
+
}
|
|
179
|
+
|
|
139
180
|
function buildRecommendations(findings) {
|
|
140
181
|
const grouped = new Map();
|
|
141
182
|
for (const finding of findings) {
|
|
@@ -331,6 +372,10 @@ function renderFindingDetail(finding, index) {
|
|
|
331
372
|
<strong>Subcategory</strong><span>${escapeHtml(finding.subcategory)}</span>
|
|
332
373
|
<strong>Review Domain</strong><span>${escapeHtml(finding.reviewDomain)}</span>
|
|
333
374
|
<strong>Confidence</strong><span>${escapeHtml(finding.confidence)}</span>
|
|
375
|
+
<strong>Finding Type</strong><span>${escapeHtml(finding.findingType)}</span>
|
|
376
|
+
<strong>Evidence Strength</strong><span>${escapeHtml(finding.evidenceStrength)}</span>
|
|
377
|
+
<strong>Flagged By</strong><span>${escapeHtml(finding.flaggedBy)}</span>
|
|
378
|
+
<strong>Manual Review</strong><span>${escapeHtml(finding.manualReviewDisplay)}</span>
|
|
334
379
|
<strong>Finding ID</strong><span>${escapeHtml(finding.id)}</span>
|
|
335
380
|
<strong>File / Location</strong><span>${escapeHtml(finding.locationDisplay)}</span>
|
|
336
381
|
<strong>Source</strong><span>${escapeHtml(finding.source)}</span>
|
|
@@ -58,6 +58,19 @@ function detectWorkspace(workspaceRoot) {
|
|
|
58
58
|
languages.add("cpp");
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
if (exists("composer.json")) {
|
|
62
|
+
languages.add("php");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (exists("Gemfile")) {
|
|
66
|
+
languages.add("ruby");
|
|
67
|
+
frameworks.add("rails");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (manifestNames.has("global.json") || [...manifestNames].some((file) => file.endsWith(".csproj") || file.endsWith(".sln"))) {
|
|
71
|
+
languages.add("csharp");
|
|
72
|
+
}
|
|
73
|
+
|
|
61
74
|
for (const file of manifestNames) {
|
|
62
75
|
const ext = path.extname(file).toLowerCase();
|
|
63
76
|
if ([".c", ".h"].includes(ext)) languages.add("c");
|
|
@@ -66,6 +79,9 @@ function detectWorkspace(workspaceRoot) {
|
|
|
66
79
|
if (ext === ".py") languages.add("python");
|
|
67
80
|
if (ext === ".go") languages.add("go");
|
|
68
81
|
if (ext === ".rs") languages.add("rust");
|
|
82
|
+
if (ext === ".php") languages.add("php");
|
|
83
|
+
if (ext === ".rb") languages.add("ruby");
|
|
84
|
+
if (ext === ".cs") languages.add("csharp");
|
|
69
85
|
if ([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"].includes(ext)) languages.add("javascript");
|
|
70
86
|
}
|
|
71
87
|
|
|
@@ -120,6 +136,24 @@ function buildInstallPlan(profile) {
|
|
|
120
136
|
});
|
|
121
137
|
}
|
|
122
138
|
|
|
139
|
+
if (hasAny(profile.languages, ["ruby"])) {
|
|
140
|
+
addTool(plan, seen, {
|
|
141
|
+
tool: "Brakeman",
|
|
142
|
+
requiredFor: ["Ruby / Rails"],
|
|
143
|
+
install: resolveGemInstall("brakeman"),
|
|
144
|
+
verify: ["brakeman", "--version"]
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (hasAny(profile.languages, ["php"])) {
|
|
149
|
+
addTool(plan, seen, {
|
|
150
|
+
tool: "PHPStan",
|
|
151
|
+
requiredFor: ["PHP"],
|
|
152
|
+
install: resolveComposerGlobalInstall("phpstan/phpstan"),
|
|
153
|
+
verify: ["phpstan", "--version"]
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
123
157
|
if (hasAny(profile.languages, ["java"])) {
|
|
124
158
|
addTool(plan, seen, {
|
|
125
159
|
tool: "SpotBugs",
|
|
@@ -168,6 +202,15 @@ function buildInstallPlan(profile) {
|
|
|
168
202
|
});
|
|
169
203
|
}
|
|
170
204
|
|
|
205
|
+
if (hasAny(profile.languages, ["csharp"])) {
|
|
206
|
+
addTool(plan, seen, {
|
|
207
|
+
tool: ".NET package audit",
|
|
208
|
+
requiredFor: ["C# / .NET"],
|
|
209
|
+
install: resolveDotnetInstall(),
|
|
210
|
+
verify: ["dotnet", "--version"]
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
171
214
|
return plan;
|
|
172
215
|
}
|
|
173
216
|
|
|
@@ -229,6 +272,16 @@ function resolveCargoInstall(crateName) {
|
|
|
229
272
|
return { kind: "manual", note: `Install ${crateName} with Cargo.` };
|
|
230
273
|
}
|
|
231
274
|
|
|
275
|
+
function resolveGemInstall(packageName) {
|
|
276
|
+
if (hasCommand("gem")) return { kind: "command", command: ["gem", "install", packageName] };
|
|
277
|
+
return { kind: "manual", note: `Install ${packageName} with RubyGems.` };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function resolveComposerGlobalInstall(packageName) {
|
|
281
|
+
if (hasCommand("composer")) return { kind: "command", command: ["composer", "global", "require", packageName] };
|
|
282
|
+
return { kind: "manual", note: `Install ${packageName} with Composer globally or add it to the project dev dependencies.` };
|
|
283
|
+
}
|
|
284
|
+
|
|
232
285
|
function resolveRustupComponent(component) {
|
|
233
286
|
if (hasCommand("rustup")) return { kind: "command", command: ["rustup", "component", "add", component] };
|
|
234
287
|
return { kind: "manual", note: `Install Rustup and add the ${component} component.` };
|
|
@@ -247,6 +300,11 @@ function resolveSpotBugsInstall() {
|
|
|
247
300
|
return { kind: "manual", note: "Install SpotBugs manually and ensure `spotbugs` is on PATH." };
|
|
248
301
|
}
|
|
249
302
|
|
|
303
|
+
function resolveDotnetInstall() {
|
|
304
|
+
if (hasCommand("dotnet")) return { kind: "already-installed" };
|
|
305
|
+
return { kind: "manual", note: "Install the .NET SDK so Secure Review can run dotnet package vulnerability checks." };
|
|
306
|
+
}
|
|
307
|
+
|
|
250
308
|
function hasAny(values, expected) {
|
|
251
309
|
return expected.some((value) => values.includes(value));
|
|
252
310
|
}
|