extension-guard 0.1.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/LICENSE +21 -0
- package/dist/index.js +272 -0
- package/dist/index.js.map +1 -0
- package/package.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aspect Guard
|
|
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/dist/index.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import ora from "ora";
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import {
|
|
9
|
+
ExtensionGuardScanner,
|
|
10
|
+
VERSION,
|
|
11
|
+
JsonReporter,
|
|
12
|
+
SarifReporter,
|
|
13
|
+
MarkdownReporter,
|
|
14
|
+
loadPolicyConfig,
|
|
15
|
+
PolicyEngine
|
|
16
|
+
} from "@aspect-guard/core";
|
|
17
|
+
function createCli() {
|
|
18
|
+
const program = new Command();
|
|
19
|
+
program.name("extension-guard").description("Scan VSCode extensions for security issues").version(VERSION);
|
|
20
|
+
program.command("scan").description("Scan installed VSCode extensions").option("-p, --path <paths...>", "Custom extension paths to scan").option("-f, --format <format>", "Output format (table|json|sarif|markdown)", "table").option("-o, --output <file>", "Output file path (default: stdout)").option("-s, --severity <level>", "Minimum severity to show", "info").option("-q, --quiet", "Only show results, no progress").option("--include-safe", "Include safe extensions in output").action(async (options) => {
|
|
21
|
+
const isJsonOutput = options.format === "json" || options.format === "sarif";
|
|
22
|
+
const spinner = options.quiet || isJsonOutput ? null : ora("Scanning extensions...").start();
|
|
23
|
+
try {
|
|
24
|
+
const scanner = new ExtensionGuardScanner({
|
|
25
|
+
autoDetect: !options.path,
|
|
26
|
+
idePaths: options.path ?? [],
|
|
27
|
+
severity: options.severity
|
|
28
|
+
});
|
|
29
|
+
const report = await scanner.scan();
|
|
30
|
+
if (spinner) {
|
|
31
|
+
spinner.succeed("Scan complete");
|
|
32
|
+
}
|
|
33
|
+
const output = generateOutput(report, options.format, {
|
|
34
|
+
includeSafe: options.includeSafe
|
|
35
|
+
});
|
|
36
|
+
if (options.output) {
|
|
37
|
+
fs.writeFileSync(options.output, output);
|
|
38
|
+
if (!options.quiet) {
|
|
39
|
+
console.log(chalk.green(`Report saved to ${options.output}`));
|
|
40
|
+
}
|
|
41
|
+
} else if (options.format === "table") {
|
|
42
|
+
printTableReport(report);
|
|
43
|
+
} else {
|
|
44
|
+
console.log(output);
|
|
45
|
+
}
|
|
46
|
+
const hasCritical = report.summary.bySeverity.critical > 0;
|
|
47
|
+
const hasHigh = report.summary.bySeverity.high > 0;
|
|
48
|
+
if (hasCritical || hasHigh) {
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (spinner) {
|
|
53
|
+
spinner.fail("Scan failed");
|
|
54
|
+
}
|
|
55
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : error);
|
|
56
|
+
process.exit(3);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
program.command("audit").description("Audit extensions against policy rules").option("-c, --config <path>", "Path to policy config file", ".extension-guard.json").option("-p, --path <paths...>", "Custom extension paths to scan").option("--fail-on <level>", "Fail on violations at or above level (block|warn|info)", "block").option("-f, --format <format>", "Output format (table|json|sarif|markdown)", "table").option("-o, --output <file>", "Output file path (default: stdout)").option("-q, --quiet", "Only show results, no progress").action(async (options) => {
|
|
60
|
+
const isJsonOutput = options.format === "json" || options.format === "sarif";
|
|
61
|
+
const spinner = options.quiet || isJsonOutput ? null : ora("Loading policy configuration...").start();
|
|
62
|
+
try {
|
|
63
|
+
const policyConfig = await loadPolicyConfig(options.config);
|
|
64
|
+
if (!policyConfig) {
|
|
65
|
+
if (spinner) {
|
|
66
|
+
spinner.fail("Policy configuration not found");
|
|
67
|
+
}
|
|
68
|
+
console.error(chalk.red(`Error: Could not find policy config at ${options.config}`));
|
|
69
|
+
console.error(chalk.dim("Create a .extension-guard.json file or specify a path with --config"));
|
|
70
|
+
process.exit(2);
|
|
71
|
+
}
|
|
72
|
+
if (spinner) {
|
|
73
|
+
spinner.text = "Scanning extensions...";
|
|
74
|
+
}
|
|
75
|
+
const scanner = new ExtensionGuardScanner({
|
|
76
|
+
autoDetect: !options.path,
|
|
77
|
+
idePaths: options.path ?? [],
|
|
78
|
+
severity: policyConfig.scanning?.minSeverity ?? "info"
|
|
79
|
+
});
|
|
80
|
+
const report = await scanner.scan();
|
|
81
|
+
if (spinner) {
|
|
82
|
+
spinner.text = "Evaluating policy rules...";
|
|
83
|
+
}
|
|
84
|
+
const engine = new PolicyEngine(policyConfig);
|
|
85
|
+
const violations = engine.evaluate(report.results);
|
|
86
|
+
if (spinner) {
|
|
87
|
+
spinner.succeed("Audit complete");
|
|
88
|
+
}
|
|
89
|
+
if (options.format !== "table") {
|
|
90
|
+
const output = generateOutput(report, options.format, {
|
|
91
|
+
includeSafe: false
|
|
92
|
+
});
|
|
93
|
+
if (options.output) {
|
|
94
|
+
fs.writeFileSync(options.output, output);
|
|
95
|
+
if (!options.quiet) {
|
|
96
|
+
console.log(chalk.green(`Report saved to ${options.output}`));
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
console.log(output);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
printAuditResults(violations, options.failOn);
|
|
103
|
+
const shouldFail = hasViolationsAtLevel(violations, options.failOn);
|
|
104
|
+
if (shouldFail) {
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (spinner) {
|
|
109
|
+
spinner.fail("Audit failed");
|
|
110
|
+
}
|
|
111
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : error);
|
|
112
|
+
process.exit(3);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
return program;
|
|
116
|
+
}
|
|
117
|
+
function generateOutput(report, format, options = {}) {
|
|
118
|
+
let reporter;
|
|
119
|
+
switch (format) {
|
|
120
|
+
case "json":
|
|
121
|
+
reporter = new JsonReporter();
|
|
122
|
+
break;
|
|
123
|
+
case "sarif":
|
|
124
|
+
reporter = new SarifReporter();
|
|
125
|
+
break;
|
|
126
|
+
case "markdown":
|
|
127
|
+
reporter = new MarkdownReporter();
|
|
128
|
+
break;
|
|
129
|
+
case "table":
|
|
130
|
+
default:
|
|
131
|
+
return "";
|
|
132
|
+
}
|
|
133
|
+
return reporter.generate(report, {
|
|
134
|
+
includeSafe: options.includeSafe,
|
|
135
|
+
includeEvidence: true
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function printTableReport(report) {
|
|
139
|
+
console.log();
|
|
140
|
+
console.log(chalk.bold(`\u{1F6E1}\uFE0F Extension Guard v${VERSION}`));
|
|
141
|
+
console.log();
|
|
142
|
+
for (const ide of report.environment.ides) {
|
|
143
|
+
console.log(`\u{1F4C1} ${chalk.cyan(ide.name)}: ${ide.path} (${ide.extensionCount} extensions)`);
|
|
144
|
+
}
|
|
145
|
+
console.log();
|
|
146
|
+
console.log(chalk.dim("\u2501".repeat(60)));
|
|
147
|
+
console.log();
|
|
148
|
+
const critical = report.results.filter((r) => r.riskLevel === "critical");
|
|
149
|
+
const high = report.results.filter((r) => r.riskLevel === "high");
|
|
150
|
+
const medium = report.results.filter((r) => r.riskLevel === "medium");
|
|
151
|
+
const safe = report.results.filter((r) => r.riskLevel === "safe" || r.riskLevel === "low");
|
|
152
|
+
if (critical.length > 0) {
|
|
153
|
+
console.log(chalk.red.bold(`\u26D4 CRITICAL (${critical.length})`));
|
|
154
|
+
for (const ext of critical) {
|
|
155
|
+
printExtensionResult(ext);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (high.length > 0) {
|
|
159
|
+
console.log(chalk.red(`\u{1F534} HIGH (${high.length})`));
|
|
160
|
+
for (const ext of high) {
|
|
161
|
+
printExtensionResult(ext);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (medium.length > 0) {
|
|
165
|
+
console.log(chalk.yellow(`\u{1F7E1} MEDIUM (${medium.length})`));
|
|
166
|
+
for (const ext of medium.slice(0, 3)) {
|
|
167
|
+
console.log(` ${ext.extensionId}`);
|
|
168
|
+
}
|
|
169
|
+
if (medium.length > 3) {
|
|
170
|
+
console.log(chalk.dim(` ... and ${medium.length - 3} more`));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (safe.length > 0) {
|
|
174
|
+
console.log(chalk.green(`\u{1F7E2} SAFE (${safe.length})`));
|
|
175
|
+
}
|
|
176
|
+
console.log();
|
|
177
|
+
console.log(chalk.dim("\u2501".repeat(60)));
|
|
178
|
+
console.log();
|
|
179
|
+
const { bySeverity, byRiskLevel } = report.summary;
|
|
180
|
+
console.log(
|
|
181
|
+
`\u{1F4CA} Summary: ${report.uniqueExtensions} scanned \xB7 ${bySeverity.critical} critical \xB7 ${bySeverity.high} high \xB7 ${bySeverity.medium} medium \xB7 ${byRiskLevel.safe} safe`
|
|
182
|
+
);
|
|
183
|
+
console.log(`\u23F1\uFE0F Completed in ${(report.scanDurationMs / 1e3).toFixed(1)}s`);
|
|
184
|
+
console.log();
|
|
185
|
+
}
|
|
186
|
+
function printExtensionResult(result) {
|
|
187
|
+
console.log(` ${chalk.bold(result.extensionId)} v${result.version}`);
|
|
188
|
+
console.log(` Publisher: ${result.metadata.publisher.name}`);
|
|
189
|
+
console.log(` Trust Score: ${result.trustScore}/100`);
|
|
190
|
+
if (result.findings.length > 0) {
|
|
191
|
+
for (const finding of result.findings.slice(0, 3)) {
|
|
192
|
+
const icon = finding.severity === "critical" ? "CRIT" : finding.severity.toUpperCase();
|
|
193
|
+
console.log(` \u2502 ${icon} ${finding.title}`);
|
|
194
|
+
if (finding.evidence.filePath) {
|
|
195
|
+
console.log(` \u2502 at ${finding.evidence.filePath}:${finding.evidence.lineNumber ?? "?"}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (result.findings.length > 3) {
|
|
199
|
+
console.log(chalk.dim(` \u2502 ... and ${result.findings.length - 3} more findings`));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
console.log();
|
|
203
|
+
}
|
|
204
|
+
function printAuditResults(violations, failOnLevel) {
|
|
205
|
+
console.log();
|
|
206
|
+
console.log(chalk.bold("Policy Audit Results"));
|
|
207
|
+
console.log(chalk.dim("\u2501".repeat(60)));
|
|
208
|
+
console.log();
|
|
209
|
+
if (violations.length === 0) {
|
|
210
|
+
console.log(chalk.green("No policy violations found."));
|
|
211
|
+
console.log();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const blocked = violations.filter((v) => v.action === "block");
|
|
215
|
+
const warned = violations.filter((v) => v.action === "warn");
|
|
216
|
+
const info = violations.filter((v) => v.action === "info");
|
|
217
|
+
if (blocked.length > 0) {
|
|
218
|
+
console.log(chalk.red.bold(`BLOCKED (${blocked.length})`));
|
|
219
|
+
for (const v of blocked) {
|
|
220
|
+
console.log(` ${chalk.red("x")} ${chalk.bold(v.extensionId)}`);
|
|
221
|
+
console.log(` Rule: ${v.rule}`);
|
|
222
|
+
console.log(` ${v.message}`);
|
|
223
|
+
}
|
|
224
|
+
console.log();
|
|
225
|
+
}
|
|
226
|
+
if (warned.length > 0) {
|
|
227
|
+
console.log(chalk.yellow.bold(`WARNINGS (${warned.length})`));
|
|
228
|
+
for (const v of warned) {
|
|
229
|
+
console.log(` ${chalk.yellow("!")} ${chalk.bold(v.extensionId)}`);
|
|
230
|
+
console.log(` Rule: ${v.rule}`);
|
|
231
|
+
console.log(` ${v.message}`);
|
|
232
|
+
}
|
|
233
|
+
console.log();
|
|
234
|
+
}
|
|
235
|
+
if (info.length > 0) {
|
|
236
|
+
console.log(chalk.blue.bold(`INFO (${info.length})`));
|
|
237
|
+
for (const v of info) {
|
|
238
|
+
console.log(` ${chalk.blue("i")} ${chalk.bold(v.extensionId)}`);
|
|
239
|
+
console.log(` Rule: ${v.rule}`);
|
|
240
|
+
console.log(` ${v.message}`);
|
|
241
|
+
}
|
|
242
|
+
console.log();
|
|
243
|
+
}
|
|
244
|
+
console.log(chalk.dim("\u2501".repeat(60)));
|
|
245
|
+
console.log();
|
|
246
|
+
const total = violations.length;
|
|
247
|
+
const willFail = hasViolationsAtLevel(violations, failOnLevel);
|
|
248
|
+
console.log(
|
|
249
|
+
`Total: ${total} violation${total !== 1 ? "s" : ""} (${blocked.length} blocked, ${warned.length} warnings, ${info.length} info)`
|
|
250
|
+
);
|
|
251
|
+
console.log(`Fail-on level: ${failOnLevel}`);
|
|
252
|
+
if (willFail) {
|
|
253
|
+
console.log(chalk.red(`Status: FAILED - violations found at or above '${failOnLevel}' level`));
|
|
254
|
+
} else {
|
|
255
|
+
console.log(chalk.green(`Status: PASSED - no violations at or above '${failOnLevel}' level`));
|
|
256
|
+
}
|
|
257
|
+
console.log();
|
|
258
|
+
}
|
|
259
|
+
function hasViolationsAtLevel(violations, level) {
|
|
260
|
+
const levelHierarchy = {
|
|
261
|
+
block: 3,
|
|
262
|
+
warn: 2,
|
|
263
|
+
info: 1
|
|
264
|
+
};
|
|
265
|
+
const threshold = levelHierarchy[level];
|
|
266
|
+
return violations.some((v) => levelHierarchy[v.action] >= threshold);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/index.ts
|
|
270
|
+
var cli = createCli();
|
|
271
|
+
cli.parse();
|
|
272
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/index.ts"],"sourcesContent":["import { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport * as fs from 'node:fs';\nimport {\n ExtensionGuardScanner,\n VERSION,\n JsonReporter,\n SarifReporter,\n MarkdownReporter,\n loadPolicyConfig,\n PolicyEngine,\n} from '@aspect-guard/core';\nimport type { FullScanReport, Reporter, PolicyViolation, PolicyAction } from '@aspect-guard/core';\n\nexport function createCli(): Command {\n const program = new Command();\n\n program\n .name('extension-guard')\n .description('Scan VSCode extensions for security issues')\n .version(VERSION);\n\n program\n .command('scan')\n .description('Scan installed VSCode extensions')\n .option('-p, --path <paths...>', 'Custom extension paths to scan')\n .option('-f, --format <format>', 'Output format (table|json|sarif|markdown)', 'table')\n .option('-o, --output <file>', 'Output file path (default: stdout)')\n .option('-s, --severity <level>', 'Minimum severity to show', 'info')\n .option('-q, --quiet', 'Only show results, no progress')\n .option('--include-safe', 'Include safe extensions in output')\n .action(async (options) => {\n const isJsonOutput = options.format === 'json' || options.format === 'sarif';\n const spinner = (options.quiet || isJsonOutput) ? null : ora('Scanning extensions...').start();\n\n try {\n const scanner = new ExtensionGuardScanner({\n autoDetect: !options.path,\n idePaths: options.path ?? [],\n severity: options.severity,\n });\n\n const report = await scanner.scan();\n\n if (spinner) {\n spinner.succeed('Scan complete');\n }\n\n // Generate output based on format\n const output = generateOutput(report, options.format, {\n includeSafe: options.includeSafe,\n });\n\n // Write to file or stdout\n if (options.output) {\n fs.writeFileSync(options.output, output);\n if (!options.quiet) {\n console.log(chalk.green(`Report saved to ${options.output}`));\n }\n } else if (options.format === 'table') {\n printTableReport(report);\n } else {\n console.log(output);\n }\n\n // Exit with error code if critical or high issues found\n const hasCritical = report.summary.bySeverity.critical > 0;\n const hasHigh = report.summary.bySeverity.high > 0;\n if (hasCritical || hasHigh) {\n process.exit(1);\n }\n } catch (error) {\n if (spinner) {\n spinner.fail('Scan failed');\n }\n console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);\n process.exit(3);\n }\n });\n\n program\n .command('audit')\n .description('Audit extensions against policy rules')\n .option('-c, --config <path>', 'Path to policy config file', '.extension-guard.json')\n .option('-p, --path <paths...>', 'Custom extension paths to scan')\n .option('--fail-on <level>', 'Fail on violations at or above level (block|warn|info)', 'block')\n .option('-f, --format <format>', 'Output format (table|json|sarif|markdown)', 'table')\n .option('-o, --output <file>', 'Output file path (default: stdout)')\n .option('-q, --quiet', 'Only show results, no progress')\n .action(async (options) => {\n const isJsonOutput = options.format === 'json' || options.format === 'sarif';\n const spinner = (options.quiet || isJsonOutput) ? null : ora('Loading policy configuration...').start();\n\n try {\n // Load policy configuration\n const policyConfig = await loadPolicyConfig(options.config);\n\n if (!policyConfig) {\n if (spinner) {\n spinner.fail('Policy configuration not found');\n }\n console.error(chalk.red(`Error: Could not find policy config at ${options.config}`));\n console.error(chalk.dim('Create a .extension-guard.json file or specify a path with --config'));\n process.exit(2);\n }\n\n if (spinner) {\n spinner.text = 'Scanning extensions...';\n }\n\n // Run scan with config-based settings\n const scanner = new ExtensionGuardScanner({\n autoDetect: !options.path,\n idePaths: options.path ?? [],\n severity: policyConfig.scanning?.minSeverity ?? 'info',\n });\n\n const report = await scanner.scan();\n\n if (spinner) {\n spinner.text = 'Evaluating policy rules...';\n }\n\n // Evaluate policy\n const engine = new PolicyEngine(policyConfig);\n const violations = engine.evaluate(report.results);\n\n if (spinner) {\n spinner.succeed('Audit complete');\n }\n\n // Generate output based on format\n if (options.format !== 'table') {\n const output = generateOutput(report, options.format, {\n includeSafe: false,\n });\n if (options.output) {\n fs.writeFileSync(options.output, output);\n if (!options.quiet) {\n console.log(chalk.green(`Report saved to ${options.output}`));\n }\n } else {\n console.log(output);\n }\n }\n\n // Print violations summary\n printAuditResults(violations, options.failOn as PolicyAction);\n\n // Determine exit code based on --fail-on level\n const shouldFail = hasViolationsAtLevel(violations, options.failOn as PolicyAction);\n if (shouldFail) {\n process.exit(1);\n }\n } catch (error) {\n if (spinner) {\n spinner.fail('Audit failed');\n }\n console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);\n process.exit(3);\n }\n });\n\n return program;\n}\n\nfunction generateOutput(\n report: FullScanReport,\n format: string,\n options: { includeSafe?: boolean } = {}\n): string {\n let reporter: Reporter;\n\n switch (format) {\n case 'json':\n reporter = new JsonReporter();\n break;\n case 'sarif':\n reporter = new SarifReporter();\n break;\n case 'markdown':\n reporter = new MarkdownReporter();\n break;\n case 'table':\n default:\n // Table format is handled separately\n return '';\n }\n\n return reporter.generate(report, {\n includeSafe: options.includeSafe,\n includeEvidence: true,\n });\n}\n\nfunction printTableReport(report: FullScanReport): void {\n console.log();\n console.log(chalk.bold(`๐ก๏ธ Extension Guard v${VERSION}`));\n console.log();\n\n for (const ide of report.environment.ides) {\n console.log(`๐ ${chalk.cyan(ide.name)}: ${ide.path} (${ide.extensionCount} extensions)`);\n }\n\n console.log();\n console.log(chalk.dim('โ'.repeat(60)));\n console.log();\n\n const critical = report.results.filter((r) => r.riskLevel === 'critical');\n const high = report.results.filter((r) => r.riskLevel === 'high');\n const medium = report.results.filter((r) => r.riskLevel === 'medium');\n const safe = report.results.filter((r) => r.riskLevel === 'safe' || r.riskLevel === 'low');\n\n if (critical.length > 0) {\n console.log(chalk.red.bold(`โ CRITICAL (${critical.length})`));\n for (const ext of critical) {\n printExtensionResult(ext);\n }\n }\n\n if (high.length > 0) {\n console.log(chalk.red(`๐ด HIGH (${high.length})`));\n for (const ext of high) {\n printExtensionResult(ext);\n }\n }\n\n if (medium.length > 0) {\n console.log(chalk.yellow(`๐ก MEDIUM (${medium.length})`));\n for (const ext of medium.slice(0, 3)) {\n console.log(` ${ext.extensionId}`);\n }\n if (medium.length > 3) {\n console.log(chalk.dim(` ... and ${medium.length - 3} more`));\n }\n }\n\n if (safe.length > 0) {\n console.log(chalk.green(`๐ข SAFE (${safe.length})`));\n }\n\n console.log();\n console.log(chalk.dim('โ'.repeat(60)));\n console.log();\n\n const { bySeverity, byRiskLevel } = report.summary;\n console.log(\n `๐ Summary: ${report.uniqueExtensions} scanned ยท ` +\n `${bySeverity.critical} critical ยท ${bySeverity.high} high ยท ` +\n `${bySeverity.medium} medium ยท ${byRiskLevel.safe} safe`\n );\n console.log(`โฑ๏ธ Completed in ${(report.scanDurationMs / 1000).toFixed(1)}s`);\n console.log();\n}\n\nfunction printExtensionResult(result: FullScanReport['results'][0]): void {\n console.log(` ${chalk.bold(result.extensionId)} v${result.version}`);\n console.log(` Publisher: ${result.metadata.publisher.name}`);\n console.log(` Trust Score: ${result.trustScore}/100`);\n\n if (result.findings.length > 0) {\n for (const finding of result.findings.slice(0, 3)) {\n const icon = finding.severity === 'critical' ? 'CRIT' : finding.severity.toUpperCase();\n console.log(` โ ${icon} ${finding.title}`);\n if (finding.evidence.filePath) {\n console.log(` โ at ${finding.evidence.filePath}:${finding.evidence.lineNumber ?? '?'}`);\n }\n }\n if (result.findings.length > 3) {\n console.log(chalk.dim(` โ ... and ${result.findings.length - 3} more findings`));\n }\n }\n console.log();\n}\n\n/**\n * Print audit results with violations grouped by action level.\n */\nfunction printAuditResults(violations: PolicyViolation[], failOnLevel: PolicyAction): void {\n console.log();\n console.log(chalk.bold('Policy Audit Results'));\n console.log(chalk.dim('โ'.repeat(60)));\n console.log();\n\n if (violations.length === 0) {\n console.log(chalk.green('No policy violations found.'));\n console.log();\n return;\n }\n\n const blocked = violations.filter(v => v.action === 'block');\n const warned = violations.filter(v => v.action === 'warn');\n const info = violations.filter(v => v.action === 'info');\n\n if (blocked.length > 0) {\n console.log(chalk.red.bold(`BLOCKED (${blocked.length})`));\n for (const v of blocked) {\n console.log(` ${chalk.red('x')} ${chalk.bold(v.extensionId)}`);\n console.log(` Rule: ${v.rule}`);\n console.log(` ${v.message}`);\n }\n console.log();\n }\n\n if (warned.length > 0) {\n console.log(chalk.yellow.bold(`WARNINGS (${warned.length})`));\n for (const v of warned) {\n console.log(` ${chalk.yellow('!')} ${chalk.bold(v.extensionId)}`);\n console.log(` Rule: ${v.rule}`);\n console.log(` ${v.message}`);\n }\n console.log();\n }\n\n if (info.length > 0) {\n console.log(chalk.blue.bold(`INFO (${info.length})`));\n for (const v of info) {\n console.log(` ${chalk.blue('i')} ${chalk.bold(v.extensionId)}`);\n console.log(` Rule: ${v.rule}`);\n console.log(` ${v.message}`);\n }\n console.log();\n }\n\n console.log(chalk.dim('โ'.repeat(60)));\n console.log();\n\n const total = violations.length;\n const willFail = hasViolationsAtLevel(violations, failOnLevel);\n\n console.log(\n `Total: ${total} violation${total !== 1 ? 's' : ''} ` +\n `(${blocked.length} blocked, ${warned.length} warnings, ${info.length} info)`\n );\n console.log(`Fail-on level: ${failOnLevel}`);\n\n if (willFail) {\n console.log(chalk.red(`Status: FAILED - violations found at or above '${failOnLevel}' level`));\n } else {\n console.log(chalk.green(`Status: PASSED - no violations at or above '${failOnLevel}' level`));\n }\n console.log();\n}\n\n/**\n * Check if there are violations at or above the specified level.\n * Level hierarchy: block > warn > info\n */\nfunction hasViolationsAtLevel(violations: PolicyViolation[], level: PolicyAction): boolean {\n const levelHierarchy: Record<PolicyAction, number> = {\n block: 3,\n warn: 2,\n info: 1,\n };\n\n const threshold = levelHierarchy[level];\n\n return violations.some(v => levelHierarchy[v.action] >= threshold);\n}\n","import { createCli } from './cli.js';\n\nconst cli = createCli();\ncli.parse();\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "extension-guard",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for scanning VSCode extensions for security issues",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"extension-guard": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"chalk": "^5.3.0",
|
|
14
|
+
"commander": "^12.0.0",
|
|
15
|
+
"ora": "^8.0.0",
|
|
16
|
+
"@aspect-guard/core": "0.1.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"tsup": "^8.0.0",
|
|
20
|
+
"typescript": "^5.4.0",
|
|
21
|
+
"@types/node": "^20.0.0"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18.0.0"
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup",
|
|
29
|
+
"dev": "tsup --watch",
|
|
30
|
+
"start": "node dist/index.js",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"clean": "rm -rf dist"
|
|
33
|
+
}
|
|
34
|
+
}
|