secure-review-extension 1.0.7 → 1.0.10

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/extension.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const vscode = require("vscode");
2
2
  const { FindingsStore } = require("./src/store");
3
3
  const { FindingsProvider } = require("./src/findings-provider");
4
- const { scanWorkspace, scanCurrentFile } = require("./src/scanners/static-scan");
4
+ const { scanWorkspace, scanFileAtPath } = require("./src/scanners/static-scan");
5
5
  const { runDockerZapScan } = require("./src/scanners/dynamic-scan");
6
6
  const { detectWorkspace, buildInstallPlan, formatInstallPlan } = require("./src/scanners/bootstrap-tools");
7
7
  const { applyDiagnostics } = require("./src/diagnostics");
@@ -11,6 +11,7 @@ const { severityOrder } = require("./src/constants");
11
11
 
12
12
  let reportPanel;
13
13
  let lastReportMetadata;
14
+ const saveScanTimers = new Map();
14
15
 
15
16
  function activate(context) {
16
17
  const store = new FindingsStore();
@@ -82,13 +83,7 @@ function activate(context) {
82
83
  cancellable: false
83
84
  },
84
85
  async () => {
85
- const currentFileFindings = await scanCurrentFile();
86
- const otherStaticFindings = store.findings.filter(
87
- (item) => item.source === "static" && item.filePath !== editor.document.uri.fsPath
88
- );
89
- const dynamicFindings = store.findings.filter((item) => item.source === "dynamic");
90
-
91
- store.setFindings(sortFindings([...otherStaticFindings, ...currentFileFindings, ...dynamicFindings]));
86
+ await rescanFile(editor.document.uri.fsPath);
92
87
  vscode.window.showInformationMessage(`Secure Review updated findings for ${editor.document.fileName}.`);
93
88
  }
94
89
  );
@@ -263,11 +258,97 @@ function activate(context) {
263
258
  vscode.commands.registerCommand("secureReview.ignoreFinding", ignoreFinding)
264
259
  );
265
260
 
261
+ async function rescanFile(filePath) {
262
+ const currentFileFindings = await scanFileAtPath(filePath, {
263
+ workspaceRoot: vscode.workspace.workspaceFolders?.[0]?.uri.fsPath,
264
+ excludeGlobs: configAccessor().get("excludeGlobs", []),
265
+ maxFiles: configAccessor().get("maxFiles", 400)
266
+ });
267
+ const otherStaticFindings = store.findings.filter(
268
+ (item) => item.source === "static" && item.filePath !== filePath
269
+ );
270
+ const dynamicFindings = store.findings.filter((item) => item.source === "dynamic");
271
+ store.setFindings(sortFindings([...otherStaticFindings, ...currentFileFindings, ...dynamicFindings]));
272
+ return currentFileFindings;
273
+ }
274
+
275
+ async function handleRunOnSave(document) {
276
+ if (!document || document.uri.scheme !== "file") {
277
+ return;
278
+ }
279
+
280
+ const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri);
281
+ if (!workspaceFolder) {
282
+ return;
283
+ }
284
+
285
+ const filePath = document.uri.fsPath;
286
+ const existingFindings = store.findings.filter(
287
+ (item) => item.source === "static" && item.filePath === filePath
288
+ );
289
+ const previousIds = new Set(existingFindings.map((item) => item.id));
290
+
291
+ const updatedFindings = await rescanFile(filePath);
292
+ const nextIds = new Set(updatedFindings.map((item) => item.id));
293
+ const newCount = updatedFindings.filter((item) => !previousIds.has(item.id)).length;
294
+ const resolvedCount = existingFindings.filter((item) => !nextIds.has(item.id)).length;
295
+
296
+ if (!updatedFindings.length && !resolvedCount) {
297
+ return;
298
+ }
299
+
300
+ const message = updatedFindings.length
301
+ ? `Secure Review found ${updatedFindings.length} finding${updatedFindings.length === 1 ? "" : "s"} in ${document.fileName}${newCount || resolvedCount ? ` (${newCount} new, ${resolvedCount} resolved)` : ""}.`
302
+ : `Secure Review resolved all findings in ${document.fileName}.`;
303
+
304
+ const action = await vscode.window.showInformationMessage(
305
+ message,
306
+ "Open Findings",
307
+ "Open Report",
308
+ "Export DOCX"
309
+ );
310
+
311
+ if (action === "Open Findings") {
312
+ await vscode.commands.executeCommand("secureReview.findingsView.focus");
313
+ return;
314
+ }
315
+
316
+ if (action === "Open Report") {
317
+ openReport();
318
+ return;
319
+ }
320
+
321
+ if (action === "Export DOCX") {
322
+ await exportFindingsReport();
323
+ }
324
+ }
325
+
266
326
  context.subscriptions.push(
267
- vscode.workspace.onDidSaveTextDocument(async () => {
268
- if (configAccessor().get("runOnSave", false)) {
269
- await runCurrentFileScan();
327
+ vscode.workspace.onDidSaveTextDocument(async (document) => {
328
+ if (!configAccessor().get("runOnSave", false)) {
329
+ return;
270
330
  }
331
+
332
+ const filePath = document?.uri?.fsPath;
333
+ if (!filePath) {
334
+ return;
335
+ }
336
+
337
+ const existingTimer = saveScanTimers.get(filePath);
338
+ if (existingTimer) {
339
+ clearTimeout(existingTimer);
340
+ }
341
+
342
+ const timer = setTimeout(async () => {
343
+ saveScanTimers.delete(filePath);
344
+ try {
345
+ await handleRunOnSave(document);
346
+ } catch (error) {
347
+ vscode.window.showErrorMessage(`Secure Review run-on-save scan failed: ${error.message}`);
348
+ }
349
+ }, 250);
350
+
351
+ saveScanTimers.set(filePath, timer);
271
352
  })
272
353
  );
273
354
 
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.7",
5
+ "version": "1.0.10",
6
6
  "publisher": "Ankit-QI",
7
7
  "icon": "media/shield.png",
8
8
  "license": "MIT",
@@ -135,19 +135,44 @@ async function scanCurrentFile() {
135
135
  return [];
136
136
  }
137
137
 
138
- const content = await readText(editor.document.uri.fsPath);
138
+ return scanFileAtPath(editor.document.uri.fsPath);
139
+ }
140
+
141
+ async function scanFileAtPath(filePath, options = {}) {
142
+ const content = await readText(filePath);
139
143
  if (content === null) {
140
144
  return [];
141
145
  }
142
146
 
143
- const workspaceRoot = vscode?.workspace?.workspaceFolders?.[0]?.uri.fsPath || path.dirname(editor.document.uri.fsPath);
144
- return dedupeFindings(scanContent(editor.document.uri.fsPath, content, workspaceRoot, {
145
- languages: [],
146
- frameworks: [],
147
- isSecureReviewWorkspace: false
147
+ const workspaceRoot = options.workspaceRoot
148
+ || vscode?.workspace?.workspaceFolders?.[0]?.uri.fsPath
149
+ || path.dirname(filePath);
150
+ const workspaceProfile = await buildWorkspaceProfileForRoot(workspaceRoot, {
151
+ excludeGlobs: options.excludeGlobs || [],
152
+ maxFiles: options.maxFiles || 400
153
+ });
154
+
155
+ if (!shouldAnalyzeSingleFile(filePath)) {
156
+ return [];
157
+ }
158
+
159
+ return dedupeFindings(scanContent(filePath, content, workspaceRoot, {
160
+ languages: workspaceProfile.languages,
161
+ frameworks: workspaceProfile.frameworks,
162
+ isSecureReviewWorkspace: workspaceProfile.isSecureReviewWorkspace
148
163
  }));
149
164
  }
150
165
 
166
+ function shouldAnalyzeSingleFile(filePath) {
167
+ const baseName = path.basename(filePath);
168
+ if (LOCKFILE_NAMES.has(baseName) || ENV_LIKE_NAMES.has(baseName)) {
169
+ return true;
170
+ }
171
+
172
+ const ext = path.extname(filePath).toLowerCase();
173
+ return ALLOWED_EXTENSIONS.has(ext);
174
+ }
175
+
151
176
  function scanProfile(workspaceProfile, config) {
152
177
  const findings = [];
153
178
  const notify = typeof config.get === "function"
@@ -654,7 +679,8 @@ async function readText(filePath) {
654
679
  module.exports = {
655
680
  scanWorkspace,
656
681
  scanWorkspaceAtPath,
657
- scanCurrentFile
682
+ scanCurrentFile,
683
+ scanFileAtPath
658
684
  };
659
685
 
660
686
  function analyzeRepository(files, workspaceRoot) {