secure-review-extension 1.0.10 → 1.0.12

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/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.10",
5
+ "version": "1.0.12",
6
6
  "publisher": "Ankit-QI",
7
7
  "icon": "media/shield.png",
8
8
  "license": "MIT",
@@ -272,6 +272,16 @@
272
272
  "default": true,
273
273
  "description": "If cppcheck is installed locally, include C and C++ static analysis findings."
274
274
  },
275
+ "secureReview.enableShellcheck": {
276
+ "type": "boolean",
277
+ "default": true,
278
+ "description": "If ShellCheck is installed locally, include shell script lint and safety findings."
279
+ },
280
+ "secureReview.enableAnsibleLint": {
281
+ "type": "boolean",
282
+ "default": true,
283
+ "description": "If ansible-lint is installed locally, include Ansible playbook findings."
284
+ },
275
285
  "secureReview.enableDotnetVuln": {
276
286
  "type": "boolean",
277
287
  "default": true,
@@ -2,67 +2,95 @@ const fs = require("node:fs");
2
2
  const path = require("node:path");
3
3
  const os = require("node:os");
4
4
  const { execFileSync } = require("node:child_process");
5
+ const { SHELL_EXTENSIONS, looksLikeAnsibleContent, looksLikeAnsiblePath } = require("./scan-targets");
5
6
 
6
7
  function detectWorkspace(workspaceRoot) {
7
8
  const manifestNames = new Set(walkFiles(workspaceRoot, 4).map((file) => path.relative(workspaceRoot, file)));
8
9
  const languages = new Set();
9
10
  const frameworks = new Set();
10
11
 
11
- if (exists("package.json")) {
12
+ const packageJsonFiles = [...manifestNames].filter((file) => path.basename(file) === "package.json");
13
+ if (packageJsonFiles.length) {
12
14
  languages.add("javascript");
13
- const packageJson = readJson(path.join(workspaceRoot, "package.json"));
14
- const dependencies = {
15
- ...(packageJson.dependencies || {}),
16
- ...(packageJson.devDependencies || {}),
17
- ...(packageJson.peerDependencies || {})
18
- };
19
-
20
- if (dependencies.react) frameworks.add("react");
21
- if (dependencies.next) frameworks.add("nextjs");
22
- if (dependencies.vue) frameworks.add("vue");
23
- if (dependencies["@angular/core"]) frameworks.add("angular");
24
- if (dependencies.express) frameworks.add("express");
25
- if (dependencies["@nestjs/core"]) frameworks.add("nestjs");
26
- }
27
-
28
- if (exists("requirements.txt") || exists("pyproject.toml") || exists("Pipfile")) {
15
+ for (const packageJsonPath of packageJsonFiles) {
16
+ const packageJson = readJson(path.join(workspaceRoot, packageJsonPath));
17
+ const dependencies = {
18
+ ...(packageJson.dependencies || {}),
19
+ ...(packageJson.devDependencies || {}),
20
+ ...(packageJson.peerDependencies || {})
21
+ };
22
+
23
+ if (dependencies.react) frameworks.add("react");
24
+ if (dependencies.next) frameworks.add("nextjs");
25
+ if (dependencies.vue) frameworks.add("vue");
26
+ if (dependencies["@angular/core"]) frameworks.add("angular");
27
+ if (dependencies.express) frameworks.add("express");
28
+ if (dependencies["@nestjs/core"]) frameworks.add("nestjs");
29
+ if (dependencies.svelte) frameworks.add("svelte");
30
+ if (dependencies.koa) frameworks.add("koa");
31
+ if (dependencies.fastify) frameworks.add("fastify");
32
+ }
33
+ }
34
+
35
+ const pythonManifestFiles = [...manifestNames].filter((file) => {
36
+ const base = path.basename(file);
37
+ return base === "requirements.txt" || base === "pyproject.toml" || base === "Pipfile";
38
+ });
39
+ if (pythonManifestFiles.length) {
29
40
  languages.add("python");
30
- const pythonText = readTextIfExists("requirements.txt") || readTextIfExists("pyproject.toml") || "";
31
- if (/django/i.test(pythonText)) frameworks.add("django");
32
- if (/flask/i.test(pythonText)) frameworks.add("flask");
33
- if (/fastapi/i.test(pythonText)) frameworks.add("fastapi");
41
+ for (const manifestPath of pythonManifestFiles) {
42
+ const pythonText = readTextIfExists(manifestPath);
43
+ if (/django/i.test(pythonText)) frameworks.add("django");
44
+ if (/flask/i.test(pythonText)) frameworks.add("flask");
45
+ if (/fastapi/i.test(pythonText)) frameworks.add("fastapi");
46
+ }
34
47
  }
35
48
 
36
- if (exists("go.mod")) {
49
+ const goModFiles = [...manifestNames].filter((file) => path.basename(file) === "go.mod");
50
+ if (goModFiles.length) {
37
51
  languages.add("go");
38
- const goMod = readTextIfExists("go.mod");
39
- if (/gin-gonic\/gin/i.test(goMod)) frameworks.add("gin");
40
- if (/labstack\/echo/i.test(goMod)) frameworks.add("echo");
52
+ for (const goModPath of goModFiles) {
53
+ const goMod = readTextIfExists(goModPath);
54
+ if (/gin-gonic\/gin/i.test(goMod)) frameworks.add("gin");
55
+ if (/labstack\/echo/i.test(goMod)) frameworks.add("echo");
56
+ if (/gofiber\/fiber/i.test(goMod)) frameworks.add("fiber");
57
+ }
41
58
  }
42
59
 
43
- if (exists("Cargo.toml")) {
60
+ const cargoTomlFiles = [...manifestNames].filter((file) => path.basename(file) === "Cargo.toml");
61
+ if (cargoTomlFiles.length) {
44
62
  languages.add("rust");
45
- const cargoToml = readTextIfExists("Cargo.toml");
46
- if (/actix-web/i.test(cargoToml)) frameworks.add("actix-web");
47
- if (/\baxum\b/i.test(cargoToml)) frameworks.add("axum");
63
+ for (const cargoTomlPath of cargoTomlFiles) {
64
+ const cargoToml = readTextIfExists(cargoTomlPath);
65
+ if (/actix-web/i.test(cargoToml)) frameworks.add("actix-web");
66
+ if (/\baxum\b/i.test(cargoToml)) frameworks.add("axum");
67
+ if (/rocket/i.test(cargoToml)) frameworks.add("rocket");
68
+ }
48
69
  }
49
70
 
50
- if (exists("pom.xml") || exists("build.gradle") || exists("build.gradle.kts")) {
71
+ const javaManifestFiles = [...manifestNames].filter((file) => {
72
+ const base = path.basename(file);
73
+ return base === "pom.xml" || base === "build.gradle" || base === "build.gradle.kts";
74
+ });
75
+ if (javaManifestFiles.length) {
51
76
  languages.add("java");
52
- const javaText = readTextIfExists("pom.xml") || readTextIfExists("build.gradle") || readTextIfExists("build.gradle.kts") || "";
53
- if (/spring/i.test(javaText)) frameworks.add("spring");
77
+ for (const manifestPath of javaManifestFiles) {
78
+ const javaText = readTextIfExists(manifestPath);
79
+ if (/spring/i.test(javaText)) frameworks.add("spring");
80
+ if (/quarkus/i.test(javaText)) frameworks.add("quarkus");
81
+ }
54
82
  }
55
83
 
56
- if (exists("CMakeLists.txt") || exists("Makefile")) {
84
+ if (manifestNames.has("CMakeLists.txt") || manifestNames.has("Makefile")) {
57
85
  languages.add("c");
58
86
  languages.add("cpp");
59
87
  }
60
88
 
61
- if (exists("composer.json")) {
89
+ if ([...manifestNames].some((file) => path.basename(file) === "composer.json")) {
62
90
  languages.add("php");
63
91
  }
64
92
 
65
- if (exists("Gemfile")) {
93
+ if ([...manifestNames].some((file) => path.basename(file) === "Gemfile")) {
66
94
  languages.add("ruby");
67
95
  frameworks.add("rails");
68
96
  }
@@ -71,6 +99,10 @@ function detectWorkspace(workspaceRoot) {
71
99
  languages.add("csharp");
72
100
  }
73
101
 
102
+ if ([...manifestNames].some((file) => path.basename(file) === "ansible.cfg")) {
103
+ frameworks.add("ansible");
104
+ }
105
+
74
106
  for (const file of manifestNames) {
75
107
  const ext = path.extname(file).toLowerCase();
76
108
  if ([".c", ".h"].includes(ext)) languages.add("c");
@@ -82,7 +114,9 @@ function detectWorkspace(workspaceRoot) {
82
114
  if (ext === ".php") languages.add("php");
83
115
  if (ext === ".rb") languages.add("ruby");
84
116
  if (ext === ".cs") languages.add("csharp");
117
+ if (SHELL_EXTENSIONS.has(ext)) languages.add("shell");
85
118
  if ([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"].includes(ext)) languages.add("javascript");
119
+ if ([".yml", ".yaml"].includes(ext) && looksLikeAnsibleFile(workspaceRoot, file)) frameworks.add("ansible");
86
120
  }
87
121
 
88
122
  return {
@@ -202,6 +236,24 @@ function buildInstallPlan(profile) {
202
236
  });
203
237
  }
204
238
 
239
+ if (hasAny(profile.languages, ["shell"])) {
240
+ addTool(plan, seen, {
241
+ tool: "ShellCheck",
242
+ requiredFor: ["Shell scripts"],
243
+ install: resolveShellcheckInstall(),
244
+ verify: ["shellcheck", "--version"]
245
+ });
246
+ }
247
+
248
+ if (hasAny(profile.frameworks, ["ansible"])) {
249
+ addTool(plan, seen, {
250
+ tool: "ansible-lint",
251
+ requiredFor: ["Ansible / playbooks"],
252
+ install: resolvePythonInstall("ansible-lint"),
253
+ verify: ["ansible-lint", "--version"]
254
+ });
255
+ }
256
+
205
257
  if (hasAny(profile.languages, ["csharp"])) {
206
258
  addTool(plan, seen, {
207
259
  tool: ".NET package audit",
@@ -294,6 +346,13 @@ function resolveCppcheckInstall() {
294
346
  return { kind: "manual", note: "Install cppcheck with your system package manager." };
295
347
  }
296
348
 
349
+ function resolveShellcheckInstall() {
350
+ if (hasCommand("shellcheck")) return { kind: "already-installed" };
351
+ if (os.platform() === "linux") return { kind: "command", command: ["sudo", "apt-get", "install", "-y", "shellcheck"] };
352
+ if (hasCommand("brew")) return { kind: "command", command: ["brew", "install", "shellcheck"] };
353
+ return { kind: "manual", note: "Install shellcheck with your system package manager." };
354
+ }
355
+
297
356
  function resolveSpotBugsInstall() {
298
357
  if (hasCommand("spotbugs")) return { kind: "already-installed" };
299
358
  if (hasCommand("brew")) return { kind: "command", command: ["brew", "install", "spotbugs"] };
@@ -354,6 +413,23 @@ function readJson(filePath) {
354
413
  }
355
414
  }
356
415
 
416
+ function looksLikeAnsibleFile(workspaceRoot, relativePath) {
417
+ if (looksLikeAnsiblePath(relativePath)) {
418
+ return true;
419
+ }
420
+
421
+ const fullPath = path.join(workspaceRoot, relativePath);
422
+ if (!fs.existsSync(fullPath)) {
423
+ return false;
424
+ }
425
+
426
+ try {
427
+ return looksLikeAnsibleContent(fs.readFileSync(fullPath, "utf8"));
428
+ } catch {
429
+ return false;
430
+ }
431
+ }
432
+
357
433
  module.exports = {
358
434
  detectWorkspace,
359
435
  buildInstallPlan,