supasec 1.0.3 → 1.0.5
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/Feature-List.md +233 -0
- package/README.md +53 -12
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/scan.d.ts.map +1 -1
- package/dist/commands/scan.js +82 -26
- package/dist/commands/scan.js.map +1 -1
- package/dist/commands/snapshot.d.ts +32 -0
- package/dist/commands/snapshot.d.ts.map +1 -0
- package/dist/commands/snapshot.js +282 -0
- package/dist/commands/snapshot.js.map +1 -0
- package/dist/reporters/html.d.ts +3 -2
- package/dist/reporters/html.d.ts.map +1 -1
- package/dist/reporters/html.js +844 -538
- package/dist/reporters/html.js.map +1 -1
- package/dist/reporters/terminal.d.ts +38 -2
- package/dist/reporters/terminal.d.ts.map +1 -1
- package/dist/reporters/terminal.js +292 -131
- package/dist/reporters/terminal.js.map +1 -1
- package/dist/scanners/auth/analyzer.d.ts +40 -0
- package/dist/scanners/auth/analyzer.d.ts.map +1 -0
- package/dist/scanners/auth/analyzer.js +673 -0
- package/dist/scanners/auth/analyzer.js.map +1 -0
- package/dist/scanners/auth/index.d.ts +6 -0
- package/dist/scanners/auth/index.d.ts.map +1 -0
- package/dist/scanners/auth/index.js +22 -0
- package/dist/scanners/auth/index.js.map +1 -0
- package/dist/scanners/edge/analyzer.d.ts +35 -0
- package/dist/scanners/edge/analyzer.d.ts.map +1 -0
- package/dist/scanners/edge/analyzer.js +614 -0
- package/dist/scanners/edge/analyzer.js.map +1 -0
- package/dist/scanners/edge/index.d.ts +6 -0
- package/dist/scanners/edge/index.d.ts.map +1 -0
- package/dist/scanners/edge/index.js +22 -0
- package/dist/scanners/edge/index.js.map +1 -0
- package/dist/scanners/functions/analyzer.d.ts +41 -0
- package/dist/scanners/functions/analyzer.d.ts.map +1 -0
- package/dist/scanners/functions/analyzer.js +378 -0
- package/dist/scanners/functions/analyzer.js.map +1 -0
- package/dist/scanners/functions/index.d.ts +6 -0
- package/dist/scanners/functions/index.d.ts.map +1 -0
- package/dist/scanners/functions/index.js +22 -0
- package/dist/scanners/functions/index.js.map +1 -0
- package/dist/scanners/git/index.d.ts +6 -0
- package/dist/scanners/git/index.d.ts.map +1 -0
- package/dist/scanners/git/index.js +22 -0
- package/dist/scanners/git/index.js.map +1 -0
- package/dist/scanners/git/scanner.d.ts +22 -0
- package/dist/scanners/git/scanner.d.ts.map +1 -0
- package/dist/scanners/git/scanner.js +531 -0
- package/dist/scanners/git/scanner.js.map +1 -0
- package/dist/scanners/https/analyzer.d.ts +42 -0
- package/dist/scanners/https/analyzer.d.ts.map +1 -0
- package/dist/scanners/https/analyzer.js +470 -0
- package/dist/scanners/https/analyzer.js.map +1 -0
- package/dist/scanners/https/index.d.ts +8 -0
- package/dist/scanners/https/index.d.ts.map +1 -0
- package/dist/scanners/https/index.js +17 -0
- package/dist/scanners/https/index.js.map +1 -0
- package/dist/scanners/index.d.ts +6 -0
- package/dist/scanners/index.d.ts.map +1 -1
- package/dist/scanners/index.js +6 -0
- package/dist/scanners/index.js.map +1 -1
- package/dist/scanners/rls/fuzzer.d.ts +40 -0
- package/dist/scanners/rls/fuzzer.d.ts.map +1 -0
- package/dist/scanners/rls/fuzzer.js +360 -0
- package/dist/scanners/rls/fuzzer.js.map +1 -0
- package/dist/scanners/rls/index.d.ts +1 -0
- package/dist/scanners/rls/index.d.ts.map +1 -1
- package/dist/scanners/rls/index.js +1 -0
- package/dist/scanners/rls/index.js.map +1 -1
- package/dist/scanners/secrets/detector.d.ts.map +1 -1
- package/dist/scanners/secrets/detector.js +44 -12
- package/dist/scanners/secrets/detector.js.map +1 -1
- package/dist/scanners/secrets/index.d.ts +1 -0
- package/dist/scanners/secrets/index.d.ts.map +1 -1
- package/dist/scanners/secrets/index.js +4 -0
- package/dist/scanners/secrets/index.js.map +1 -1
- package/dist/scanners/secrets/patterns.d.ts +25 -0
- package/dist/scanners/secrets/patterns.d.ts.map +1 -1
- package/dist/scanners/secrets/patterns.js +138 -27
- package/dist/scanners/secrets/patterns.js.map +1 -1
- package/dist/scanners/storage/analyzer.d.ts +49 -0
- package/dist/scanners/storage/analyzer.d.ts.map +1 -0
- package/dist/scanners/storage/analyzer.js +438 -0
- package/dist/scanners/storage/analyzer.js.map +1 -0
- package/dist/scanners/storage/index.d.ts +6 -0
- package/dist/scanners/storage/index.d.ts.map +1 -0
- package/dist/scanners/storage/index.js +22 -0
- package/dist/scanners/storage/index.js.map +1 -0
- package/package.json +1 -1
- package/reports/{supasec-audityour-app-2026-01-28-17-09-24.html → supasec-audityour-app-2026-01-28-19-42-22.html} +51 -16
- package/reports/supasec-audityour-app-2026-01-28-19-49-18.html +1122 -0
- package/COMPLETION_REPORT.md +0 -324
- package/FIXES_SUMMARY.md +0 -224
- package/IMPLEMENTATION_NOTES.md +0 -305
- package/QUICK_REFERENCE.md +0 -185
- package/REPORTING.md +0 -217
- package/STATUS.md +0 -269
- package/reports/supasec---------app-2026-01-28-16-58-47.html +0 -804
- package/reports/supasec---------app-2026-01-28-17-06-43.html +0 -722
- package/reports/supasec---------app-2026-01-28-17-07-23.html +0 -722
- package/reports/supasec---------app-2026-01-28-17-08-00.html +0 -722
- package/reports/supasec---------app-2026-01-28-17-08-20.html +0 -722
- package/reports/supasec---------app-2026-01-28-17-08-41.html +0 -722
- package/reports/supasec-au---your-app-2026-01-28-17-14-57.html +0 -715
- package/reports/supasec-au---your-app-2026-01-28-17-19-03.html +0 -715
- package/reports/supasec-ex-mple-com-2026-01-28-17-14-52.json +0 -229
- package/reports/supasec-ex-mple-com-2026-01-28-17-15-39.html +0 -715
- package/reports/supasec-ex-mple-com-2026-01-28-17-17-22.html +0 -715
- package/reports/supasec-example-com-2026-01-28-17-15-06.html +0 -715
- package/reports/supasec-my--------------name-com-2026-01-28-17-15-02.html +0 -715
- package/reports/supasec-st-ging-com-2026-01-28-17-16-17.html +0 -715
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Terminal Reporter
|
|
4
|
-
* Formats and displays scan results in the terminal
|
|
4
|
+
* Formats and displays scan results in the terminal with enhanced visual output
|
|
5
5
|
*/
|
|
6
6
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
7
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
@@ -9,8 +9,29 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.generateTerminalReport = generateTerminalReport;
|
|
11
11
|
exports.printReport = printReport;
|
|
12
|
+
exports.generateSummary = generateEnhancedSummary;
|
|
13
|
+
exports.generateFindingsSection = generateEnhancedFindingsSection;
|
|
14
|
+
exports.formatFinding = formatEnhancedFinding;
|
|
15
|
+
exports.generatePassedSection = generateEnhancedPassedSection;
|
|
16
|
+
exports.generateGradeSection = generateEnhancedGradeSection;
|
|
17
|
+
exports.generateActionsSection = generateEnhancedActionsSection;
|
|
18
|
+
exports.generateFooter = generateEnhancedFooter;
|
|
12
19
|
const chalk_1 = __importDefault(require("chalk"));
|
|
13
20
|
const finding_js_1 = require("../models/finding.js");
|
|
21
|
+
// Box drawing characters for better visuals
|
|
22
|
+
const BOX = {
|
|
23
|
+
topLeft: '┌',
|
|
24
|
+
topRight: '┐',
|
|
25
|
+
bottomLeft: '└',
|
|
26
|
+
bottomRight: '┘',
|
|
27
|
+
horizontal: '─',
|
|
28
|
+
vertical: '│',
|
|
29
|
+
leftT: '├',
|
|
30
|
+
rightT: '┤',
|
|
31
|
+
topT: '┬',
|
|
32
|
+
bottomT: '┴',
|
|
33
|
+
cross: '┼'
|
|
34
|
+
};
|
|
14
35
|
/**
|
|
15
36
|
* Generate terminal report from scan result
|
|
16
37
|
*/
|
|
@@ -21,77 +42,110 @@ function generateTerminalReport(result, options = {}) {
|
|
|
21
42
|
chalk_1.default.level = 0;
|
|
22
43
|
}
|
|
23
44
|
const lines = [];
|
|
24
|
-
// Header
|
|
25
|
-
lines.push(
|
|
26
|
-
// Summary
|
|
27
|
-
lines.push(
|
|
28
|
-
// Findings by severity
|
|
45
|
+
// Header with banner
|
|
46
|
+
lines.push(generateBanner(result));
|
|
47
|
+
// Summary with progress bars
|
|
48
|
+
lines.push(generateEnhancedSummary(result));
|
|
49
|
+
// Findings by severity with enhanced formatting
|
|
29
50
|
if (result.findings.length > 0) {
|
|
30
|
-
lines.push(
|
|
51
|
+
lines.push(generateEnhancedFindingsSection(result.findings, showRemediation, compact));
|
|
31
52
|
}
|
|
32
|
-
// Passed checks
|
|
53
|
+
// Passed checks with icons
|
|
33
54
|
if (showPassed && result.passed_checks.length > 0) {
|
|
34
|
-
lines.push(
|
|
55
|
+
lines.push(generateEnhancedPassedSection(result.passed_checks, compact));
|
|
35
56
|
}
|
|
36
|
-
// Grade
|
|
37
|
-
lines.push(
|
|
57
|
+
// Grade with visual indicator
|
|
58
|
+
lines.push(generateEnhancedGradeSection(result));
|
|
38
59
|
// Quick actions
|
|
39
|
-
lines.push(
|
|
60
|
+
lines.push(generateEnhancedActionsSection(result));
|
|
40
61
|
// Footer
|
|
41
|
-
lines.push(
|
|
62
|
+
lines.push(generateEnhancedFooter(result));
|
|
42
63
|
return lines.join('\n');
|
|
43
64
|
}
|
|
44
65
|
/**
|
|
45
|
-
* Generate header
|
|
66
|
+
* Generate banner header
|
|
46
67
|
*/
|
|
47
|
-
function
|
|
68
|
+
function generateBanner(result) {
|
|
48
69
|
const lines = [];
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
lines.push(chalk_1.default.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
lines.push('');
|
|
70
|
+
const width = 60;
|
|
71
|
+
// Top border
|
|
72
|
+
lines.push(chalk_1.default.cyan(BOX.topLeft + BOX.horizontal.repeat(width - 2) + BOX.topRight));
|
|
73
|
+
// Title
|
|
74
|
+
const title = '🔍 SUPASEC SECURITY AUDIT';
|
|
75
|
+
const padding = Math.floor((width - 2 - title.length) / 2);
|
|
76
|
+
lines.push(chalk_1.default.cyan(BOX.vertical) + ' '.repeat(padding) + chalk_1.default.bold.white(title) + ' '.repeat(width - 2 - padding - title.length) + chalk_1.default.cyan(BOX.vertical));
|
|
77
|
+
// Version
|
|
78
|
+
const version = 'v1.0.4';
|
|
79
|
+
const versionPadding = Math.floor((width - 2 - version.length) / 2);
|
|
80
|
+
lines.push(chalk_1.default.cyan(BOX.vertical) + ' '.repeat(versionPadding) + chalk_1.default.gray(version) + ' '.repeat(width - 2 - versionPadding - version.length) + chalk_1.default.cyan(BOX.vertical));
|
|
81
|
+
// Separator
|
|
82
|
+
lines.push(chalk_1.default.cyan(BOX.leftT + BOX.horizontal.repeat(width - 2) + BOX.rightT));
|
|
83
|
+
// Target info
|
|
84
|
+
const targetText = `🎯 Target: ${result.scan_metadata.target_url}`;
|
|
85
|
+
lines.push(chalk_1.default.cyan(BOX.vertical) + ' ' + chalk_1.default.white(targetText) + ' '.repeat(Math.max(0, width - 3 - targetText.length)) + chalk_1.default.cyan(BOX.vertical));
|
|
86
|
+
// Date
|
|
87
|
+
const dateText = `⏱️ Started: ${new Date(result.scan_metadata.scan_date).toLocaleString()}`;
|
|
88
|
+
lines.push(chalk_1.default.cyan(BOX.vertical) + ' ' + chalk_1.default.gray(dateText) + ' '.repeat(Math.max(0, width - 3 - dateText.length)) + chalk_1.default.cyan(BOX.vertical));
|
|
89
|
+
// Project info if available
|
|
56
90
|
if (result.project_info.tables_count > 0) {
|
|
57
|
-
lines.push(chalk_1.default.
|
|
58
|
-
|
|
59
|
-
lines.push('');
|
|
91
|
+
lines.push(chalk_1.default.cyan(BOX.leftT + BOX.horizontal.repeat(width - 2) + BOX.rightT));
|
|
92
|
+
const projectText = `✓ Supabase Project Detected`;
|
|
93
|
+
lines.push(chalk_1.default.cyan(BOX.vertical) + ' ' + chalk_1.default.green(projectText) + ' '.repeat(Math.max(0, width - 3 - projectText.length)) + chalk_1.default.cyan(BOX.vertical));
|
|
94
|
+
const statsText = ` ${result.project_info.tables_count} tables · ${result.project_info.rpcs_count} RPCs · ${result.project_info.storage_buckets} buckets · ${result.project_info.edge_functions} edge functions`;
|
|
95
|
+
lines.push(chalk_1.default.cyan(BOX.vertical) + ' ' + chalk_1.default.gray(statsText) + ' '.repeat(Math.max(0, width - 3 - statsText.length)) + chalk_1.default.cyan(BOX.vertical));
|
|
60
96
|
}
|
|
97
|
+
// Bottom border
|
|
98
|
+
lines.push(chalk_1.default.cyan(BOX.bottomLeft + BOX.horizontal.repeat(width - 2) + BOX.bottomRight));
|
|
99
|
+
lines.push('');
|
|
61
100
|
return lines.join('\n');
|
|
62
101
|
}
|
|
63
102
|
/**
|
|
64
|
-
* Generate summary
|
|
103
|
+
* Generate enhanced summary with progress bars
|
|
65
104
|
*/
|
|
66
|
-
function
|
|
105
|
+
function generateEnhancedSummary(result) {
|
|
67
106
|
const lines = [];
|
|
68
107
|
const counts = (0, finding_js_1.countFindingsBySeverity)(result.findings);
|
|
69
|
-
|
|
108
|
+
const width = 60;
|
|
70
109
|
lines.push(chalk_1.default.bold('📊 SCAN SUMMARY'));
|
|
71
|
-
lines.push(chalk_1.default.gray(
|
|
110
|
+
lines.push(chalk_1.default.gray(BOX.horizontal.repeat(width)));
|
|
72
111
|
lines.push('');
|
|
112
|
+
// Progress bar for security score
|
|
113
|
+
const score = result.grading.overall_score;
|
|
114
|
+
const scoreColor = score >= 80 ? chalk_1.default.green : score >= 60 ? chalk_1.default.yellow : score >= 40 ? chalk_1.default.hex('#FFA500') : chalk_1.default.red;
|
|
115
|
+
const filled = Math.round(score / 2);
|
|
116
|
+
const empty = 50 - filled;
|
|
117
|
+
const progressBar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
118
|
+
lines.push(` Security Score: ${scoreColor.bold(`${score}/100`)}`);
|
|
119
|
+
lines.push(` ${scoreColor(progressBar)}`);
|
|
120
|
+
lines.push('');
|
|
121
|
+
// Issue counts with icons and bars
|
|
122
|
+
const maxCount = Math.max(counts.CRITICAL, counts.HIGH, counts.MEDIUM, counts.LOW, 1);
|
|
73
123
|
if (counts.CRITICAL > 0) {
|
|
74
|
-
|
|
124
|
+
const bar = '█'.repeat(Math.round((counts.CRITICAL / maxCount) * 20));
|
|
125
|
+
lines.push(chalk_1.default.red(` ❌ CRITICAL ${bar} ${counts.CRITICAL}`));
|
|
75
126
|
}
|
|
76
127
|
if (counts.HIGH > 0) {
|
|
77
|
-
|
|
128
|
+
const bar = '█'.repeat(Math.round((counts.HIGH / maxCount) * 20));
|
|
129
|
+
lines.push(chalk_1.default.yellow(` ⚠️ HIGH ${bar} ${counts.HIGH}`));
|
|
78
130
|
}
|
|
79
131
|
if (counts.MEDIUM > 0) {
|
|
80
|
-
|
|
132
|
+
const bar = '█'.repeat(Math.round((counts.MEDIUM / maxCount) * 20));
|
|
133
|
+
lines.push(chalk_1.default.hex('#FFA500')(` ⚡ MEDIUM ${bar} ${counts.MEDIUM}`));
|
|
81
134
|
}
|
|
82
135
|
if (counts.LOW > 0) {
|
|
83
|
-
|
|
136
|
+
const bar = '█'.repeat(Math.round((counts.LOW / maxCount) * 20));
|
|
137
|
+
lines.push(chalk_1.default.blue(` ℹ️ LOW ${bar} ${counts.LOW}`));
|
|
84
138
|
}
|
|
85
139
|
if (counts.total === 0) {
|
|
86
|
-
lines.push(chalk_1.default.green('✅ No issues found!'));
|
|
140
|
+
lines.push(chalk_1.default.green(' ✅ No security issues found!'));
|
|
87
141
|
}
|
|
88
142
|
lines.push('');
|
|
89
143
|
return lines.join('\n');
|
|
90
144
|
}
|
|
91
145
|
/**
|
|
92
|
-
* Generate findings section
|
|
146
|
+
* Generate enhanced findings section with better formatting
|
|
93
147
|
*/
|
|
94
|
-
function
|
|
148
|
+
function generateEnhancedFindingsSection(findings, showRemediation, compact) {
|
|
95
149
|
const lines = [];
|
|
96
150
|
const sorted = (0, finding_js_1.sortFindingsBySeverity)(findings);
|
|
97
151
|
// Group by severity
|
|
@@ -107,179 +161,278 @@ function generateFindingsSection(findings, showRemediation, compact) {
|
|
|
107
161
|
}
|
|
108
162
|
// CRITICAL findings
|
|
109
163
|
if (bySeverity.CRITICAL.length > 0) {
|
|
110
|
-
lines.push(chalk_1.default.red
|
|
111
|
-
lines.push(chalk_1.default.red.bold(`❌ CRITICAL (${bySeverity.CRITICAL.length} issues)`));
|
|
112
|
-
lines.push(chalk_1.default.red('━'.repeat(50)));
|
|
113
|
-
lines.push('');
|
|
164
|
+
lines.push(generateSeverityHeader('CRITICAL', bySeverity.CRITICAL.length, chalk_1.default.red));
|
|
114
165
|
for (const finding of bySeverity.CRITICAL) {
|
|
115
|
-
lines.push(
|
|
166
|
+
lines.push(formatEnhancedFinding(finding, showRemediation, compact, chalk_1.default.red));
|
|
116
167
|
}
|
|
117
168
|
}
|
|
118
169
|
// HIGH findings
|
|
119
170
|
if (bySeverity.HIGH.length > 0) {
|
|
120
|
-
lines.push(chalk_1.default.yellow
|
|
121
|
-
lines.push(chalk_1.default.yellow.bold(`⚠️ HIGH (${bySeverity.HIGH.length} issues)`));
|
|
122
|
-
lines.push(chalk_1.default.yellow('━'.repeat(50)));
|
|
123
|
-
lines.push('');
|
|
171
|
+
lines.push(generateSeverityHeader('HIGH', bySeverity.HIGH.length, chalk_1.default.yellow));
|
|
124
172
|
for (const finding of bySeverity.HIGH) {
|
|
125
|
-
lines.push(
|
|
173
|
+
lines.push(formatEnhancedFinding(finding, showRemediation, compact, chalk_1.default.yellow));
|
|
126
174
|
}
|
|
127
175
|
}
|
|
128
176
|
// MEDIUM findings
|
|
129
177
|
if (bySeverity.MEDIUM.length > 0) {
|
|
130
|
-
lines.push(chalk_1.default.hex('#FFA500')
|
|
131
|
-
lines.push(chalk_1.default.hex('#FFA500').bold(`⚡ MEDIUM (${bySeverity.MEDIUM.length} issues)`));
|
|
132
|
-
lines.push(chalk_1.default.hex('#FFA500')('━'.repeat(50)));
|
|
133
|
-
lines.push('');
|
|
178
|
+
lines.push(generateSeverityHeader('MEDIUM', bySeverity.MEDIUM.length, chalk_1.default.hex('#FFA500')));
|
|
134
179
|
for (const finding of bySeverity.MEDIUM) {
|
|
135
|
-
lines.push(
|
|
180
|
+
lines.push(formatEnhancedFinding(finding, showRemediation, compact, chalk_1.default.hex('#FFA500')));
|
|
136
181
|
}
|
|
137
182
|
}
|
|
138
183
|
// LOW findings
|
|
139
184
|
if (bySeverity.LOW.length > 0 && !compact) {
|
|
140
|
-
lines.push(chalk_1.default.blue
|
|
141
|
-
lines.push(chalk_1.default.blue.bold(`ℹ️ LOW (${bySeverity.LOW.length} issues)`));
|
|
142
|
-
lines.push(chalk_1.default.blue('━'.repeat(50)));
|
|
143
|
-
lines.push('');
|
|
185
|
+
lines.push(generateSeverityHeader('LOW', bySeverity.LOW.length, chalk_1.default.blue));
|
|
144
186
|
for (const finding of bySeverity.LOW) {
|
|
145
|
-
lines.push(
|
|
187
|
+
lines.push(formatEnhancedFinding(finding, showRemediation, compact, chalk_1.default.blue));
|
|
146
188
|
}
|
|
147
189
|
}
|
|
148
190
|
return lines.join('\n');
|
|
149
191
|
}
|
|
150
192
|
/**
|
|
151
|
-
*
|
|
193
|
+
* Generate severity section header
|
|
152
194
|
*/
|
|
153
|
-
function
|
|
195
|
+
function generateSeverityHeader(severity, count, color) {
|
|
154
196
|
const lines = [];
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
lines.push(
|
|
197
|
+
const width = 60;
|
|
198
|
+
const icon = severity === 'CRITICAL' ? '❌' : severity === 'HIGH' ? '⚠️ ' : severity === 'MEDIUM' ? '⚡' : 'ℹ️ ';
|
|
199
|
+
lines.push('');
|
|
200
|
+
lines.push(color(BOX.horizontal.repeat(width)));
|
|
201
|
+
lines.push(color.bold(`${icon} ${severity} (${count} ${count === 1 ? 'issue' : 'issues'})`));
|
|
202
|
+
lines.push(color(BOX.horizontal.repeat(width)));
|
|
203
|
+
lines.push('');
|
|
204
|
+
return lines.join('\n');
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Format a single finding with enhanced visuals
|
|
208
|
+
*/
|
|
209
|
+
function formatEnhancedFinding(finding, showRemediation, compact, color) {
|
|
210
|
+
const lines = [];
|
|
211
|
+
const width = 60;
|
|
212
|
+
// Box header with ID and title
|
|
213
|
+
lines.push(color(`${BOX.topLeft}${BOX.horizontal.repeat(width - 2)}${BOX.topRight}`));
|
|
214
|
+
const title = `${finding.finding_id}: ${finding.title}`;
|
|
215
|
+
const titleLines = wrapText(title, width - 4);
|
|
216
|
+
for (const line of titleLines) {
|
|
217
|
+
lines.push(color(`${BOX.vertical} `) + chalk_1.default.bold.white(line) + ' '.repeat(Math.max(0, width - 3 - line.length)) + color(BOX.vertical));
|
|
218
|
+
}
|
|
219
|
+
// Separator
|
|
220
|
+
lines.push(color(`${BOX.leftT}${BOX.horizontal.repeat(width - 2)}${BOX.rightT}`));
|
|
158
221
|
// Description
|
|
159
222
|
if (!compact) {
|
|
160
|
-
|
|
161
|
-
|
|
223
|
+
const descLines = wrapText(finding.description, width - 4);
|
|
224
|
+
for (const line of descLines) {
|
|
225
|
+
lines.push(color(`${BOX.vertical} `) + chalk_1.default.gray(line) + ' '.repeat(Math.max(0, width - 3 - line.length)) + color(BOX.vertical));
|
|
226
|
+
}
|
|
227
|
+
lines.push(color(`${BOX.vertical}` + ' '.repeat(width - 2) + BOX.vertical));
|
|
162
228
|
}
|
|
229
|
+
// Category badge
|
|
230
|
+
const categoryBadge = `📁 ${finding.category.toUpperCase()}`;
|
|
231
|
+
lines.push(color(`${BOX.vertical} `) + chalk_1.default.cyan(categoryBadge) + ' '.repeat(Math.max(0, width - 3 - categoryBadge.length)) + color(BOX.vertical));
|
|
163
232
|
// Location
|
|
164
233
|
if (finding.location) {
|
|
165
234
|
const locationParts = [];
|
|
166
235
|
if (finding.location.file)
|
|
167
|
-
locationParts.push(finding.location.file);
|
|
236
|
+
locationParts.push(`📄 ${finding.location.file}`);
|
|
168
237
|
if (finding.location.line)
|
|
169
238
|
locationParts.push(`line ${finding.location.line}`);
|
|
170
|
-
if (finding.location.column)
|
|
171
|
-
locationParts.push(`col ${finding.location.column}`);
|
|
172
239
|
if (finding.location.table)
|
|
173
|
-
locationParts.push(
|
|
240
|
+
locationParts.push(`🗃️ ${finding.location.table}`);
|
|
241
|
+
if (finding.location.url)
|
|
242
|
+
locationParts.push(`🌐 ${finding.location.url.substring(0, 30)}...`);
|
|
174
243
|
if (locationParts.length > 0) {
|
|
175
|
-
|
|
244
|
+
const locationText = `📍 ${locationParts.join(' · ')}`;
|
|
245
|
+
const locationLines = wrapText(locationText, width - 4);
|
|
246
|
+
for (const line of locationLines) {
|
|
247
|
+
lines.push(color(`${BOX.vertical} `) + chalk_1.default.gray(line) + ' '.repeat(Math.max(0, width - 3 - line.length)) + color(BOX.vertical));
|
|
248
|
+
}
|
|
176
249
|
}
|
|
177
250
|
}
|
|
178
|
-
// Evidence
|
|
251
|
+
// Evidence
|
|
179
252
|
if (finding.evidence?.sample_data?.masked) {
|
|
180
|
-
|
|
253
|
+
const evidenceText = `🔑 Exposed: ${finding.evidence.sample_data.masked}`;
|
|
254
|
+
lines.push(color(`${BOX.vertical} `) + chalk_1.default.yellow(evidenceText) + ' '.repeat(Math.max(0, width - 3 - evidenceText.length)) + color(BOX.vertical));
|
|
181
255
|
}
|
|
182
256
|
if (finding.evidence?.matched_pattern) {
|
|
183
|
-
|
|
257
|
+
const patternText = `🏷️ Type: ${finding.evidence.matched_pattern}`;
|
|
258
|
+
lines.push(color(`${BOX.vertical} `) + chalk_1.default.gray(patternText) + ' '.repeat(Math.max(0, width - 3 - patternText.length)) + color(BOX.vertical));
|
|
184
259
|
}
|
|
185
260
|
// Impact
|
|
186
261
|
if (!compact && finding.impact) {
|
|
187
|
-
lines.push(
|
|
262
|
+
lines.push(color(`${BOX.vertical}` + ' '.repeat(width - 2) + BOX.vertical));
|
|
263
|
+
const impactLines = wrapText(`💥 Impact: ${finding.impact.description}`, width - 4);
|
|
264
|
+
for (const line of impactLines) {
|
|
265
|
+
lines.push(color(`${BOX.vertical} `) + chalk_1.default.white(line) + ' '.repeat(Math.max(0, width - 3 - line.length)) + color(BOX.vertical));
|
|
266
|
+
}
|
|
188
267
|
}
|
|
189
268
|
// Remediation
|
|
190
269
|
if (showRemediation && finding.remediation) {
|
|
191
|
-
lines.push(
|
|
192
|
-
|
|
270
|
+
lines.push(color(`${BOX.leftT}${BOX.horizontal.repeat(width - 2)}${BOX.rightT}`));
|
|
271
|
+
const fixLines = wrapText(`✅ Fix: ${finding.remediation.summary}`, width - 4);
|
|
272
|
+
for (const line of fixLines) {
|
|
273
|
+
lines.push(color(`${BOX.vertical} `) + chalk_1.default.green(line) + ' '.repeat(Math.max(0, width - 3 - line.length)) + color(BOX.vertical));
|
|
274
|
+
}
|
|
193
275
|
if (!compact && finding.remediation.sql) {
|
|
276
|
+
lines.push(color(`${BOX.vertical}` + ' '.repeat(width - 2) + BOX.vertical));
|
|
277
|
+
lines.push(color(`${BOX.vertical} `) + chalk_1.default.gray('SQL:') + ' '.repeat(Math.max(0, width - 8)) + color(BOX.vertical));
|
|
194
278
|
const sqlLines = finding.remediation.sql.split('\n').slice(0, 5);
|
|
195
|
-
lines.push(`│ ${chalk_1.default.gray('SQL:')}`);
|
|
196
279
|
for (const sqlLine of sqlLines) {
|
|
197
|
-
|
|
280
|
+
const trimmedLine = sqlLine.trim().substring(0, width - 6);
|
|
281
|
+
lines.push(color(`${BOX.vertical} `) + chalk_1.default.gray(trimmedLine) + ' '.repeat(Math.max(0, width - 5 - trimmedLine.length)) + color(BOX.vertical));
|
|
198
282
|
}
|
|
199
283
|
if (finding.remediation.sql.split('\n').length > 5) {
|
|
200
|
-
lines.push(
|
|
284
|
+
lines.push(color(`${BOX.vertical} `) + chalk_1.default.gray('...') + ' '.repeat(Math.max(0, width - 8)) + color(BOX.vertical));
|
|
201
285
|
}
|
|
202
286
|
}
|
|
203
287
|
}
|
|
204
|
-
|
|
288
|
+
// Box footer
|
|
289
|
+
lines.push(color(`${BOX.bottomLeft}${BOX.horizontal.repeat(width - 2)}${BOX.bottomRight}`));
|
|
205
290
|
lines.push('');
|
|
206
291
|
return lines.join('\n');
|
|
207
292
|
}
|
|
208
293
|
/**
|
|
209
|
-
*
|
|
294
|
+
* Wrap text to fit within a width
|
|
210
295
|
*/
|
|
211
|
-
function
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
296
|
+
function wrapText(text, width) {
|
|
297
|
+
const words = text.split(' ');
|
|
298
|
+
const lines = [];
|
|
299
|
+
let currentLine = '';
|
|
300
|
+
for (const word of words) {
|
|
301
|
+
if ((currentLine + word).length > width) {
|
|
302
|
+
if (currentLine) {
|
|
303
|
+
lines.push(currentLine.trim());
|
|
304
|
+
currentLine = '';
|
|
305
|
+
}
|
|
306
|
+
// If a single word is longer than width, split it
|
|
307
|
+
if (word.length > width) {
|
|
308
|
+
for (let i = 0; i < word.length; i += width) {
|
|
309
|
+
lines.push(word.substring(i, i + width));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
currentLine = word + ' ';
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
currentLine += word + ' ';
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (currentLine) {
|
|
321
|
+
lines.push(currentLine.trim());
|
|
225
322
|
}
|
|
323
|
+
return lines;
|
|
226
324
|
}
|
|
227
325
|
/**
|
|
228
|
-
* Generate passed checks section
|
|
326
|
+
* Generate enhanced passed checks section
|
|
229
327
|
*/
|
|
230
|
-
function
|
|
328
|
+
function generateEnhancedPassedSection(passedChecks, compact) {
|
|
231
329
|
const lines = [];
|
|
232
|
-
|
|
233
|
-
lines.push(
|
|
234
|
-
lines.push(chalk_1.default.green(
|
|
330
|
+
const width = 60;
|
|
331
|
+
lines.push('');
|
|
332
|
+
lines.push(chalk_1.default.green(BOX.horizontal.repeat(width)));
|
|
333
|
+
lines.push(chalk_1.default.green.bold(`✅ PASSED CHECKS (${passedChecks.length})`));
|
|
334
|
+
lines.push(chalk_1.default.green(BOX.horizontal.repeat(width)));
|
|
235
335
|
lines.push('');
|
|
236
336
|
if (compact) {
|
|
237
337
|
const checkNames = passedChecks.map(c => c.title).join(', ');
|
|
238
|
-
|
|
338
|
+
const wrapped = wrapText(checkNames, width - 4);
|
|
339
|
+
for (const line of wrapped) {
|
|
340
|
+
lines.push(' ' + chalk_1.default.gray(line));
|
|
341
|
+
}
|
|
239
342
|
}
|
|
240
343
|
else {
|
|
241
344
|
for (const check of passedChecks.slice(0, 10)) {
|
|
242
345
|
lines.push(chalk_1.default.green(` ✓ ${check.title}`));
|
|
243
346
|
lines.push(chalk_1.default.gray(` ${check.description}`));
|
|
347
|
+
lines.push('');
|
|
244
348
|
}
|
|
245
349
|
if (passedChecks.length > 10) {
|
|
246
|
-
lines.push(chalk_1.default.gray(` ... and ${passedChecks.length - 10} more`));
|
|
350
|
+
lines.push(chalk_1.default.gray(` ... and ${passedChecks.length - 10} more passed checks`));
|
|
247
351
|
}
|
|
248
352
|
}
|
|
249
353
|
lines.push('');
|
|
250
354
|
return lines.join('\n');
|
|
251
355
|
}
|
|
252
356
|
/**
|
|
253
|
-
* Generate grade section
|
|
357
|
+
* Generate enhanced grade section
|
|
254
358
|
*/
|
|
255
|
-
function
|
|
359
|
+
function generateEnhancedGradeSection(result) {
|
|
256
360
|
const lines = [];
|
|
361
|
+
const width = 60;
|
|
257
362
|
const grade = result.grading.overall_grade;
|
|
258
363
|
const score = result.grading.overall_score;
|
|
259
|
-
lines.push(chalk_1.default.gray(
|
|
364
|
+
lines.push(chalk_1.default.gray(BOX.horizontal.repeat(width)));
|
|
260
365
|
lines.push(chalk_1.default.bold('📈 SECURITY GRADE'));
|
|
261
|
-
lines.push(chalk_1.default.gray(
|
|
366
|
+
lines.push(chalk_1.default.gray(BOX.horizontal.repeat(width)));
|
|
262
367
|
lines.push('');
|
|
263
|
-
//
|
|
368
|
+
// Large grade display
|
|
264
369
|
const gradeColor = getGradeColor(grade);
|
|
265
|
-
const
|
|
266
|
-
lines.push(
|
|
267
|
-
lines.push(` ${
|
|
370
|
+
const gradeArt = generateGradeArt(grade);
|
|
371
|
+
lines.push(gradeArt);
|
|
372
|
+
lines.push(` ${gradeColor.bold(`Grade ${grade}`)} - Score: ${score}/100`);
|
|
373
|
+
lines.push(` ${chalk_1.default.gray(getGradeMessage(grade))}`);
|
|
268
374
|
lines.push('');
|
|
269
|
-
// Category scores
|
|
375
|
+
// Category scores table
|
|
270
376
|
const categories = Object.entries(result.grading.category_scores);
|
|
271
377
|
if (categories.length > 0) {
|
|
272
378
|
lines.push(chalk_1.default.gray(' Category Breakdown:'));
|
|
379
|
+
lines.push(' ' + chalk_1.default.gray('─'.repeat(40)));
|
|
273
380
|
for (const [category, scores] of categories) {
|
|
274
381
|
const catColor = scores.grade === 'A' || scores.grade === 'B' ? chalk_1.default.green :
|
|
275
382
|
scores.grade === 'C' ? chalk_1.default.yellow :
|
|
276
383
|
scores.grade === 'D' ? chalk_1.default.hex('#FFA500') : chalk_1.default.red;
|
|
277
|
-
|
|
384
|
+
const categoryPadded = category.charAt(0).toUpperCase() + category.slice(1).padEnd(12);
|
|
385
|
+
const scoreBar = '█'.repeat(Math.round(scores.score / 10)) + '░'.repeat(10 - Math.round(scores.score / 10));
|
|
386
|
+
lines.push(` ${categoryPadded} ${catColor(scoreBar)} ${catColor(`${scores.grade} (${scores.score})`)}`);
|
|
278
387
|
}
|
|
279
388
|
lines.push('');
|
|
280
389
|
}
|
|
281
390
|
return lines.join('\n');
|
|
282
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Generate ASCII art for grade
|
|
394
|
+
*/
|
|
395
|
+
function generateGradeArt(grade) {
|
|
396
|
+
const color = getGradeColor(grade);
|
|
397
|
+
const art = {
|
|
398
|
+
'A': `
|
|
399
|
+
█████
|
|
400
|
+
██ ██
|
|
401
|
+
███████
|
|
402
|
+
██ ██
|
|
403
|
+
██ ██
|
|
404
|
+
`,
|
|
405
|
+
'B': `
|
|
406
|
+
██████
|
|
407
|
+
██ ██
|
|
408
|
+
██████
|
|
409
|
+
██ ██
|
|
410
|
+
██████
|
|
411
|
+
`,
|
|
412
|
+
'C': `
|
|
413
|
+
██████
|
|
414
|
+
██
|
|
415
|
+
██
|
|
416
|
+
██
|
|
417
|
+
██████
|
|
418
|
+
`,
|
|
419
|
+
'D': `
|
|
420
|
+
██████
|
|
421
|
+
██ ██
|
|
422
|
+
██ ██
|
|
423
|
+
██ ██
|
|
424
|
+
██████
|
|
425
|
+
`,
|
|
426
|
+
'F': `
|
|
427
|
+
███████
|
|
428
|
+
██
|
|
429
|
+
█████
|
|
430
|
+
██
|
|
431
|
+
██
|
|
432
|
+
`
|
|
433
|
+
};
|
|
434
|
+
return color(art[grade] || art['F']);
|
|
435
|
+
}
|
|
283
436
|
/**
|
|
284
437
|
* Get color for grade
|
|
285
438
|
*/
|
|
@@ -304,44 +457,52 @@ function getGradeColor(grade) {
|
|
|
304
457
|
*/
|
|
305
458
|
function getGradeMessage(grade) {
|
|
306
459
|
const messages = {
|
|
307
|
-
'A': 'Excellent security posture!',
|
|
308
|
-
'B': 'Good security, minor improvements possible.',
|
|
309
|
-
'C': 'Average security, some issues need attention.',
|
|
310
|
-
'D': 'Below average - serious issues found.',
|
|
311
|
-
'F': 'Critical vulnerabilities detected!'
|
|
460
|
+
'A': 'Excellent security posture! 🎉',
|
|
461
|
+
'B': 'Good security, minor improvements possible. 👍',
|
|
462
|
+
'C': 'Average security, some issues need attention. ⚠️',
|
|
463
|
+
'D': 'Below average - serious issues found. ❗',
|
|
464
|
+
'F': 'Critical vulnerabilities detected! 🚨'
|
|
312
465
|
};
|
|
313
466
|
return messages[grade] || 'Review findings for details.';
|
|
314
467
|
}
|
|
315
468
|
/**
|
|
316
|
-
* Generate actions section
|
|
469
|
+
* Generate enhanced actions section
|
|
317
470
|
*/
|
|
318
|
-
function
|
|
471
|
+
function generateEnhancedActionsSection(result) {
|
|
319
472
|
const lines = [];
|
|
320
|
-
|
|
473
|
+
const width = 60;
|
|
474
|
+
lines.push(chalk_1.default.gray(BOX.horizontal.repeat(width)));
|
|
321
475
|
lines.push(chalk_1.default.bold('🛠️ QUICK ACTIONS'));
|
|
322
|
-
lines.push(chalk_1.default.gray(
|
|
476
|
+
lines.push(chalk_1.default.gray(BOX.horizontal.repeat(width)));
|
|
323
477
|
lines.push('');
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
lines.push(chalk_1.default.
|
|
478
|
+
const hasCritical = result.findings.some(f => f.severity === 'CRITICAL' || f.severity === 'HIGH');
|
|
479
|
+
if (hasCritical) {
|
|
480
|
+
lines.push(chalk_1.default.yellow(' 🚨 Fix critical issues immediately:'));
|
|
481
|
+
lines.push(chalk_1.default.cyan(' $ supasec fix --interactive'));
|
|
327
482
|
lines.push('');
|
|
328
483
|
}
|
|
329
|
-
lines.push(chalk_1.default.gray('
|
|
330
|
-
lines.push(chalk_1.default.cyan('
|
|
484
|
+
lines.push(chalk_1.default.gray(' 📄 Generate detailed HTML report:'));
|
|
485
|
+
lines.push(chalk_1.default.cyan(' $ supasec scan <url> --format html --output report.html'));
|
|
486
|
+
lines.push('');
|
|
487
|
+
lines.push(chalk_1.default.gray(' 📊 Export for CI/CD pipeline:'));
|
|
488
|
+
lines.push(chalk_1.default.cyan(' $ supasec scan <url> --format json --output audit.json'));
|
|
331
489
|
lines.push('');
|
|
332
|
-
lines.push(chalk_1.default.gray('
|
|
333
|
-
lines.push(chalk_1.default.cyan('
|
|
490
|
+
lines.push(chalk_1.default.gray(' 💾 Save snapshot for comparison:'));
|
|
491
|
+
lines.push(chalk_1.default.cyan(' $ supasec snapshot save <url>'));
|
|
334
492
|
lines.push('');
|
|
335
493
|
return lines.join('\n');
|
|
336
494
|
}
|
|
337
495
|
/**
|
|
338
|
-
* Generate footer
|
|
496
|
+
* Generate enhanced footer
|
|
339
497
|
*/
|
|
340
|
-
function
|
|
498
|
+
function generateEnhancedFooter(result) {
|
|
341
499
|
const lines = [];
|
|
342
|
-
|
|
343
|
-
lines.push(chalk_1.default.gray(
|
|
344
|
-
|
|
500
|
+
const width = 60;
|
|
501
|
+
lines.push(chalk_1.default.gray(BOX.horizontal.repeat(width)));
|
|
502
|
+
const reportFile = `./supasec-report-${result.scan_metadata.scan_id}.json`;
|
|
503
|
+
lines.push(chalk_1.default.gray(` 📄 Report saved: ${reportFile}`));
|
|
504
|
+
lines.push(chalk_1.default.gray(` ⏱️ Duration: ${result.scan_metadata.scan_duration_seconds.toFixed(1)}s`));
|
|
505
|
+
lines.push(chalk_1.default.gray(` 🔗 Documentation: https://github.com/Interpoolx/supasec`));
|
|
345
506
|
lines.push('');
|
|
346
507
|
return lines.join('\n');
|
|
347
508
|
}
|