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.
Files changed (117) hide show
  1. package/Feature-List.md +233 -0
  2. package/README.md +53 -12
  3. package/dist/cli.js +2 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/index.d.ts +1 -0
  6. package/dist/commands/index.d.ts.map +1 -1
  7. package/dist/commands/index.js +1 -0
  8. package/dist/commands/index.js.map +1 -1
  9. package/dist/commands/scan.d.ts.map +1 -1
  10. package/dist/commands/scan.js +82 -26
  11. package/dist/commands/scan.js.map +1 -1
  12. package/dist/commands/snapshot.d.ts +32 -0
  13. package/dist/commands/snapshot.d.ts.map +1 -0
  14. package/dist/commands/snapshot.js +282 -0
  15. package/dist/commands/snapshot.js.map +1 -0
  16. package/dist/reporters/html.d.ts +3 -2
  17. package/dist/reporters/html.d.ts.map +1 -1
  18. package/dist/reporters/html.js +844 -538
  19. package/dist/reporters/html.js.map +1 -1
  20. package/dist/reporters/terminal.d.ts +38 -2
  21. package/dist/reporters/terminal.d.ts.map +1 -1
  22. package/dist/reporters/terminal.js +292 -131
  23. package/dist/reporters/terminal.js.map +1 -1
  24. package/dist/scanners/auth/analyzer.d.ts +40 -0
  25. package/dist/scanners/auth/analyzer.d.ts.map +1 -0
  26. package/dist/scanners/auth/analyzer.js +673 -0
  27. package/dist/scanners/auth/analyzer.js.map +1 -0
  28. package/dist/scanners/auth/index.d.ts +6 -0
  29. package/dist/scanners/auth/index.d.ts.map +1 -0
  30. package/dist/scanners/auth/index.js +22 -0
  31. package/dist/scanners/auth/index.js.map +1 -0
  32. package/dist/scanners/edge/analyzer.d.ts +35 -0
  33. package/dist/scanners/edge/analyzer.d.ts.map +1 -0
  34. package/dist/scanners/edge/analyzer.js +614 -0
  35. package/dist/scanners/edge/analyzer.js.map +1 -0
  36. package/dist/scanners/edge/index.d.ts +6 -0
  37. package/dist/scanners/edge/index.d.ts.map +1 -0
  38. package/dist/scanners/edge/index.js +22 -0
  39. package/dist/scanners/edge/index.js.map +1 -0
  40. package/dist/scanners/functions/analyzer.d.ts +41 -0
  41. package/dist/scanners/functions/analyzer.d.ts.map +1 -0
  42. package/dist/scanners/functions/analyzer.js +378 -0
  43. package/dist/scanners/functions/analyzer.js.map +1 -0
  44. package/dist/scanners/functions/index.d.ts +6 -0
  45. package/dist/scanners/functions/index.d.ts.map +1 -0
  46. package/dist/scanners/functions/index.js +22 -0
  47. package/dist/scanners/functions/index.js.map +1 -0
  48. package/dist/scanners/git/index.d.ts +6 -0
  49. package/dist/scanners/git/index.d.ts.map +1 -0
  50. package/dist/scanners/git/index.js +22 -0
  51. package/dist/scanners/git/index.js.map +1 -0
  52. package/dist/scanners/git/scanner.d.ts +22 -0
  53. package/dist/scanners/git/scanner.d.ts.map +1 -0
  54. package/dist/scanners/git/scanner.js +531 -0
  55. package/dist/scanners/git/scanner.js.map +1 -0
  56. package/dist/scanners/https/analyzer.d.ts +42 -0
  57. package/dist/scanners/https/analyzer.d.ts.map +1 -0
  58. package/dist/scanners/https/analyzer.js +470 -0
  59. package/dist/scanners/https/analyzer.js.map +1 -0
  60. package/dist/scanners/https/index.d.ts +8 -0
  61. package/dist/scanners/https/index.d.ts.map +1 -0
  62. package/dist/scanners/https/index.js +17 -0
  63. package/dist/scanners/https/index.js.map +1 -0
  64. package/dist/scanners/index.d.ts +6 -0
  65. package/dist/scanners/index.d.ts.map +1 -1
  66. package/dist/scanners/index.js +6 -0
  67. package/dist/scanners/index.js.map +1 -1
  68. package/dist/scanners/rls/fuzzer.d.ts +40 -0
  69. package/dist/scanners/rls/fuzzer.d.ts.map +1 -0
  70. package/dist/scanners/rls/fuzzer.js +360 -0
  71. package/dist/scanners/rls/fuzzer.js.map +1 -0
  72. package/dist/scanners/rls/index.d.ts +1 -0
  73. package/dist/scanners/rls/index.d.ts.map +1 -1
  74. package/dist/scanners/rls/index.js +1 -0
  75. package/dist/scanners/rls/index.js.map +1 -1
  76. package/dist/scanners/secrets/detector.d.ts.map +1 -1
  77. package/dist/scanners/secrets/detector.js +44 -12
  78. package/dist/scanners/secrets/detector.js.map +1 -1
  79. package/dist/scanners/secrets/index.d.ts +1 -0
  80. package/dist/scanners/secrets/index.d.ts.map +1 -1
  81. package/dist/scanners/secrets/index.js +4 -0
  82. package/dist/scanners/secrets/index.js.map +1 -1
  83. package/dist/scanners/secrets/patterns.d.ts +25 -0
  84. package/dist/scanners/secrets/patterns.d.ts.map +1 -1
  85. package/dist/scanners/secrets/patterns.js +138 -27
  86. package/dist/scanners/secrets/patterns.js.map +1 -1
  87. package/dist/scanners/storage/analyzer.d.ts +49 -0
  88. package/dist/scanners/storage/analyzer.d.ts.map +1 -0
  89. package/dist/scanners/storage/analyzer.js +438 -0
  90. package/dist/scanners/storage/analyzer.js.map +1 -0
  91. package/dist/scanners/storage/index.d.ts +6 -0
  92. package/dist/scanners/storage/index.d.ts.map +1 -0
  93. package/dist/scanners/storage/index.js +22 -0
  94. package/dist/scanners/storage/index.js.map +1 -0
  95. package/package.json +1 -1
  96. package/reports/{supasec-audityour-app-2026-01-28-17-09-24.html → supasec-audityour-app-2026-01-28-19-42-22.html} +51 -16
  97. package/reports/supasec-audityour-app-2026-01-28-19-49-18.html +1122 -0
  98. package/COMPLETION_REPORT.md +0 -324
  99. package/FIXES_SUMMARY.md +0 -224
  100. package/IMPLEMENTATION_NOTES.md +0 -305
  101. package/QUICK_REFERENCE.md +0 -185
  102. package/REPORTING.md +0 -217
  103. package/STATUS.md +0 -269
  104. package/reports/supasec---------app-2026-01-28-16-58-47.html +0 -804
  105. package/reports/supasec---------app-2026-01-28-17-06-43.html +0 -722
  106. package/reports/supasec---------app-2026-01-28-17-07-23.html +0 -722
  107. package/reports/supasec---------app-2026-01-28-17-08-00.html +0 -722
  108. package/reports/supasec---------app-2026-01-28-17-08-20.html +0 -722
  109. package/reports/supasec---------app-2026-01-28-17-08-41.html +0 -722
  110. package/reports/supasec-au---your-app-2026-01-28-17-14-57.html +0 -715
  111. package/reports/supasec-au---your-app-2026-01-28-17-19-03.html +0 -715
  112. package/reports/supasec-ex-mple-com-2026-01-28-17-14-52.json +0 -229
  113. package/reports/supasec-ex-mple-com-2026-01-28-17-15-39.html +0 -715
  114. package/reports/supasec-ex-mple-com-2026-01-28-17-17-22.html +0 -715
  115. package/reports/supasec-example-com-2026-01-28-17-15-06.html +0 -715
  116. package/reports/supasec-my--------------name-com-2026-01-28-17-15-02.html +0 -715
  117. 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(generateHeader(result));
26
- // Summary
27
- lines.push(generateSummary(result));
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(generateFindingsSection(result.findings, showRemediation, compact));
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(generatePassedSection(result.passed_checks, compact));
55
+ lines.push(generateEnhancedPassedSection(result.passed_checks, compact));
35
56
  }
36
- // Grade
37
- lines.push(generateGradeSection(result));
57
+ // Grade with visual indicator
58
+ lines.push(generateEnhancedGradeSection(result));
38
59
  // Quick actions
39
- lines.push(generateActionsSection(result));
60
+ lines.push(generateEnhancedActionsSection(result));
40
61
  // Footer
41
- lines.push(generateFooter(result));
62
+ lines.push(generateEnhancedFooter(result));
42
63
  return lines.join('\n');
43
64
  }
44
65
  /**
45
- * Generate header section
66
+ * Generate banner header
46
67
  */
47
- function generateHeader(result) {
68
+ function generateBanner(result) {
48
69
  const lines = [];
49
- lines.push('');
50
- lines.push(chalk_1.default.cyan('🔍 SupaSec - Supabase Security Audit v1.0.0'));
51
- lines.push(chalk_1.default.gray('━'.repeat(50)));
52
- lines.push('');
53
- lines.push(`${chalk_1.default.bold('🎯 Target:')} ${result.scan_metadata.target_url}`);
54
- lines.push(`${chalk_1.default.bold('⏱️ Started:')} ${result.scan_metadata.scan_date}`);
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.green(`✓ Detected Supabase project`));
58
- lines.push(chalk_1.default.gray(` Found ${result.project_info.tables_count} tables, ${result.project_info.rpcs_count} RPCs, ${result.project_info.storage_buckets} storage buckets`));
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 section
103
+ * Generate enhanced summary with progress bars
65
104
  */
66
- function generateSummary(result) {
105
+ function generateEnhancedSummary(result) {
67
106
  const lines = [];
68
107
  const counts = (0, finding_js_1.countFindingsBySeverity)(result.findings);
69
- lines.push(chalk_1.default.gray('━'.repeat(50)));
108
+ const width = 60;
70
109
  lines.push(chalk_1.default.bold('📊 SCAN SUMMARY'));
71
- lines.push(chalk_1.default.gray('━'.repeat(50)));
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
- lines.push(chalk_1.default.red(`❌ CRITICAL: ${counts.CRITICAL} issues`));
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
- lines.push(chalk_1.default.yellow(`⚠️ HIGH: ${counts.HIGH} issues`));
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
- lines.push(chalk_1.default.hex('#FFA500')(`⚡ MEDIUM: ${counts.MEDIUM} issues`));
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
- lines.push(chalk_1.default.blue(`ℹ️ LOW: ${counts.LOW} issues`));
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 generateFindingsSection(findings, showRemediation, compact) {
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('━'.repeat(50)));
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(formatFinding(finding, showRemediation, compact));
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('━'.repeat(50)));
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(formatFinding(finding, showRemediation, compact));
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')('━'.repeat(50)));
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(formatFinding(finding, showRemediation, compact));
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('━'.repeat(50)));
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(formatFinding(finding, showRemediation, compact));
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
- * Format a single finding
193
+ * Generate severity section header
152
194
  */
153
- function formatFinding(finding, showRemediation, compact) {
195
+ function generateSeverityHeader(severity, count, color) {
154
196
  const lines = [];
155
- // Header with ID and title
156
- const severityColor = getSeverityColor(finding.severity);
157
- lines.push(severityColor(`┌─ ${finding.finding_id}: ${finding.title}`));
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
- lines.push(`│ ${chalk_1.default.gray(finding.description)}`);
161
- lines.push('│');
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(`table ${finding.location.table}`);
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
- lines.push(`│ ${chalk_1.default.cyan('Location:')} ${locationParts.join(', ')}`);
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 (exposed key, etc.)
251
+ // Evidence
179
252
  if (finding.evidence?.sample_data?.masked) {
180
- lines.push(`│ ${chalk_1.default.cyan('Exposed Key:')} ${finding.evidence.sample_data.masked}`);
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
- lines.push(`│ ${chalk_1.default.cyan('Key Type:')} ${finding.evidence.matched_pattern}`);
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(`│ ${chalk_1.default.cyan('Impact:')} ${finding.impact.description}`);
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
- lines.push(`│ ${chalk_1.default.green('Fix:')} ${finding.remediation.summary}`);
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
- lines.push(`│ ${chalk_1.default.gray(sqlLine)}`);
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(`│ ${chalk_1.default.gray('...')}`);
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
- lines.push(severityColor('└'));
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
- * Get color for severity
294
+ * Wrap text to fit within a width
210
295
  */
211
- function getSeverityColor(severity) {
212
- switch (severity) {
213
- case 'CRITICAL':
214
- return chalk_1.default.red;
215
- case 'HIGH':
216
- return chalk_1.default.yellow;
217
- case 'MEDIUM':
218
- return chalk_1.default.hex('#FFA500');
219
- case 'LOW':
220
- return chalk_1.default.blue;
221
- case 'INFO':
222
- return chalk_1.default.gray;
223
- default:
224
- return chalk_1.default.white;
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 generatePassedSection(passedChecks, compact) {
328
+ function generateEnhancedPassedSection(passedChecks, compact) {
231
329
  const lines = [];
232
- lines.push(chalk_1.default.green('━'.repeat(50)));
233
- lines.push(chalk_1.default.green.bold(`✅ PASSED (${passedChecks.length} checks)`));
234
- lines.push(chalk_1.default.green('━'.repeat(50)));
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
- lines.push(chalk_1.default.gray(checkNames));
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 generateGradeSection(result) {
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('━'.repeat(50)));
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('━'.repeat(50)));
366
+ lines.push(chalk_1.default.gray(BOX.horizontal.repeat(width)));
262
367
  lines.push('');
263
- // Grade badge
368
+ // Large grade display
264
369
  const gradeColor = getGradeColor(grade);
265
- const gradeMessage = getGradeMessage(grade);
266
- lines.push(` ${gradeColor.bold(`Grade ${grade}`)} - ${score}/100`);
267
- lines.push(` ${gradeMessage}`);
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 if available
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
- lines.push(` ${category.padEnd(12)} ${catColor(`${scores.grade} (${scores.score})`)}`);
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 generateActionsSection(result) {
471
+ function generateEnhancedActionsSection(result) {
319
472
  const lines = [];
320
- lines.push(chalk_1.default.gray('━'.repeat(50)));
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('━'.repeat(50)));
476
+ lines.push(chalk_1.default.gray(BOX.horizontal.repeat(width)));
323
477
  lines.push('');
324
- if (result.findings.some(f => f.severity === 'CRITICAL' || f.severity === 'HIGH')) {
325
- lines.push(chalk_1.default.yellow('Fix critical issues now:'));
326
- lines.push(chalk_1.default.cyan(' $ supasec fix --interactive'));
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('View detailed report:'));
330
- lines.push(chalk_1.default.cyan(' $ supasec report --format html --output report.html'));
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('Export for CI/CD:'));
333
- lines.push(chalk_1.default.cyan(' $ supasec scan --format json --output audit.json'));
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 section
496
+ * Generate enhanced footer
339
497
  */
340
- function generateFooter(result) {
498
+ function generateEnhancedFooter(result) {
341
499
  const lines = [];
342
- lines.push(chalk_1.default.gray('━'.repeat(50)));
343
- lines.push(chalk_1.default.gray(`📄 Full report saved: ./supasec-report-${result.scan_metadata.scan_id}.json`));
344
- lines.push(chalk_1.default.gray(`⏱️ Scan completed in ${result.scan_metadata.scan_duration_seconds.toFixed(1)}s`));
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
  }