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.
@@ -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
- Commands:
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
- Examples:
323
- secure-review inspect /path/to/repo
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/main/
326
- secure-review bootstrap /path/to/repo --run
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 scan /path/to/repo --severity medium
329
- secure-review scan https://bitbucket.org/acme/payments-service/src/release-1.2/ --severity high
330
- secure-review scan /path/to/repo --format docx --out review.docx
331
- secure-review scan /path/to/repo --dynamic-url http://127.0.0.1:3001 --format html --out report.html
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",
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; full scan is more intrusive."
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",
@@ -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
- ? (finding.targetUrl || "Unknown URL")
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
- ? (finding.targetUrl || "Unknown URL")
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
  }