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.
- package/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +304 -0
- package/bin/secure-review.js +269 -0
- package/extension.js +368 -0
- package/media/shield.png +0 -0
- package/media/shield.svg +6 -0
- package/package.json +323 -0
- package/scripts/bootstrap-review-tools.js +54 -0
- package/src/code-actions.js +47 -0
- package/src/constants.js +20 -0
- package/src/diagnostics.js +41 -0
- package/src/findings-provider.js +78 -0
- package/src/report.js +837 -0
- package/src/scanners/bootstrap-tools.js +303 -0
- package/src/scanners/dynamic-scan.js +224 -0
- package/src/scanners/static-rules.js +497 -0
- package/src/scanners/static-scan.js +341 -0
- package/src/scanners/tool-integrations.js +666 -0
- package/src/scanners/workspace-profile.js +316 -0
- package/src/store.js +49 -0
- package/src/utils.js +24 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
- Initial production-ready v1 packaging of the Secure Review extension
|
|
6
|
+
- Deep static secure review workflows inside VS Code
|
|
7
|
+
- Findings tree, diagnostics, quick fixes, and report webview
|
|
8
|
+
- DOCX report export with structured security report formatting
|
|
9
|
+
- Marketplace-ready metadata, packaging scripts, and release docs
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Your Organization
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# Secure Review VS Code Extension
|
|
2
|
+
|
|
3
|
+
Secure Review is a VS Code extension for internal engineering teams that runs deep static secure code reviews and Docker-based dynamic web security scans directly from VS Code.
|
|
4
|
+
|
|
5
|
+
It can also run as a local terminal agent through the `secure-review` CLI.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Static workspace scan with built-in review packs
|
|
10
|
+
- Current-file scan command
|
|
11
|
+
- Docker-based OWASP ZAP dynamic scan for local/test applications
|
|
12
|
+
- Optional local external tool enrichment when available
|
|
13
|
+
- Findings tree view in the VS Code activity bar
|
|
14
|
+
- Inline diagnostics in the editor
|
|
15
|
+
- Report webview with summary, findings, and evidence
|
|
16
|
+
- DOCX security report export with project metadata prompts
|
|
17
|
+
- Ignore findings for the current workspace session
|
|
18
|
+
- Optional scan-on-save behavior
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
### Visual Studio Marketplace
|
|
23
|
+
|
|
24
|
+
1. Publish the extension with your real publisher identity.
|
|
25
|
+
2. Search for `Secure Review` in VS Code Extensions.
|
|
26
|
+
3. Click `Install`.
|
|
27
|
+
|
|
28
|
+
### VSIX
|
|
29
|
+
|
|
30
|
+
1. Package the extension:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm run package
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
2. In VS Code, run `Extensions: Install from VSIX...`
|
|
37
|
+
3. Select the generated `.vsix` file.
|
|
38
|
+
|
|
39
|
+
### CLI Agent
|
|
40
|
+
|
|
41
|
+
To use Secure Review as a terminal agent from this repo:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm link
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Then the `secure-review` command will be available on your machine:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
secure-review help
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
To prepare the CLI package for distribution:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm run verify
|
|
57
|
+
npm run pack:cli:dry-run
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
To build the tarball you would share or publish:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm run pack:cli
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
To publish to npm after setting the final package name and metadata:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm run publish:npm
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Commands
|
|
73
|
+
|
|
74
|
+
- `Secure Review: Scan Workspace`
|
|
75
|
+
- `Secure Review: Scan Current File`
|
|
76
|
+
- `Secure Review: Dynamic Scan Local App`
|
|
77
|
+
- `Secure Review: Open Review Report`
|
|
78
|
+
- `Secure Review: Export Findings Report`
|
|
79
|
+
- `Secure Review: Clear Findings`
|
|
80
|
+
- `Secure Review: Ignore Finding`
|
|
81
|
+
|
|
82
|
+
## CLI Usage
|
|
83
|
+
|
|
84
|
+
The terminal agent supports:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
secure-review inspect /path/to/repo
|
|
88
|
+
secure-review bootstrap /path/to/repo
|
|
89
|
+
secure-review bootstrap /path/to/repo --run
|
|
90
|
+
secure-review scan /path/to/repo --severity medium
|
|
91
|
+
secure-review scan /path/to/repo --format html --out report.html
|
|
92
|
+
secure-review scan /path/to/repo --format docx --out report.docx
|
|
93
|
+
secure-review scan /path/to/repo --dynamic-url http://127.0.0.1:3001 --dynamic-mode baseline
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Command summary:
|
|
97
|
+
- `inspect`
|
|
98
|
+
Shows detected languages, frameworks, and recommended scanner tools for a workspace.
|
|
99
|
+
- `bootstrap`
|
|
100
|
+
Prints the install plan for the workspace, or runs the supported install commands with `--run`.
|
|
101
|
+
- `scan`
|
|
102
|
+
Runs static review, optionally adds a Docker-based ZAP dynamic scan, and can export text, JSON, HTML, Markdown, or DOCX output.
|
|
103
|
+
|
|
104
|
+
## npm Publishing Notes
|
|
105
|
+
|
|
106
|
+
Before publishing the CLI package for other users:
|
|
107
|
+
- replace the placeholder npm package name in [package.json](/home/ankit.singh/CODE_REVIEW_TOOL/package.json) if needed
|
|
108
|
+
- replace placeholder homepage, repository, and bugs URLs
|
|
109
|
+
- make sure the package name is actually available on npm
|
|
110
|
+
- run `npm run verify`
|
|
111
|
+
- run `npm run pack:cli:dry-run`
|
|
112
|
+
- run `npm run pack:cli`
|
|
113
|
+
- test the generated tarball locally with `npm install -g ./<tarball>.tgz`
|
|
114
|
+
- publish with `npm run publish:npm`
|
|
115
|
+
|
|
116
|
+
## What The Extension Checks
|
|
117
|
+
|
|
118
|
+
### Static checks
|
|
119
|
+
|
|
120
|
+
- hard-coded secrets and credentials
|
|
121
|
+
- weak crypto algorithms like `md5` and `sha1`
|
|
122
|
+
- SQL injection style string concatenation
|
|
123
|
+
- command execution sinks
|
|
124
|
+
- dangerous HTML injection patterns
|
|
125
|
+
- debug and verbose logging smells
|
|
126
|
+
- unsafe eval-like execution
|
|
127
|
+
|
|
128
|
+
### Review domains
|
|
129
|
+
|
|
130
|
+
- security and vulnerability checks
|
|
131
|
+
- dependency and supply-chain checks
|
|
132
|
+
- code quality and maintainability
|
|
133
|
+
- reliability and error handling
|
|
134
|
+
- performance smells
|
|
135
|
+
- outdated practices
|
|
136
|
+
- testing gaps
|
|
137
|
+
|
|
138
|
+
### Dynamic checks
|
|
139
|
+
|
|
140
|
+
- ZAP baseline scan for passive web checks
|
|
141
|
+
- ZAP full scan for active testing against local/test apps
|
|
142
|
+
- missing headers
|
|
143
|
+
- cookie issues
|
|
144
|
+
- information disclosure
|
|
145
|
+
- XSS and injection candidates surfaced by ZAP
|
|
146
|
+
|
|
147
|
+
Dynamic scans are intended only for developer-controlled local or test URLs. Full scan mode is intrusive and requires explicit confirmation.
|
|
148
|
+
|
|
149
|
+
## Report Export
|
|
150
|
+
|
|
151
|
+
- Exported reports are generated as `.docx`
|
|
152
|
+
- The export flow prompts for:
|
|
153
|
+
- project name
|
|
154
|
+
- report date
|
|
155
|
+
- scan type
|
|
156
|
+
- optional notes
|
|
157
|
+
- Reports include:
|
|
158
|
+
- executive summary
|
|
159
|
+
- severity summary
|
|
160
|
+
- findings by category
|
|
161
|
+
- findings by review domain
|
|
162
|
+
- overview grouped by severity
|
|
163
|
+
- detailed findings with evidence, why-it-matters, remediation, and suggestions
|
|
164
|
+
- recommendations summary
|
|
165
|
+
- methodology, limitations, and scan configuration
|
|
166
|
+
|
|
167
|
+
## Test In This Workspace
|
|
168
|
+
|
|
169
|
+
1. Open this folder in VS Code.
|
|
170
|
+
2. Press `Fn + F5` or use `Run and Debug` to launch the Extension Development Host.
|
|
171
|
+
3. In the new VS Code window, open any code project.
|
|
172
|
+
4. Run `Secure Review: Scan Workspace` from the Command Palette.
|
|
173
|
+
5. Open the `Secure Review` activity bar icon to inspect findings.
|
|
174
|
+
6. Run `Secure Review: Open Review Report` to see the webview report.
|
|
175
|
+
7. Run `Secure Review: Export Findings Report` to create the Word report.
|
|
176
|
+
|
|
177
|
+
This repo already includes a vulnerable sample in [sample-workspace/demo-app.js](/home/ankit.singh/CODE_REVIEW_TOOL/sample-workspace/demo-app.js), so scanning this workspace should immediately produce findings.
|
|
178
|
+
|
|
179
|
+
## Test The CLI Agent
|
|
180
|
+
|
|
181
|
+
1. From this repo, run:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
npm link
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
2. Inspect a target workspace:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
secure-review inspect /home/ankit.singh/netbox-history-platform
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
3. See which scanners should be installed:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
secure-review bootstrap /home/ankit.singh/netbox-history-platform
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
4. Run the code review:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
secure-review scan /home/ankit.singh/netbox-history-platform --severity low
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
5. Export a report if needed:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
secure-review scan /home/ankit.singh/netbox-history-platform --format docx --out /home/ankit.singh/netbox-history-platform/review.docx
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Dynamic Scan Setup
|
|
212
|
+
|
|
213
|
+
1. Install Docker and ensure it is running.
|
|
214
|
+
2. Start a local test app or the included sample:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
PORT=3001 node sample-workspace/dev-server.js
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
3. Set `Secure Review > Dynamic Base Url` to match the port you used, for example `http://127.0.0.1:3001`.
|
|
221
|
+
4. Run `Secure Review: Dynamic Scan Local App`.
|
|
222
|
+
5. Use `baseline` mode first; switch to `full` mode only for applications you control.
|
|
223
|
+
|
|
224
|
+
## Bootstrap Scanner Tools
|
|
225
|
+
|
|
226
|
+
If you want the workspace to prepare the matching external static-analysis tools before scanning, run:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
npm run bootstrap:tools
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
That command inspects the current workspace and prints the install plan for tools such as:
|
|
233
|
+
- `semgrep`
|
|
234
|
+
- `eslint`
|
|
235
|
+
- `bandit`
|
|
236
|
+
- `pip-audit`
|
|
237
|
+
- `spotbugs`
|
|
238
|
+
- `gosec`
|
|
239
|
+
- `govulncheck`
|
|
240
|
+
- `cargo-audit`
|
|
241
|
+
- `clippy`
|
|
242
|
+
- `cppcheck`
|
|
243
|
+
|
|
244
|
+
To actually execute the supported install commands:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
npm run bootstrap:tools:run
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The bootstrap is best-effort:
|
|
251
|
+
- it detects the languages and frameworks present in the workspace
|
|
252
|
+
- it chooses the matching scanner tools
|
|
253
|
+
- it only auto-runs commands when there is a reasonable local install path
|
|
254
|
+
- some tools, such as `SpotBugs`, may still need a manual install depending on your OS and package manager setup
|
|
255
|
+
|
|
256
|
+
## Packaging And Release
|
|
257
|
+
|
|
258
|
+
### Precheck
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
npm run verify
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Package
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
npm run package
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Marketplace publish
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
npm run publish:marketplace
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Before publishing:
|
|
277
|
+
- replace `your-publisher-id` in [package.json](/home/ankit.singh/CODE_REVIEW_TOOL/package.json)
|
|
278
|
+
- replace placeholder repository, homepage, and bugs URLs
|
|
279
|
+
- sign in to `vsce` with the publisher PAT for your Azure DevOps publisher
|
|
280
|
+
|
|
281
|
+
## Security And Privacy Notes
|
|
282
|
+
|
|
283
|
+
- This extension is self-contained and does not require external scanner binaries.
|
|
284
|
+
- If local tools like `semgrep`, `npm audit`, or `pip-audit` are available, the extension can enrich static review results with them.
|
|
285
|
+
- Static scans operate only on files in the open workspace.
|
|
286
|
+
- Dynamic scans use OWASP ZAP via Docker and should only target developer-controlled local or test applications.
|
|
287
|
+
- The extension does not send findings to remote services.
|
|
288
|
+
- The rule engine is intentionally local and portable so it can be tested immediately in this workspace.
|
|
289
|
+
|
|
290
|
+
## Troubleshooting
|
|
291
|
+
|
|
292
|
+
- `F5 changes brightness`
|
|
293
|
+
- Use `Fn + F5` or `Run and Debug`.
|
|
294
|
+
- `No findings appear`
|
|
295
|
+
- Lower `Secure Review > Minimum Severity` and confirm excluded globs are not hiding your files.
|
|
296
|
+
- Check whether built-in rules or external-tool integrations are disabled in settings.
|
|
297
|
+
- `Dynamic scan failed`
|
|
298
|
+
- Confirm Docker is installed and running.
|
|
299
|
+
- Confirm the target host is listed in `Secure Review > Dynamic Allow Hosts`.
|
|
300
|
+
- For localhost targets, confirm the app is reachable from Docker.
|
|
301
|
+
- `DOCX export opens incorrectly`
|
|
302
|
+
- Re-export after a fresh scan and test in Word or LibreOffice.
|
|
303
|
+
- `Marketplace package fails`
|
|
304
|
+
- Confirm your publisher ID, PAT, and repository metadata are set correctly.
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
const { spawnSync } = require("node:child_process");
|
|
5
|
+
const { detectWorkspace, buildInstallPlan, formatInstallPlan } = require("../src/scanners/bootstrap-tools");
|
|
6
|
+
const { scanWorkspaceAtPath } = require("../src/scanners/static-scan");
|
|
7
|
+
const { runDockerZapScanWithOptions } = require("../src/scanners/dynamic-scan");
|
|
8
|
+
const { buildReportModel, renderReportHtml, renderMarkdown, exportDocx } = require("../src/report");
|
|
9
|
+
|
|
10
|
+
const severityRank = {
|
|
11
|
+
low: 0,
|
|
12
|
+
medium: 1,
|
|
13
|
+
high: 2,
|
|
14
|
+
critical: 3
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
main().catch((error) => {
|
|
18
|
+
console.error(`secure-review failed: ${error.message}`);
|
|
19
|
+
process.exitCode = 1;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
async function main() {
|
|
23
|
+
const { command, workspaceArg, flags } = parseArguments(process.argv.slice(2));
|
|
24
|
+
const workspaceRoot = path.resolve(workspaceArg || process.cwd());
|
|
25
|
+
|
|
26
|
+
switch (command) {
|
|
27
|
+
case "inspect":
|
|
28
|
+
inspectWorkspace(workspaceRoot);
|
|
29
|
+
return;
|
|
30
|
+
case "bootstrap":
|
|
31
|
+
bootstrapWorkspace(workspaceRoot, flags.run);
|
|
32
|
+
return;
|
|
33
|
+
case "scan":
|
|
34
|
+
await scanWorkspace(workspaceRoot, flags);
|
|
35
|
+
return;
|
|
36
|
+
case "help":
|
|
37
|
+
default:
|
|
38
|
+
printHelp();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseArguments(argv) {
|
|
43
|
+
const command = argv[0] || "help";
|
|
44
|
+
const flags = {};
|
|
45
|
+
let workspaceArg;
|
|
46
|
+
|
|
47
|
+
for (let index = 1; index < argv.length; index += 1) {
|
|
48
|
+
const token = argv[index];
|
|
49
|
+
if (!token.startsWith("--")) {
|
|
50
|
+
workspaceArg = workspaceArg || token;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const key = token.slice(2);
|
|
55
|
+
const next = argv[index + 1];
|
|
56
|
+
if (!next || next.startsWith("--")) {
|
|
57
|
+
flags[key] = true;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
flags[key] = next;
|
|
62
|
+
index += 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { command, workspaceArg, flags };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function inspectWorkspace(workspaceRoot) {
|
|
69
|
+
const profile = detectWorkspace(workspaceRoot);
|
|
70
|
+
const plan = buildInstallPlan(profile);
|
|
71
|
+
console.log(formatInstallPlan(profile, plan));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function bootstrapWorkspace(workspaceRoot, shouldRun) {
|
|
75
|
+
const profile = detectWorkspace(workspaceRoot);
|
|
76
|
+
const plan = buildInstallPlan(profile);
|
|
77
|
+
console.log(formatInstallPlan(profile, plan));
|
|
78
|
+
|
|
79
|
+
if (!shouldRun) {
|
|
80
|
+
console.log("");
|
|
81
|
+
console.log("Dry run only. Re-run with `--run` to execute supported install commands.");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log("");
|
|
86
|
+
for (const item of plan) {
|
|
87
|
+
if (item.install.kind === "already-installed") {
|
|
88
|
+
console.log(`Skipping ${item.tool}: already available.`);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (item.install.kind !== "command") {
|
|
92
|
+
console.log(`Skipping ${item.tool}: ${item.install.note}`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log(`Running ${item.install.command.join(" ")}`);
|
|
97
|
+
const result = spawnSync(item.install.command[0], item.install.command.slice(1), {
|
|
98
|
+
cwd: workspaceRoot,
|
|
99
|
+
stdio: "inherit"
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (result.status !== 0) {
|
|
103
|
+
console.log(`Install failed for ${item.tool}.`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function scanWorkspace(workspaceRoot, flags) {
|
|
109
|
+
const findings = await scanWorkspaceAtPath(workspaceRoot, buildStaticConfig(flags));
|
|
110
|
+
|
|
111
|
+
if (flags["dynamic-url"]) {
|
|
112
|
+
const dynamicFindings = await runDockerZapScanWithOptions({
|
|
113
|
+
workspaceRoot,
|
|
114
|
+
target: flags["dynamic-url"],
|
|
115
|
+
scanMode: flags["dynamic-mode"] || "baseline",
|
|
116
|
+
allowHosts: splitCsv(flags["dynamic-allow-hosts"]) || ["127.0.0.1", "localhost"],
|
|
117
|
+
spiderMinutes: Number(flags["dynamic-spider-minutes"] || 1),
|
|
118
|
+
useAjaxSpider: Boolean(flags["dynamic-ajax"]),
|
|
119
|
+
dockerImage: flags["zap-image"] || "ghcr.io/zaproxy/zaproxy:stable"
|
|
120
|
+
});
|
|
121
|
+
findings.push(...dynamicFindings);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const filtered = filterBySeverity(findings, flags.severity || "low");
|
|
125
|
+
|
|
126
|
+
if (flags.format === "json" && !flags.out) {
|
|
127
|
+
console.log(JSON.stringify(filtered, null, 2));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (flags.format && flags.format !== "text") {
|
|
132
|
+
await writeReport(filtered, workspaceRoot, flags);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
printScanSummary(filtered, workspaceRoot);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function buildStaticConfig(flags) {
|
|
140
|
+
return {
|
|
141
|
+
excludeGlobs: [],
|
|
142
|
+
maxFiles: Number(flags["max-files"] || 400),
|
|
143
|
+
enableBuiltInRules: !isFalse(flags["built-in-rules"]),
|
|
144
|
+
enableSemgrep: !isFalse(flags.semgrep),
|
|
145
|
+
enableEslint: !isFalse(flags.eslint),
|
|
146
|
+
enableNpmAudit: !isFalse(flags["npm-audit"]),
|
|
147
|
+
enableBandit: !isFalse(flags.bandit),
|
|
148
|
+
enablePipAudit: !isFalse(flags["pip-audit"]),
|
|
149
|
+
enableSpotBugs: !isFalse(flags.spotbugs),
|
|
150
|
+
enableGosec: !isFalse(flags.gosec),
|
|
151
|
+
enableGovulncheck: !isFalse(flags.govulncheck),
|
|
152
|
+
enableCargoAudit: !isFalse(flags["cargo-audit"]),
|
|
153
|
+
enableClippy: !isFalse(flags.clippy),
|
|
154
|
+
enableCppcheck: !isFalse(flags.cppcheck)
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function filterBySeverity(findings, minimumSeverity) {
|
|
159
|
+
const threshold = severityRank[minimumSeverity] ?? severityRank.low;
|
|
160
|
+
return findings.filter((finding) => (severityRank[finding.severity] ?? 0) >= threshold);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function writeReport(findings, workspaceRoot, flags) {
|
|
164
|
+
const format = flags.format || "html";
|
|
165
|
+
const projectName = flags.project || path.basename(workspaceRoot);
|
|
166
|
+
const scanType = flags["dynamic-url"] ? "Static + Dynamic Analysis" : "Static Analysis";
|
|
167
|
+
const reportModel = buildReportModel(findings, {
|
|
168
|
+
projectName,
|
|
169
|
+
reportDate: flags.date || new Date(),
|
|
170
|
+
scanType,
|
|
171
|
+
notes: flags.notes || "",
|
|
172
|
+
scanConfiguration: buildScanConfiguration(flags)
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const extension = format === "docx" ? "docx" : format === "md" ? "md" : format === "json" ? "json" : "html";
|
|
176
|
+
const outPath = path.resolve(flags.out || path.join(workspaceRoot, `${slugify(projectName)}-secure-review-report.${extension}`));
|
|
177
|
+
|
|
178
|
+
if (format === "docx") {
|
|
179
|
+
await exportDocx(reportModel, outPath);
|
|
180
|
+
} else if (format === "md") {
|
|
181
|
+
await require("node:fs/promises").writeFile(outPath, renderMarkdown(reportModel), "utf8");
|
|
182
|
+
} else if (format === "json") {
|
|
183
|
+
await require("node:fs/promises").writeFile(outPath, JSON.stringify({ findings, report: reportModel }, null, 2), "utf8");
|
|
184
|
+
} else {
|
|
185
|
+
await require("node:fs/promises").writeFile(outPath, renderReportHtml(reportModel), "utf8");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
console.log(`Report written to ${outPath}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function buildScanConfiguration(flags) {
|
|
192
|
+
return [
|
|
193
|
+
`Minimum Severity: ${flags.severity || "low"}`,
|
|
194
|
+
`Built-In Rules: ${isFalse(flags["built-in-rules"]) ? "Disabled" : "Enabled"}`,
|
|
195
|
+
`Semgrep: ${isFalse(flags.semgrep) ? "Disabled" : "Enabled"}`,
|
|
196
|
+
`ESLint: ${isFalse(flags.eslint) ? "Disabled" : "Enabled"}`,
|
|
197
|
+
`Bandit: ${isFalse(flags.bandit) ? "Disabled" : "Enabled"}`,
|
|
198
|
+
`npm audit: ${isFalse(flags["npm-audit"]) ? "Disabled" : "Enabled"}`,
|
|
199
|
+
`pip-audit: ${isFalse(flags["pip-audit"]) ? "Disabled" : "Enabled"}`,
|
|
200
|
+
`SpotBugs: ${isFalse(flags.spotbugs) ? "Disabled" : "Enabled"}`,
|
|
201
|
+
`gosec: ${isFalse(flags.gosec) ? "Disabled" : "Enabled"}`,
|
|
202
|
+
`govulncheck: ${isFalse(flags.govulncheck) ? "Disabled" : "Enabled"}`,
|
|
203
|
+
`cargo-audit: ${isFalse(flags["cargo-audit"]) ? "Disabled" : "Enabled"}`,
|
|
204
|
+
`Clippy: ${isFalse(flags.clippy) ? "Disabled" : "Enabled"}`,
|
|
205
|
+
`cppcheck: ${isFalse(flags.cppcheck) ? "Disabled" : "Enabled"}`,
|
|
206
|
+
flags["dynamic-url"] ? `Dynamic URL: ${flags["dynamic-url"]}` : null,
|
|
207
|
+
flags["dynamic-mode"] ? `Dynamic Mode: ${flags["dynamic-mode"]}` : null
|
|
208
|
+
].filter(Boolean).join("; ");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function printScanSummary(findings, workspaceRoot) {
|
|
212
|
+
console.log(`Secure Review scan completed for ${workspaceRoot}`);
|
|
213
|
+
console.log(`Total findings: ${findings.length}`);
|
|
214
|
+
|
|
215
|
+
const counts = ["critical", "high", "medium", "low"]
|
|
216
|
+
.map((severity) => `${severity}: ${findings.filter((item) => item.severity === severity).length}`)
|
|
217
|
+
.join(" | ");
|
|
218
|
+
console.log(`Severity summary: ${counts}`);
|
|
219
|
+
console.log("");
|
|
220
|
+
|
|
221
|
+
for (const finding of findings.slice(0, 25)) {
|
|
222
|
+
const location = finding.relativePath
|
|
223
|
+
? `${finding.relativePath}:${finding.line || 1}`
|
|
224
|
+
: finding.targetUrl || "unknown-location";
|
|
225
|
+
console.log(`[${String(finding.severity || "low").toUpperCase()}] ${finding.title} :: ${location}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (findings.length > 25) {
|
|
229
|
+
console.log("");
|
|
230
|
+
console.log(`Showing first 25 findings. Re-run with --format json or --format html --out <path> for full output.`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function splitCsv(value) {
|
|
235
|
+
if (!value) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
return String(value).split(",").map((item) => item.trim()).filter(Boolean);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function isFalse(value) {
|
|
242
|
+
return value === "false" || value === false;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function slugify(value) {
|
|
246
|
+
return String(value || "secure-review")
|
|
247
|
+
.toLowerCase()
|
|
248
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
249
|
+
.replace(/^-+|-+$/g, "")
|
|
250
|
+
|| "secure-review";
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function printHelp() {
|
|
254
|
+
console.log(`secure-review
|
|
255
|
+
|
|
256
|
+
Commands:
|
|
257
|
+
secure-review inspect [workspace]
|
|
258
|
+
secure-review bootstrap [workspace] [--run]
|
|
259
|
+
secure-review scan [workspace] [--severity low|medium|high|critical] [--format text|json|html|md|docx] [--out <path>]
|
|
260
|
+
secure-review scan [workspace] --dynamic-url <url> [--dynamic-mode baseline|full]
|
|
261
|
+
|
|
262
|
+
Examples:
|
|
263
|
+
secure-review inspect /path/to/repo
|
|
264
|
+
secure-review bootstrap /path/to/repo --run
|
|
265
|
+
secure-review scan /path/to/repo --severity medium
|
|
266
|
+
secure-review scan /path/to/repo --format docx --out review.docx
|
|
267
|
+
secure-review scan /path/to/repo --dynamic-url http://127.0.0.1:3001 --format html --out report.html
|
|
268
|
+
`);
|
|
269
|
+
}
|