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.
@@ -0,0 +1,316 @@
1
+ const fs = require("node:fs/promises");
2
+ const syncFs = require("node:fs");
3
+ const path = require("node:path");
4
+ let vscode;
5
+ try {
6
+ vscode = require("vscode");
7
+ } catch {
8
+ vscode = null;
9
+ }
10
+
11
+ const LANGUAGE_BY_EXTENSION = new Map([
12
+ [".js", "javascript"],
13
+ [".jsx", "javascript"],
14
+ [".mjs", "javascript"],
15
+ [".cjs", "javascript"],
16
+ [".ts", "typescript"],
17
+ [".tsx", "typescript"],
18
+ [".py", "python"],
19
+ [".java", "java"],
20
+ [".go", "go"],
21
+ [".rs", "rust"],
22
+ [".c", "c"],
23
+ [".h", "c"],
24
+ [".cpp", "cpp"],
25
+ [".cc", "cpp"],
26
+ [".cxx", "cpp"],
27
+ [".hpp", "cpp"],
28
+ [".hh", "cpp"],
29
+ [".cs", "csharp"],
30
+ [".php", "php"],
31
+ [".rb", "ruby"],
32
+ [".json", "config"],
33
+ [".yaml", "config"],
34
+ [".yml", "config"],
35
+ [".toml", "config"],
36
+ [".xml", "config"],
37
+ [".tf", "config"],
38
+ [".properties", "config"],
39
+ [".sh", "shell"]
40
+ ]);
41
+
42
+ const SPECIAL_FILENAMES = new Set([
43
+ ".env",
44
+ "Dockerfile",
45
+ "docker-compose.yml",
46
+ "docker-compose.yaml",
47
+ "package.json",
48
+ "package-lock.json",
49
+ "pnpm-lock.yaml",
50
+ "yarn.lock",
51
+ "requirements.txt",
52
+ "pyproject.toml",
53
+ "Pipfile",
54
+ "poetry.lock",
55
+ "go.mod",
56
+ "go.sum",
57
+ "Cargo.toml",
58
+ "Cargo.lock",
59
+ "pom.xml",
60
+ "build.gradle",
61
+ "build.gradle.kts",
62
+ "CMakeLists.txt",
63
+ "Makefile"
64
+ ]);
65
+
66
+ async function buildWorkspaceProfile(config) {
67
+ const workspaceRoot = vscode?.workspace?.workspaceFolders?.[0]?.uri.fsPath;
68
+ if (!workspaceRoot) {
69
+ return {
70
+ workspaceRoot: null,
71
+ files: [],
72
+ languages: [],
73
+ frameworks: [],
74
+ manifests: {},
75
+ stats: { totalFiles: 0 }
76
+ };
77
+ }
78
+
79
+ const excludeGlobs = config.get("excludeGlobs", []);
80
+ const maxFiles = config.get("maxFiles", 400);
81
+ return buildWorkspaceProfileForRoot(workspaceRoot, { excludeGlobs, maxFiles });
82
+ }
83
+
84
+ async function buildWorkspaceProfileForRoot(workspaceRoot, options = {}) {
85
+ const excludeGlobs = options.excludeGlobs || [];
86
+ const maxFiles = options.maxFiles || 400;
87
+ const candidatePaths = await walkFiles(workspaceRoot, excludeGlobs, maxFiles);
88
+ const files = [];
89
+
90
+ for (const fsPath of candidatePaths) {
91
+ if (!shouldAnalyzeFile(fsPath)) {
92
+ continue;
93
+ }
94
+
95
+ const content = await readText(fsPath);
96
+ if (content === null) {
97
+ continue;
98
+ }
99
+
100
+ files.push({
101
+ fsPath,
102
+ relativePath: path.relative(workspaceRoot, fsPath).split(path.sep).join("/"),
103
+ baseName: path.basename(fsPath),
104
+ ext: path.extname(fsPath).toLowerCase(),
105
+ content
106
+ });
107
+ }
108
+
109
+ const languages = detectLanguages(files);
110
+ const frameworks = detectFrameworks(files);
111
+ const manifests = detectManifests(files);
112
+
113
+ return {
114
+ workspaceRoot,
115
+ files,
116
+ languages: [...languages].sort(),
117
+ frameworks: [...frameworks].sort(),
118
+ manifests,
119
+ stats: { totalFiles: files.length }
120
+ };
121
+ }
122
+
123
+ async function walkFiles(workspaceRoot, excludeGlobs, maxFiles) {
124
+ const results = [];
125
+ const excluded = buildExclusionMatchers(excludeGlobs);
126
+
127
+ async function visit(currentPath) {
128
+ if (results.length >= maxFiles) {
129
+ return;
130
+ }
131
+
132
+ let entries = [];
133
+ try {
134
+ entries = await fs.readdir(currentPath, { withFileTypes: true });
135
+ } catch {
136
+ return;
137
+ }
138
+
139
+ for (const entry of entries) {
140
+ if (results.length >= maxFiles) {
141
+ return;
142
+ }
143
+
144
+ const fullPath = path.join(currentPath, entry.name);
145
+ const relativePath = path.relative(workspaceRoot, fullPath).split(path.sep).join("/");
146
+ if (shouldExclude(relativePath, excluded)) {
147
+ continue;
148
+ }
149
+
150
+ if (entry.isDirectory()) {
151
+ await visit(fullPath);
152
+ } else if (entry.isFile()) {
153
+ results.push(fullPath);
154
+ }
155
+ }
156
+ }
157
+
158
+ await visit(workspaceRoot);
159
+ return results;
160
+ }
161
+
162
+ function buildExclusionMatchers(excludeGlobs) {
163
+ return (excludeGlobs || [])
164
+ .map((glob) => String(glob || ""))
165
+ .filter(Boolean)
166
+ .map((glob) => glob
167
+ .replaceAll("\\", "/")
168
+ .replace(/^\*\*\//, "")
169
+ .replace(/\/\*\*$/, "/")
170
+ .replace(/\*/g, ""));
171
+ }
172
+
173
+ function shouldExclude(relativePath, matchers) {
174
+ const normalized = relativePath.replaceAll("\\", "/");
175
+ return matchers.some((matcher) => matcher && normalized.includes(matcher));
176
+ }
177
+
178
+ function shouldAnalyzeFile(filePath) {
179
+ const baseName = path.basename(filePath);
180
+ if (SPECIAL_FILENAMES.has(baseName)) {
181
+ return true;
182
+ }
183
+
184
+ const ext = path.extname(filePath).toLowerCase();
185
+ return LANGUAGE_BY_EXTENSION.has(ext);
186
+ }
187
+
188
+ function detectLanguages(files) {
189
+ const languages = new Set();
190
+
191
+ for (const file of files) {
192
+ const fromExt = LANGUAGE_BY_EXTENSION.get(file.ext);
193
+ if (fromExt && fromExt !== "config") {
194
+ languages.add(fromExt);
195
+ }
196
+
197
+ if (file.baseName === "Cargo.toml") {
198
+ languages.add("rust");
199
+ }
200
+ if (file.baseName === "go.mod") {
201
+ languages.add("go");
202
+ }
203
+ if (file.baseName === "requirements.txt" || file.baseName === "pyproject.toml" || file.baseName === "Pipfile") {
204
+ languages.add("python");
205
+ }
206
+ if (file.baseName === "pom.xml" || file.baseName === "build.gradle" || file.baseName === "build.gradle.kts") {
207
+ languages.add("java");
208
+ }
209
+ if (file.baseName === "package.json") {
210
+ languages.add("javascript");
211
+ }
212
+ if (file.baseName === "CMakeLists.txt" || file.baseName === "Makefile") {
213
+ languages.add("c");
214
+ languages.add("cpp");
215
+ }
216
+ }
217
+
218
+ return languages;
219
+ }
220
+
221
+ function detectFrameworks(files) {
222
+ const frameworks = new Set();
223
+ const packageJsonFile = files.find((file) => file.baseName === "package.json");
224
+
225
+ if (packageJsonFile) {
226
+ try {
227
+ const payload = JSON.parse(packageJsonFile.content);
228
+ const dependencies = {
229
+ ...(payload.dependencies || {}),
230
+ ...(payload.devDependencies || {}),
231
+ ...(payload.peerDependencies || {})
232
+ };
233
+
234
+ addIfDependency(frameworks, dependencies, "react", "react");
235
+ addIfDependency(frameworks, dependencies, "next", "nextjs");
236
+ addIfDependency(frameworks, dependencies, "vue", "vue");
237
+ addIfDependency(frameworks, dependencies, "@angular/core", "angular");
238
+ addIfDependency(frameworks, dependencies, "svelte", "svelte");
239
+ addIfDependency(frameworks, dependencies, "express", "express");
240
+ addIfDependency(frameworks, dependencies, "@nestjs/core", "nestjs");
241
+ addIfDependency(frameworks, dependencies, "koa", "koa");
242
+ addIfDependency(frameworks, dependencies, "fastify", "fastify");
243
+ } catch {}
244
+ }
245
+
246
+ for (const file of files) {
247
+ const lower = file.content.toLowerCase();
248
+ if (file.baseName === "requirements.txt" || file.baseName === "pyproject.toml") {
249
+ addIfText(frameworks, lower, "django", "django");
250
+ addIfText(frameworks, lower, "flask", "flask");
251
+ addIfText(frameworks, lower, "fastapi", "fastapi");
252
+ }
253
+ if (file.baseName === "pom.xml" || file.baseName === "build.gradle" || file.baseName === "build.gradle.kts") {
254
+ addIfText(frameworks, lower, "spring", "spring");
255
+ addIfText(frameworks, lower, "quarkus", "quarkus");
256
+ }
257
+ if (file.baseName === "go.mod") {
258
+ addIfText(frameworks, lower, "github.com/gin-gonic/gin", "gin");
259
+ addIfText(frameworks, lower, "github.com/labstack/echo", "echo");
260
+ addIfText(frameworks, lower, "github.com/gofiber/fiber", "fiber");
261
+ }
262
+ if (file.baseName === "Cargo.toml") {
263
+ addIfText(frameworks, lower, "actix-web", "actix-web");
264
+ addIfText(frameworks, lower, "rocket", "rocket");
265
+ addIfText(frameworks, lower, "axum", "axum");
266
+ }
267
+ }
268
+
269
+ return frameworks;
270
+ }
271
+
272
+ function detectManifests(files) {
273
+ const manifestNames = [
274
+ "package.json",
275
+ "requirements.txt",
276
+ "pyproject.toml",
277
+ "Pipfile",
278
+ "go.mod",
279
+ "Cargo.toml",
280
+ "pom.xml",
281
+ "build.gradle",
282
+ "build.gradle.kts",
283
+ "CMakeLists.txt",
284
+ "Dockerfile"
285
+ ];
286
+
287
+ return manifestNames.reduce((accumulator, name) => {
288
+ accumulator[name] = files.some((file) => file.baseName === name);
289
+ return accumulator;
290
+ }, {});
291
+ }
292
+
293
+ function addIfDependency(frameworks, dependencies, dependencyName, frameworkName) {
294
+ if (dependencies[dependencyName]) {
295
+ frameworks.add(frameworkName);
296
+ }
297
+ }
298
+
299
+ function addIfText(frameworks, haystack, needle, frameworkName) {
300
+ if (haystack.includes(needle)) {
301
+ frameworks.add(frameworkName);
302
+ }
303
+ }
304
+
305
+ async function readText(filePath) {
306
+ try {
307
+ return await fs.readFile(filePath, "utf8");
308
+ } catch {
309
+ return null;
310
+ }
311
+ }
312
+
313
+ module.exports = {
314
+ buildWorkspaceProfile,
315
+ buildWorkspaceProfileForRoot
316
+ };
package/src/store.js ADDED
@@ -0,0 +1,49 @@
1
+ const vscode = require("vscode");
2
+
3
+ class FindingsStore {
4
+ constructor() {
5
+ this.findings = [];
6
+ this.ignored = new Set();
7
+ this.lastRun = null;
8
+ this._emitter = new vscode.EventEmitter();
9
+ }
10
+
11
+ get onDidChange() {
12
+ return this._emitter.event;
13
+ }
14
+
15
+ getVisibleFindings(minSeverity, severityOrder) {
16
+ return this.findings.filter((finding) => {
17
+ if (this.ignored.has(finding.id)) {
18
+ return false;
19
+ }
20
+
21
+ return severityOrder[finding.severity] >= severityOrder[minSeverity];
22
+ });
23
+ }
24
+
25
+ setFindings(findings) {
26
+ this.findings = findings;
27
+ this.lastRun = new Date();
28
+ this._emitter.fire();
29
+ }
30
+
31
+ clear() {
32
+ this.findings = [];
33
+ this.lastRun = null;
34
+ this._emitter.fire();
35
+ }
36
+
37
+ ignore(id) {
38
+ this.ignored.add(id);
39
+ this._emitter.fire();
40
+ }
41
+
42
+ getById(id) {
43
+ return this.findings.find((finding) => finding.id === id);
44
+ }
45
+ }
46
+
47
+ module.exports = {
48
+ FindingsStore
49
+ };
package/src/utils.js ADDED
@@ -0,0 +1,24 @@
1
+ const crypto = require("node:crypto");
2
+ const path = require("node:path");
3
+
4
+ function hashFinding(parts) {
5
+ return crypto.createHash("sha1").update(parts.join("|")).digest("hex").slice(0, 12);
6
+ }
7
+
8
+ function toPosixPath(filePath) {
9
+ return filePath.split(path.sep).join("/");
10
+ }
11
+
12
+ function shorten(text, length = 140) {
13
+ if (!text) {
14
+ return "";
15
+ }
16
+
17
+ return text.length > length ? `${text.slice(0, length - 1)}...` : text;
18
+ }
19
+
20
+ module.exports = {
21
+ hashFinding,
22
+ toPosixPath,
23
+ shorten
24
+ };