@spektre/veil 1.0.0 → 1.0.2
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/dist/analysis/analysisModal.d.ts +1 -0
- package/dist/analysis/sourceMapAnalyzer.d.ts +40 -0
- package/dist/index.esm.js +542 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +542 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function openAnalysisModal(): Promise<void>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
interface CodeIssue {
|
|
2
|
+
type: string;
|
|
3
|
+
severity: 'critical' | 'high' | 'medium' | 'low';
|
|
4
|
+
file: string;
|
|
5
|
+
line: number;
|
|
6
|
+
column: number;
|
|
7
|
+
message: string;
|
|
8
|
+
code: string;
|
|
9
|
+
}
|
|
10
|
+
interface AnalysisResult {
|
|
11
|
+
timestamp: string;
|
|
12
|
+
totalIssuesFound: number;
|
|
13
|
+
criticalCount: number;
|
|
14
|
+
highCount: number;
|
|
15
|
+
mediumCount: number;
|
|
16
|
+
lowCount: number;
|
|
17
|
+
issues: CodeIssue[];
|
|
18
|
+
summary: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Fetch and parse the source map
|
|
22
|
+
*/
|
|
23
|
+
export declare function fetchSourceMap(sourceMapUrl: string): Promise<any>;
|
|
24
|
+
/**
|
|
25
|
+
* Extract all source code from source map
|
|
26
|
+
*/
|
|
27
|
+
export declare function extractAllSourceCode(sourceMap: any): Promise<Map<string, string>>;
|
|
28
|
+
/**
|
|
29
|
+
* Analyze source code for security issues
|
|
30
|
+
*/
|
|
31
|
+
export declare function analyzeSourceCode(sourceCode: string, fileName: string): CodeIssue[];
|
|
32
|
+
/**
|
|
33
|
+
* Generate full security analysis of all source code
|
|
34
|
+
*/
|
|
35
|
+
export declare function generateSecurityAnalysis(sourceMapUrl: string): Promise<AnalysisResult>;
|
|
36
|
+
/**
|
|
37
|
+
* Format analysis for display
|
|
38
|
+
*/
|
|
39
|
+
export declare function formatAnalysisForDisplay(analysis: AnalysisResult): string;
|
|
40
|
+
export type { AnalysisResult, CodeIssue };
|
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,225 @@
|
|
|
1
1
|
import React, { createContext, useState, useEffect, useContext } from 'react';
|
|
2
2
|
|
|
3
|
+
// src/analysis/sourceMapAnalyzer.ts
|
|
4
|
+
// Reads all code via source mapping and generates security analysis
|
|
5
|
+
let cachedSourceMap = null;
|
|
6
|
+
let cachedSourceCode = new Map();
|
|
7
|
+
/**
|
|
8
|
+
* Fetch and parse the source map
|
|
9
|
+
*/
|
|
10
|
+
async function fetchSourceMap(sourceMapUrl) {
|
|
11
|
+
if (cachedSourceMap) {
|
|
12
|
+
return cachedSourceMap;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const response = await fetch(sourceMapUrl);
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
console.warn('[Spektre] Could not fetch source map:', sourceMapUrl);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
cachedSourceMap = await response.json();
|
|
21
|
+
return cachedSourceMap;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.warn('[Spektre] Source map parsing failed:', error);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Extract all source code from source map
|
|
30
|
+
*/
|
|
31
|
+
async function extractAllSourceCode(sourceMap) {
|
|
32
|
+
if (cachedSourceCode.size > 0) {
|
|
33
|
+
return cachedSourceCode;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
if (!sourceMap || !sourceMap.sourcesContent) {
|
|
37
|
+
return cachedSourceCode;
|
|
38
|
+
}
|
|
39
|
+
sourceMap.sources.forEach((file, index) => {
|
|
40
|
+
const content = sourceMap.sourcesContent[index];
|
|
41
|
+
if (content) {
|
|
42
|
+
cachedSourceCode.set(file, content);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return cachedSourceCode;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.warn('[Spektre] Error extracting source code:', error);
|
|
49
|
+
return cachedSourceCode;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Security analysis patterns to check
|
|
54
|
+
*/
|
|
55
|
+
const SECURITY_PATTERNS = [
|
|
56
|
+
{
|
|
57
|
+
name: 'eval() usage',
|
|
58
|
+
pattern: /\beval\s*\(/g,
|
|
59
|
+
severity: 'critical',
|
|
60
|
+
message: 'eval() is extremely dangerous and should never be used',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'innerHTML assignment',
|
|
64
|
+
pattern: /\.innerHTML\s*=/g,
|
|
65
|
+
severity: 'high',
|
|
66
|
+
message: 'Direct innerHTML assignment can lead to XSS vulnerabilities',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'dangerouslySetInnerHTML',
|
|
70
|
+
pattern: /dangerouslySetInnerHTML/g,
|
|
71
|
+
severity: 'high',
|
|
72
|
+
message: 'dangerouslySetInnerHTML should only be used with sanitized content',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'document.write()',
|
|
76
|
+
pattern: /document\.write\s*\(/g,
|
|
77
|
+
severity: 'medium',
|
|
78
|
+
message: 'document.write() can overwrite the entire document',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'setTimeout with string',
|
|
82
|
+
pattern: /setTimeout\s*\(\s*['"]/g,
|
|
83
|
+
severity: 'high',
|
|
84
|
+
message: 'Passing strings to setTimeout is equivalent to eval()',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'Hardcoded API key',
|
|
88
|
+
pattern: /(?:api[_-]?key|token|password|secret)\s*[:=]\s*['"](.*?)['"]/gi,
|
|
89
|
+
severity: 'critical',
|
|
90
|
+
message: 'Hardcoded credentials found in code',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'No input validation',
|
|
94
|
+
pattern: /innerHTML\s*=\s*(?:userInput|params|query|request)/gi,
|
|
95
|
+
severity: 'high',
|
|
96
|
+
message: 'Unvalidated user input assigned to DOM',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'SQL injection risk',
|
|
100
|
+
pattern: /query\s*=\s*['"]\s*\+|query\s*=\s*`.*\$/gi,
|
|
101
|
+
severity: 'high',
|
|
102
|
+
message: 'String concatenation detected in database query',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'Unsafe regex',
|
|
106
|
+
pattern: /RegExp\s*\(\s*['"].*\*.*['"]\s*\)/g,
|
|
107
|
+
severity: 'medium',
|
|
108
|
+
message: 'Unsafe regex pattern could cause ReDoS attacks',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'Missing HTTPS',
|
|
112
|
+
pattern: /http:\/\/(?!localhost)/gi,
|
|
113
|
+
severity: 'high',
|
|
114
|
+
message: 'Non-HTTPS URL detected',
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
/**
|
|
118
|
+
* Analyze source code for security issues
|
|
119
|
+
*/
|
|
120
|
+
function analyzeSourceCode(sourceCode, fileName) {
|
|
121
|
+
const issues = [];
|
|
122
|
+
const lines = sourceCode.split('\n');
|
|
123
|
+
SECURITY_PATTERNS.forEach(({ name, pattern, severity, message }) => {
|
|
124
|
+
let match;
|
|
125
|
+
const patternWithIndices = new RegExp(pattern.source, pattern.flags + 'g');
|
|
126
|
+
while ((match = patternWithIndices.exec(sourceCode)) !== null) {
|
|
127
|
+
// Calculate line number from string position
|
|
128
|
+
const lineNumber = sourceCode.substring(0, match.index).split('\n').length;
|
|
129
|
+
const lineContent = lines[lineNumber - 1] || '';
|
|
130
|
+
const columnNumber = match.index - sourceCode.lastIndexOf('\n', match.index);
|
|
131
|
+
// Avoid duplicate issues on same line
|
|
132
|
+
const isDuplicate = issues.some(issue => issue.file === fileName &&
|
|
133
|
+
issue.line === lineNumber &&
|
|
134
|
+
issue.type === name);
|
|
135
|
+
if (!isDuplicate) {
|
|
136
|
+
issues.push({
|
|
137
|
+
type: name,
|
|
138
|
+
severity,
|
|
139
|
+
file: fileName,
|
|
140
|
+
line: lineNumber,
|
|
141
|
+
column: columnNumber,
|
|
142
|
+
message,
|
|
143
|
+
code: lineContent.trim(),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
return issues;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Generate full security analysis of all source code
|
|
152
|
+
*/
|
|
153
|
+
async function generateSecurityAnalysis(sourceMapUrl) {
|
|
154
|
+
const sourceMap = await fetchSourceMap(sourceMapUrl);
|
|
155
|
+
if (!sourceMap) {
|
|
156
|
+
return {
|
|
157
|
+
timestamp: new Date().toISOString(),
|
|
158
|
+
totalIssuesFound: 0,
|
|
159
|
+
criticalCount: 0,
|
|
160
|
+
highCount: 0,
|
|
161
|
+
mediumCount: 0,
|
|
162
|
+
lowCount: 0,
|
|
163
|
+
issues: [],
|
|
164
|
+
summary: 'Could not load source map for analysis.',
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const sourceCode = await extractAllSourceCode(sourceMap);
|
|
168
|
+
const allIssues = [];
|
|
169
|
+
// Analyze each source file
|
|
170
|
+
sourceCode.forEach((code, fileName) => {
|
|
171
|
+
const issues = analyzeSourceCode(code, fileName);
|
|
172
|
+
allIssues.push(...issues);
|
|
173
|
+
});
|
|
174
|
+
// Sort by severity
|
|
175
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
176
|
+
allIssues.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
177
|
+
// Count issues by severity
|
|
178
|
+
const criticalCount = allIssues.filter(i => i.severity === 'critical').length;
|
|
179
|
+
const highCount = allIssues.filter(i => i.severity === 'high').length;
|
|
180
|
+
const mediumCount = allIssues.filter(i => i.severity === 'medium').length;
|
|
181
|
+
const lowCount = allIssues.filter(i => i.severity === 'low').length;
|
|
182
|
+
// Generate summary
|
|
183
|
+
const summary = generateSummary(criticalCount, highCount, mediumCount, lowCount);
|
|
184
|
+
return {
|
|
185
|
+
timestamp: new Date().toISOString(),
|
|
186
|
+
totalIssuesFound: allIssues.length,
|
|
187
|
+
criticalCount,
|
|
188
|
+
highCount,
|
|
189
|
+
mediumCount,
|
|
190
|
+
lowCount,
|
|
191
|
+
issues: allIssues,
|
|
192
|
+
summary,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Generate human-readable summary
|
|
197
|
+
*/
|
|
198
|
+
function generateSummary(critical, high, medium, low) {
|
|
199
|
+
const total = critical + high + medium + low;
|
|
200
|
+
if (total === 0) {
|
|
201
|
+
return '✅ No security issues detected! Your code looks good.';
|
|
202
|
+
}
|
|
203
|
+
let summary = `Found ${total} potential security issue${total !== 1 ? 's' : ''}: `;
|
|
204
|
+
const parts = [];
|
|
205
|
+
if (critical > 0)
|
|
206
|
+
parts.push(`${critical} critical`);
|
|
207
|
+
if (high > 0)
|
|
208
|
+
parts.push(`${high} high`);
|
|
209
|
+
if (medium > 0)
|
|
210
|
+
parts.push(`${medium} medium`);
|
|
211
|
+
if (low > 0)
|
|
212
|
+
parts.push(`${low} low`);
|
|
213
|
+
summary += parts.join(', ') + '.';
|
|
214
|
+
if (critical > 0) {
|
|
215
|
+
summary += ' ⚠️ Critical issues must be fixed before deployment.';
|
|
216
|
+
}
|
|
217
|
+
else if (high > 0) {
|
|
218
|
+
summary += ' Please review and fix high-severity issues.';
|
|
219
|
+
}
|
|
220
|
+
return summary;
|
|
221
|
+
}
|
|
222
|
+
|
|
3
223
|
const DEV_UI_STORAGE_KEY = 'spektre_dev_enabled';
|
|
4
224
|
let DEV_UI_VERSION = '1.0.0';
|
|
5
225
|
// Inject styles directly into the document
|
|
@@ -246,6 +466,31 @@ function injectDevUIStyles() {
|
|
|
246
466
|
box-shadow: 0 0 0 3px rgba(249, 115, 22, 0.2);
|
|
247
467
|
}
|
|
248
468
|
|
|
469
|
+
/* Analysis Button */
|
|
470
|
+
.spektre-analysis-button {
|
|
471
|
+
width: 100%;
|
|
472
|
+
padding: 12px 16px;
|
|
473
|
+
background-color: rgba(249, 115, 22, 0.2);
|
|
474
|
+
border: 1px solid rgba(249, 115, 22, 0.5);
|
|
475
|
+
color: #fed7aa;
|
|
476
|
+
border-radius: 8px;
|
|
477
|
+
cursor: pointer;
|
|
478
|
+
font-size: 14px;
|
|
479
|
+
font-weight: 500;
|
|
480
|
+
transition: all 0.2s ease;
|
|
481
|
+
margin-bottom: 16px;
|
|
482
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.spektre-analysis-button:hover {
|
|
486
|
+
background-color: rgba(249, 115, 22, 0.3);
|
|
487
|
+
box-shadow: 0 0 0 2px rgba(249, 115, 22, 0.2);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.spektre-analysis-button:active {
|
|
491
|
+
transform: scale(0.98);
|
|
492
|
+
}
|
|
493
|
+
|
|
249
494
|
/* Reload Message */
|
|
250
495
|
.spektre-dev-modal-reload-msg {
|
|
251
496
|
margin: 0;
|
|
@@ -435,6 +680,294 @@ function runSecurityScan() {
|
|
|
435
680
|
issues,
|
|
436
681
|
};
|
|
437
682
|
}
|
|
683
|
+
async function openAnalysisModal() {
|
|
684
|
+
// Check if modal already exists
|
|
685
|
+
if (document.getElementById('spektre-analysis-modal')) {
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
// Show loading state
|
|
689
|
+
const loadingOverlay = document.createElement('div');
|
|
690
|
+
loadingOverlay.style.cssText = `
|
|
691
|
+
position: fixed;
|
|
692
|
+
top: 0;
|
|
693
|
+
left: 0;
|
|
694
|
+
right: 0;
|
|
695
|
+
bottom: 0;
|
|
696
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
697
|
+
display: flex;
|
|
698
|
+
align-items: center;
|
|
699
|
+
justify-content: center;
|
|
700
|
+
z-index: 9999;
|
|
701
|
+
`;
|
|
702
|
+
const loadingModal = document.createElement('div');
|
|
703
|
+
loadingModal.style.cssText = `
|
|
704
|
+
background-color: #000000;
|
|
705
|
+
border: 1px solid #1e293b;
|
|
706
|
+
border-radius: 12px;
|
|
707
|
+
padding: 40px;
|
|
708
|
+
text-align: center;
|
|
709
|
+
color: #ffffff;
|
|
710
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
711
|
+
`;
|
|
712
|
+
loadingModal.innerHTML = `
|
|
713
|
+
<div style="width: 40px; height: 40px; border: 4px solid rgba(71, 85, 105, 0.3); border-top-color: #f97316; border-radius: 50%; margin: 0 auto 16px; animation: spin 1s linear infinite;"></div>
|
|
714
|
+
<p style="margin: 0; font-size: 16px; color: #cbd5e1;">Analyzing your code...</p>
|
|
715
|
+
`;
|
|
716
|
+
loadingOverlay.appendChild(loadingModal);
|
|
717
|
+
document.body.appendChild(loadingOverlay);
|
|
718
|
+
try {
|
|
719
|
+
// Generate analysis
|
|
720
|
+
const sourceMapUrl = `${window.location.origin}/index.js.map`;
|
|
721
|
+
const analysis = await generateSecurityAnalysis(sourceMapUrl);
|
|
722
|
+
// Remove loading modal
|
|
723
|
+
loadingOverlay.remove();
|
|
724
|
+
// Show analysis modal
|
|
725
|
+
createAnalysisModal(analysis);
|
|
726
|
+
}
|
|
727
|
+
catch (error) {
|
|
728
|
+
console.error('[Spektre] Analysis failed:', error);
|
|
729
|
+
loadingOverlay.remove();
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
function createAnalysisModal(analysis) {
|
|
733
|
+
const overlay = document.createElement('div');
|
|
734
|
+
overlay.id = 'spektre-analysis-modal';
|
|
735
|
+
overlay.style.cssText = `
|
|
736
|
+
position: fixed;
|
|
737
|
+
top: 0;
|
|
738
|
+
left: 0;
|
|
739
|
+
right: 0;
|
|
740
|
+
bottom: 0;
|
|
741
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
742
|
+
display: flex;
|
|
743
|
+
align-items: center;
|
|
744
|
+
justify-content: center;
|
|
745
|
+
z-index: 9999;
|
|
746
|
+
animation: spektre-fade-in 0.2s ease;
|
|
747
|
+
`;
|
|
748
|
+
const modal = document.createElement('div');
|
|
749
|
+
modal.style.cssText = `
|
|
750
|
+
background-color: #000000;
|
|
751
|
+
border: 1px solid #1e293b;
|
|
752
|
+
border-radius: 12px;
|
|
753
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.8);
|
|
754
|
+
width: 90%;
|
|
755
|
+
max-width: 700px;
|
|
756
|
+
max-height: 80vh;
|
|
757
|
+
overflow: hidden;
|
|
758
|
+
display: flex;
|
|
759
|
+
flex-direction: column;
|
|
760
|
+
animation: spektre-slide-up 0.3s ease;
|
|
761
|
+
`;
|
|
762
|
+
// Header
|
|
763
|
+
const header = document.createElement('div');
|
|
764
|
+
header.style.cssText = `
|
|
765
|
+
display: flex;
|
|
766
|
+
justify-content: space-between;
|
|
767
|
+
align-items: center;
|
|
768
|
+
padding: 20px;
|
|
769
|
+
background: linear-gradient(135deg, rgba(249, 115, 22, 0.1) 0%, rgba(0, 0, 0, 0.5) 100%);
|
|
770
|
+
border-bottom: 1px solid #1e293b;
|
|
771
|
+
`;
|
|
772
|
+
const title = document.createElement('h2');
|
|
773
|
+
title.style.cssText = `
|
|
774
|
+
margin: 0;
|
|
775
|
+
font-size: 18px;
|
|
776
|
+
font-weight: 600;
|
|
777
|
+
color: #ffffff;
|
|
778
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
779
|
+
`;
|
|
780
|
+
title.textContent = '🔒 Security Analysis Report';
|
|
781
|
+
const closeBtn = document.createElement('button');
|
|
782
|
+
closeBtn.style.cssText = `
|
|
783
|
+
background: none;
|
|
784
|
+
border: none;
|
|
785
|
+
color: #94a3b8;
|
|
786
|
+
font-size: 24px;
|
|
787
|
+
cursor: pointer;
|
|
788
|
+
padding: 0;
|
|
789
|
+
width: 28px;
|
|
790
|
+
height: 28px;
|
|
791
|
+
display: flex;
|
|
792
|
+
align-items: center;
|
|
793
|
+
justify-content: center;
|
|
794
|
+
transition: color 0.2s ease;
|
|
795
|
+
border-radius: 4px;
|
|
796
|
+
`;
|
|
797
|
+
closeBtn.innerHTML = '✕';
|
|
798
|
+
closeBtn.addEventListener('click', () => overlay.remove());
|
|
799
|
+
closeBtn.addEventListener('mouseover', () => (closeBtn.style.color = '#f97316'));
|
|
800
|
+
closeBtn.addEventListener('mouseout', () => (closeBtn.style.color = '#94a3b8'));
|
|
801
|
+
header.appendChild(title);
|
|
802
|
+
header.appendChild(closeBtn);
|
|
803
|
+
// Body
|
|
804
|
+
const body = document.createElement('div');
|
|
805
|
+
body.style.cssText = `
|
|
806
|
+
padding: 24px;
|
|
807
|
+
overflow-y: auto;
|
|
808
|
+
flex: 1;
|
|
809
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
810
|
+
font-size: 14px;
|
|
811
|
+
color: #cbd5e1;
|
|
812
|
+
line-height: 1.6;
|
|
813
|
+
`;
|
|
814
|
+
// Summary
|
|
815
|
+
const summaryDiv = document.createElement('div');
|
|
816
|
+
summaryDiv.style.cssText = `
|
|
817
|
+
background-color: ${analysis.totalIssuesFound === 0 ? 'rgba(34, 197, 94, 0.1)' : 'rgba(239, 68, 68, 0.1)'};
|
|
818
|
+
border: 1px solid ${analysis.totalIssuesFound === 0 ? 'rgba(34, 197, 94, 0.3)' : 'rgba(239, 68, 68, 0.3)'};
|
|
819
|
+
border-radius: 8px;
|
|
820
|
+
padding: 16px;
|
|
821
|
+
margin-bottom: 24px;
|
|
822
|
+
`;
|
|
823
|
+
const summaryText = document.createElement('p');
|
|
824
|
+
summaryText.style.cssText = `
|
|
825
|
+
margin: 0;
|
|
826
|
+
font-size: 15px;
|
|
827
|
+
font-weight: 500;
|
|
828
|
+
color: ${analysis.totalIssuesFound === 0 ? '#86efac' : '#fed7aa'};
|
|
829
|
+
`;
|
|
830
|
+
summaryText.textContent = analysis.summary;
|
|
831
|
+
summaryDiv.appendChild(summaryText);
|
|
832
|
+
body.appendChild(summaryDiv);
|
|
833
|
+
// Stats
|
|
834
|
+
const statsDiv = document.createElement('div');
|
|
835
|
+
statsDiv.style.cssText = `
|
|
836
|
+
display: grid;
|
|
837
|
+
grid-template-columns: repeat(4, 1fr);
|
|
838
|
+
gap: 12px;
|
|
839
|
+
margin-bottom: 24px;
|
|
840
|
+
`;
|
|
841
|
+
const stats = [
|
|
842
|
+
{ label: 'Critical', count: analysis.criticalCount, color: '#ef4444' },
|
|
843
|
+
{ label: 'High', count: analysis.highCount, color: '#f97316' },
|
|
844
|
+
{ label: 'Medium', count: analysis.mediumCount, color: '#eab308' },
|
|
845
|
+
{ label: 'Low', count: analysis.lowCount, color: '#22c55e' },
|
|
846
|
+
];
|
|
847
|
+
stats.forEach(stat => {
|
|
848
|
+
const statCard = document.createElement('div');
|
|
849
|
+
statCard.style.cssText = `
|
|
850
|
+
background-color: rgba(15, 23, 42, 0.8);
|
|
851
|
+
border: 1px solid rgba(71, 85, 105, 0.3);
|
|
852
|
+
border-radius: 6px;
|
|
853
|
+
padding: 12px;
|
|
854
|
+
text-align: center;
|
|
855
|
+
`;
|
|
856
|
+
const count = document.createElement('div');
|
|
857
|
+
count.style.cssText = `
|
|
858
|
+
font-size: 24px;
|
|
859
|
+
font-weight: 600;
|
|
860
|
+
color: ${stat.color};
|
|
861
|
+
margin-bottom: 4px;
|
|
862
|
+
`;
|
|
863
|
+
count.textContent = String(stat.count);
|
|
864
|
+
const label = document.createElement('div');
|
|
865
|
+
label.style.cssText = `
|
|
866
|
+
font-size: 12px;
|
|
867
|
+
color: #94a3b8;
|
|
868
|
+
text-transform: uppercase;
|
|
869
|
+
`;
|
|
870
|
+
label.textContent = stat.label;
|
|
871
|
+
statCard.appendChild(count);
|
|
872
|
+
statCard.appendChild(label);
|
|
873
|
+
statsDiv.appendChild(statCard);
|
|
874
|
+
});
|
|
875
|
+
body.appendChild(statsDiv);
|
|
876
|
+
// Issues list
|
|
877
|
+
if (analysis.issues.length > 0) {
|
|
878
|
+
const issuesTitle = document.createElement('h3');
|
|
879
|
+
issuesTitle.style.cssText = `
|
|
880
|
+
margin: 0 0 16px 0;
|
|
881
|
+
font-size: 16px;
|
|
882
|
+
font-weight: 600;
|
|
883
|
+
color: #ffffff;
|
|
884
|
+
`;
|
|
885
|
+
issuesTitle.textContent = `${analysis.issues.length} Issue${analysis.issues.length !== 1 ? 's' : ''} Found`;
|
|
886
|
+
body.appendChild(issuesTitle);
|
|
887
|
+
analysis.issues.forEach((issue) => {
|
|
888
|
+
const issueCard = document.createElement('div');
|
|
889
|
+
issueCard.style.cssText = `
|
|
890
|
+
background-color: rgba(15, 23, 42, 0.6);
|
|
891
|
+
border-left: 4px solid ${issue.severity === 'critical'
|
|
892
|
+
? '#ef4444'
|
|
893
|
+
: issue.severity === 'high'
|
|
894
|
+
? '#f97316'
|
|
895
|
+
: issue.severity === 'medium'
|
|
896
|
+
? '#eab308'
|
|
897
|
+
: '#22c55e'};
|
|
898
|
+
border-radius: 6px;
|
|
899
|
+
padding: 12px;
|
|
900
|
+
margin-bottom: 12px;
|
|
901
|
+
`;
|
|
902
|
+
issueCard.innerHTML = `
|
|
903
|
+
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 8px;">
|
|
904
|
+
<div style="font-weight: 600; color: #ffffff;">${escapeHtml(issue.type)}</div>
|
|
905
|
+
<div style="background-color: rgba(71, 85, 105, 0.3); padding: 2px 8px; border-radius: 4px; font-size: 12px; color: #94a3b8; text-transform: uppercase;">${issue.severity}</div>
|
|
906
|
+
</div>
|
|
907
|
+
<div style="font-size: 13px; color: #cbd5e1; margin-bottom: 8px;">${escapeHtml(issue.message)}</div>
|
|
908
|
+
<div style="font-size: 12px; color: #64748b; margin-bottom: 8px;">
|
|
909
|
+
📍 ${escapeHtml(issue.file)}:${issue.line}:${issue.column}
|
|
910
|
+
</div>
|
|
911
|
+
<div style="background-color: rgba(0, 0, 0, 0.3); padding: 8px; border-radius: 4px; font-family: monospace; font-size: 12px; color: #cbd5e1; overflow-x: auto;">
|
|
912
|
+
${escapeHtml(issue.code)}
|
|
913
|
+
</div>
|
|
914
|
+
`;
|
|
915
|
+
body.appendChild(issueCard);
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
else {
|
|
919
|
+
const noIssues = document.createElement('div');
|
|
920
|
+
noIssues.style.cssText = `
|
|
921
|
+
text-align: center;
|
|
922
|
+
padding: 40px 20px;
|
|
923
|
+
color: #86efac;
|
|
924
|
+
`;
|
|
925
|
+
noIssues.innerHTML = `
|
|
926
|
+
<div style="font-size: 48px; margin-bottom: 16px;">✅</div>
|
|
927
|
+
<p style="margin: 0; font-size: 16px; font-weight: 500;">No security issues detected!</p>
|
|
928
|
+
<p style="margin: 8px 0 0 0; font-size: 14px; color: #64748b;">Your code looks great.</p>
|
|
929
|
+
`;
|
|
930
|
+
body.appendChild(noIssues);
|
|
931
|
+
}
|
|
932
|
+
// Footer
|
|
933
|
+
const footer = document.createElement('div');
|
|
934
|
+
footer.style.cssText = `
|
|
935
|
+
padding: 16px 24px;
|
|
936
|
+
background-color: #000000;
|
|
937
|
+
border-top: 1px solid #1e293b;
|
|
938
|
+
font-size: 12px;
|
|
939
|
+
color: #64748b;
|
|
940
|
+
text-align: center;
|
|
941
|
+
`;
|
|
942
|
+
footer.textContent = `Generated: ${new Date(analysis.timestamp).toLocaleString()}`;
|
|
943
|
+
modal.appendChild(header);
|
|
944
|
+
modal.appendChild(body);
|
|
945
|
+
modal.appendChild(footer);
|
|
946
|
+
overlay.appendChild(modal);
|
|
947
|
+
// Close on overlay click
|
|
948
|
+
overlay.addEventListener('click', e => {
|
|
949
|
+
if (e.target === overlay) {
|
|
950
|
+
overlay.remove();
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
// Close on escape
|
|
954
|
+
document.addEventListener('keydown', e => {
|
|
955
|
+
if (e.key === 'Escape' && document.getElementById('spektre-analysis-modal')) {
|
|
956
|
+
overlay.remove();
|
|
957
|
+
}
|
|
958
|
+
}, { once: true });
|
|
959
|
+
document.body.appendChild(overlay);
|
|
960
|
+
}
|
|
961
|
+
function escapeHtml(text) {
|
|
962
|
+
const map = {
|
|
963
|
+
'&': '&',
|
|
964
|
+
'<': '<',
|
|
965
|
+
'>': '>',
|
|
966
|
+
'"': '"',
|
|
967
|
+
"'": ''',
|
|
968
|
+
};
|
|
969
|
+
return text.replace(/[&<>"']/g, m => map[m]);
|
|
970
|
+
}
|
|
438
971
|
function initializeDevUI(version) {
|
|
439
972
|
// Set version if provided
|
|
440
973
|
{
|
|
@@ -542,6 +1075,15 @@ function openDevModal(scanResult) {
|
|
|
542
1075
|
body.appendChild(infoText);
|
|
543
1076
|
body.appendChild(statusBadge);
|
|
544
1077
|
body.appendChild(toggleContainer);
|
|
1078
|
+
// Add analysis button
|
|
1079
|
+
const analysisButton = document.createElement('button');
|
|
1080
|
+
analysisButton.className = 'spektre-analysis-button';
|
|
1081
|
+
analysisButton.textContent = '📊 Run Full Code Analysis';
|
|
1082
|
+
analysisButton.addEventListener('click', () => {
|
|
1083
|
+
modal.remove();
|
|
1084
|
+
openAnalysisModal();
|
|
1085
|
+
});
|
|
1086
|
+
body.appendChild(analysisButton);
|
|
545
1087
|
body.appendChild(reloadMessage);
|
|
546
1088
|
// Add security scan results if available
|
|
547
1089
|
if (scanResult) {
|
package/dist/index.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,226 @@
|
|
|
2
2
|
|
|
3
3
|
var React = require('react');
|
|
4
4
|
|
|
5
|
+
// src/analysis/sourceMapAnalyzer.ts
|
|
6
|
+
// Reads all code via source mapping and generates security analysis
|
|
7
|
+
let cachedSourceMap = null;
|
|
8
|
+
let cachedSourceCode = new Map();
|
|
9
|
+
/**
|
|
10
|
+
* Fetch and parse the source map
|
|
11
|
+
*/
|
|
12
|
+
async function fetchSourceMap(sourceMapUrl) {
|
|
13
|
+
if (cachedSourceMap) {
|
|
14
|
+
return cachedSourceMap;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const response = await fetch(sourceMapUrl);
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
console.warn('[Spektre] Could not fetch source map:', sourceMapUrl);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
cachedSourceMap = await response.json();
|
|
23
|
+
return cachedSourceMap;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
console.warn('[Spektre] Source map parsing failed:', error);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Extract all source code from source map
|
|
32
|
+
*/
|
|
33
|
+
async function extractAllSourceCode(sourceMap) {
|
|
34
|
+
if (cachedSourceCode.size > 0) {
|
|
35
|
+
return cachedSourceCode;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
if (!sourceMap || !sourceMap.sourcesContent) {
|
|
39
|
+
return cachedSourceCode;
|
|
40
|
+
}
|
|
41
|
+
sourceMap.sources.forEach((file, index) => {
|
|
42
|
+
const content = sourceMap.sourcesContent[index];
|
|
43
|
+
if (content) {
|
|
44
|
+
cachedSourceCode.set(file, content);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return cachedSourceCode;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.warn('[Spektre] Error extracting source code:', error);
|
|
51
|
+
return cachedSourceCode;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Security analysis patterns to check
|
|
56
|
+
*/
|
|
57
|
+
const SECURITY_PATTERNS = [
|
|
58
|
+
{
|
|
59
|
+
name: 'eval() usage',
|
|
60
|
+
pattern: /\beval\s*\(/g,
|
|
61
|
+
severity: 'critical',
|
|
62
|
+
message: 'eval() is extremely dangerous and should never be used',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'innerHTML assignment',
|
|
66
|
+
pattern: /\.innerHTML\s*=/g,
|
|
67
|
+
severity: 'high',
|
|
68
|
+
message: 'Direct innerHTML assignment can lead to XSS vulnerabilities',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'dangerouslySetInnerHTML',
|
|
72
|
+
pattern: /dangerouslySetInnerHTML/g,
|
|
73
|
+
severity: 'high',
|
|
74
|
+
message: 'dangerouslySetInnerHTML should only be used with sanitized content',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'document.write()',
|
|
78
|
+
pattern: /document\.write\s*\(/g,
|
|
79
|
+
severity: 'medium',
|
|
80
|
+
message: 'document.write() can overwrite the entire document',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'setTimeout with string',
|
|
84
|
+
pattern: /setTimeout\s*\(\s*['"]/g,
|
|
85
|
+
severity: 'high',
|
|
86
|
+
message: 'Passing strings to setTimeout is equivalent to eval()',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'Hardcoded API key',
|
|
90
|
+
pattern: /(?:api[_-]?key|token|password|secret)\s*[:=]\s*['"](.*?)['"]/gi,
|
|
91
|
+
severity: 'critical',
|
|
92
|
+
message: 'Hardcoded credentials found in code',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'No input validation',
|
|
96
|
+
pattern: /innerHTML\s*=\s*(?:userInput|params|query|request)/gi,
|
|
97
|
+
severity: 'high',
|
|
98
|
+
message: 'Unvalidated user input assigned to DOM',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'SQL injection risk',
|
|
102
|
+
pattern: /query\s*=\s*['"]\s*\+|query\s*=\s*`.*\$/gi,
|
|
103
|
+
severity: 'high',
|
|
104
|
+
message: 'String concatenation detected in database query',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'Unsafe regex',
|
|
108
|
+
pattern: /RegExp\s*\(\s*['"].*\*.*['"]\s*\)/g,
|
|
109
|
+
severity: 'medium',
|
|
110
|
+
message: 'Unsafe regex pattern could cause ReDoS attacks',
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'Missing HTTPS',
|
|
114
|
+
pattern: /http:\/\/(?!localhost)/gi,
|
|
115
|
+
severity: 'high',
|
|
116
|
+
message: 'Non-HTTPS URL detected',
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
/**
|
|
120
|
+
* Analyze source code for security issues
|
|
121
|
+
*/
|
|
122
|
+
function analyzeSourceCode(sourceCode, fileName) {
|
|
123
|
+
const issues = [];
|
|
124
|
+
const lines = sourceCode.split('\n');
|
|
125
|
+
SECURITY_PATTERNS.forEach(({ name, pattern, severity, message }) => {
|
|
126
|
+
let match;
|
|
127
|
+
const patternWithIndices = new RegExp(pattern.source, pattern.flags + 'g');
|
|
128
|
+
while ((match = patternWithIndices.exec(sourceCode)) !== null) {
|
|
129
|
+
// Calculate line number from string position
|
|
130
|
+
const lineNumber = sourceCode.substring(0, match.index).split('\n').length;
|
|
131
|
+
const lineContent = lines[lineNumber - 1] || '';
|
|
132
|
+
const columnNumber = match.index - sourceCode.lastIndexOf('\n', match.index);
|
|
133
|
+
// Avoid duplicate issues on same line
|
|
134
|
+
const isDuplicate = issues.some(issue => issue.file === fileName &&
|
|
135
|
+
issue.line === lineNumber &&
|
|
136
|
+
issue.type === name);
|
|
137
|
+
if (!isDuplicate) {
|
|
138
|
+
issues.push({
|
|
139
|
+
type: name,
|
|
140
|
+
severity,
|
|
141
|
+
file: fileName,
|
|
142
|
+
line: lineNumber,
|
|
143
|
+
column: columnNumber,
|
|
144
|
+
message,
|
|
145
|
+
code: lineContent.trim(),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
return issues;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Generate full security analysis of all source code
|
|
154
|
+
*/
|
|
155
|
+
async function generateSecurityAnalysis(sourceMapUrl) {
|
|
156
|
+
const sourceMap = await fetchSourceMap(sourceMapUrl);
|
|
157
|
+
if (!sourceMap) {
|
|
158
|
+
return {
|
|
159
|
+
timestamp: new Date().toISOString(),
|
|
160
|
+
totalIssuesFound: 0,
|
|
161
|
+
criticalCount: 0,
|
|
162
|
+
highCount: 0,
|
|
163
|
+
mediumCount: 0,
|
|
164
|
+
lowCount: 0,
|
|
165
|
+
issues: [],
|
|
166
|
+
summary: 'Could not load source map for analysis.',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const sourceCode = await extractAllSourceCode(sourceMap);
|
|
170
|
+
const allIssues = [];
|
|
171
|
+
// Analyze each source file
|
|
172
|
+
sourceCode.forEach((code, fileName) => {
|
|
173
|
+
const issues = analyzeSourceCode(code, fileName);
|
|
174
|
+
allIssues.push(...issues);
|
|
175
|
+
});
|
|
176
|
+
// Sort by severity
|
|
177
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
178
|
+
allIssues.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
179
|
+
// Count issues by severity
|
|
180
|
+
const criticalCount = allIssues.filter(i => i.severity === 'critical').length;
|
|
181
|
+
const highCount = allIssues.filter(i => i.severity === 'high').length;
|
|
182
|
+
const mediumCount = allIssues.filter(i => i.severity === 'medium').length;
|
|
183
|
+
const lowCount = allIssues.filter(i => i.severity === 'low').length;
|
|
184
|
+
// Generate summary
|
|
185
|
+
const summary = generateSummary(criticalCount, highCount, mediumCount, lowCount);
|
|
186
|
+
return {
|
|
187
|
+
timestamp: new Date().toISOString(),
|
|
188
|
+
totalIssuesFound: allIssues.length,
|
|
189
|
+
criticalCount,
|
|
190
|
+
highCount,
|
|
191
|
+
mediumCount,
|
|
192
|
+
lowCount,
|
|
193
|
+
issues: allIssues,
|
|
194
|
+
summary,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Generate human-readable summary
|
|
199
|
+
*/
|
|
200
|
+
function generateSummary(critical, high, medium, low) {
|
|
201
|
+
const total = critical + high + medium + low;
|
|
202
|
+
if (total === 0) {
|
|
203
|
+
return '✅ No security issues detected! Your code looks good.';
|
|
204
|
+
}
|
|
205
|
+
let summary = `Found ${total} potential security issue${total !== 1 ? 's' : ''}: `;
|
|
206
|
+
const parts = [];
|
|
207
|
+
if (critical > 0)
|
|
208
|
+
parts.push(`${critical} critical`);
|
|
209
|
+
if (high > 0)
|
|
210
|
+
parts.push(`${high} high`);
|
|
211
|
+
if (medium > 0)
|
|
212
|
+
parts.push(`${medium} medium`);
|
|
213
|
+
if (low > 0)
|
|
214
|
+
parts.push(`${low} low`);
|
|
215
|
+
summary += parts.join(', ') + '.';
|
|
216
|
+
if (critical > 0) {
|
|
217
|
+
summary += ' ⚠️ Critical issues must be fixed before deployment.';
|
|
218
|
+
}
|
|
219
|
+
else if (high > 0) {
|
|
220
|
+
summary += ' Please review and fix high-severity issues.';
|
|
221
|
+
}
|
|
222
|
+
return summary;
|
|
223
|
+
}
|
|
224
|
+
|
|
5
225
|
const DEV_UI_STORAGE_KEY = 'spektre_dev_enabled';
|
|
6
226
|
let DEV_UI_VERSION = '1.0.0';
|
|
7
227
|
// Inject styles directly into the document
|
|
@@ -248,6 +468,31 @@ function injectDevUIStyles() {
|
|
|
248
468
|
box-shadow: 0 0 0 3px rgba(249, 115, 22, 0.2);
|
|
249
469
|
}
|
|
250
470
|
|
|
471
|
+
/* Analysis Button */
|
|
472
|
+
.spektre-analysis-button {
|
|
473
|
+
width: 100%;
|
|
474
|
+
padding: 12px 16px;
|
|
475
|
+
background-color: rgba(249, 115, 22, 0.2);
|
|
476
|
+
border: 1px solid rgba(249, 115, 22, 0.5);
|
|
477
|
+
color: #fed7aa;
|
|
478
|
+
border-radius: 8px;
|
|
479
|
+
cursor: pointer;
|
|
480
|
+
font-size: 14px;
|
|
481
|
+
font-weight: 500;
|
|
482
|
+
transition: all 0.2s ease;
|
|
483
|
+
margin-bottom: 16px;
|
|
484
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.spektre-analysis-button:hover {
|
|
488
|
+
background-color: rgba(249, 115, 22, 0.3);
|
|
489
|
+
box-shadow: 0 0 0 2px rgba(249, 115, 22, 0.2);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.spektre-analysis-button:active {
|
|
493
|
+
transform: scale(0.98);
|
|
494
|
+
}
|
|
495
|
+
|
|
251
496
|
/* Reload Message */
|
|
252
497
|
.spektre-dev-modal-reload-msg {
|
|
253
498
|
margin: 0;
|
|
@@ -437,6 +682,294 @@ function runSecurityScan() {
|
|
|
437
682
|
issues,
|
|
438
683
|
};
|
|
439
684
|
}
|
|
685
|
+
async function openAnalysisModal() {
|
|
686
|
+
// Check if modal already exists
|
|
687
|
+
if (document.getElementById('spektre-analysis-modal')) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
// Show loading state
|
|
691
|
+
const loadingOverlay = document.createElement('div');
|
|
692
|
+
loadingOverlay.style.cssText = `
|
|
693
|
+
position: fixed;
|
|
694
|
+
top: 0;
|
|
695
|
+
left: 0;
|
|
696
|
+
right: 0;
|
|
697
|
+
bottom: 0;
|
|
698
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
699
|
+
display: flex;
|
|
700
|
+
align-items: center;
|
|
701
|
+
justify-content: center;
|
|
702
|
+
z-index: 9999;
|
|
703
|
+
`;
|
|
704
|
+
const loadingModal = document.createElement('div');
|
|
705
|
+
loadingModal.style.cssText = `
|
|
706
|
+
background-color: #000000;
|
|
707
|
+
border: 1px solid #1e293b;
|
|
708
|
+
border-radius: 12px;
|
|
709
|
+
padding: 40px;
|
|
710
|
+
text-align: center;
|
|
711
|
+
color: #ffffff;
|
|
712
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
713
|
+
`;
|
|
714
|
+
loadingModal.innerHTML = `
|
|
715
|
+
<div style="width: 40px; height: 40px; border: 4px solid rgba(71, 85, 105, 0.3); border-top-color: #f97316; border-radius: 50%; margin: 0 auto 16px; animation: spin 1s linear infinite;"></div>
|
|
716
|
+
<p style="margin: 0; font-size: 16px; color: #cbd5e1;">Analyzing your code...</p>
|
|
717
|
+
`;
|
|
718
|
+
loadingOverlay.appendChild(loadingModal);
|
|
719
|
+
document.body.appendChild(loadingOverlay);
|
|
720
|
+
try {
|
|
721
|
+
// Generate analysis
|
|
722
|
+
const sourceMapUrl = `${window.location.origin}/index.js.map`;
|
|
723
|
+
const analysis = await generateSecurityAnalysis(sourceMapUrl);
|
|
724
|
+
// Remove loading modal
|
|
725
|
+
loadingOverlay.remove();
|
|
726
|
+
// Show analysis modal
|
|
727
|
+
createAnalysisModal(analysis);
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
console.error('[Spektre] Analysis failed:', error);
|
|
731
|
+
loadingOverlay.remove();
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
function createAnalysisModal(analysis) {
|
|
735
|
+
const overlay = document.createElement('div');
|
|
736
|
+
overlay.id = 'spektre-analysis-modal';
|
|
737
|
+
overlay.style.cssText = `
|
|
738
|
+
position: fixed;
|
|
739
|
+
top: 0;
|
|
740
|
+
left: 0;
|
|
741
|
+
right: 0;
|
|
742
|
+
bottom: 0;
|
|
743
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
744
|
+
display: flex;
|
|
745
|
+
align-items: center;
|
|
746
|
+
justify-content: center;
|
|
747
|
+
z-index: 9999;
|
|
748
|
+
animation: spektre-fade-in 0.2s ease;
|
|
749
|
+
`;
|
|
750
|
+
const modal = document.createElement('div');
|
|
751
|
+
modal.style.cssText = `
|
|
752
|
+
background-color: #000000;
|
|
753
|
+
border: 1px solid #1e293b;
|
|
754
|
+
border-radius: 12px;
|
|
755
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.8);
|
|
756
|
+
width: 90%;
|
|
757
|
+
max-width: 700px;
|
|
758
|
+
max-height: 80vh;
|
|
759
|
+
overflow: hidden;
|
|
760
|
+
display: flex;
|
|
761
|
+
flex-direction: column;
|
|
762
|
+
animation: spektre-slide-up 0.3s ease;
|
|
763
|
+
`;
|
|
764
|
+
// Header
|
|
765
|
+
const header = document.createElement('div');
|
|
766
|
+
header.style.cssText = `
|
|
767
|
+
display: flex;
|
|
768
|
+
justify-content: space-between;
|
|
769
|
+
align-items: center;
|
|
770
|
+
padding: 20px;
|
|
771
|
+
background: linear-gradient(135deg, rgba(249, 115, 22, 0.1) 0%, rgba(0, 0, 0, 0.5) 100%);
|
|
772
|
+
border-bottom: 1px solid #1e293b;
|
|
773
|
+
`;
|
|
774
|
+
const title = document.createElement('h2');
|
|
775
|
+
title.style.cssText = `
|
|
776
|
+
margin: 0;
|
|
777
|
+
font-size: 18px;
|
|
778
|
+
font-weight: 600;
|
|
779
|
+
color: #ffffff;
|
|
780
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
781
|
+
`;
|
|
782
|
+
title.textContent = '🔒 Security Analysis Report';
|
|
783
|
+
const closeBtn = document.createElement('button');
|
|
784
|
+
closeBtn.style.cssText = `
|
|
785
|
+
background: none;
|
|
786
|
+
border: none;
|
|
787
|
+
color: #94a3b8;
|
|
788
|
+
font-size: 24px;
|
|
789
|
+
cursor: pointer;
|
|
790
|
+
padding: 0;
|
|
791
|
+
width: 28px;
|
|
792
|
+
height: 28px;
|
|
793
|
+
display: flex;
|
|
794
|
+
align-items: center;
|
|
795
|
+
justify-content: center;
|
|
796
|
+
transition: color 0.2s ease;
|
|
797
|
+
border-radius: 4px;
|
|
798
|
+
`;
|
|
799
|
+
closeBtn.innerHTML = '✕';
|
|
800
|
+
closeBtn.addEventListener('click', () => overlay.remove());
|
|
801
|
+
closeBtn.addEventListener('mouseover', () => (closeBtn.style.color = '#f97316'));
|
|
802
|
+
closeBtn.addEventListener('mouseout', () => (closeBtn.style.color = '#94a3b8'));
|
|
803
|
+
header.appendChild(title);
|
|
804
|
+
header.appendChild(closeBtn);
|
|
805
|
+
// Body
|
|
806
|
+
const body = document.createElement('div');
|
|
807
|
+
body.style.cssText = `
|
|
808
|
+
padding: 24px;
|
|
809
|
+
overflow-y: auto;
|
|
810
|
+
flex: 1;
|
|
811
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
812
|
+
font-size: 14px;
|
|
813
|
+
color: #cbd5e1;
|
|
814
|
+
line-height: 1.6;
|
|
815
|
+
`;
|
|
816
|
+
// Summary
|
|
817
|
+
const summaryDiv = document.createElement('div');
|
|
818
|
+
summaryDiv.style.cssText = `
|
|
819
|
+
background-color: ${analysis.totalIssuesFound === 0 ? 'rgba(34, 197, 94, 0.1)' : 'rgba(239, 68, 68, 0.1)'};
|
|
820
|
+
border: 1px solid ${analysis.totalIssuesFound === 0 ? 'rgba(34, 197, 94, 0.3)' : 'rgba(239, 68, 68, 0.3)'};
|
|
821
|
+
border-radius: 8px;
|
|
822
|
+
padding: 16px;
|
|
823
|
+
margin-bottom: 24px;
|
|
824
|
+
`;
|
|
825
|
+
const summaryText = document.createElement('p');
|
|
826
|
+
summaryText.style.cssText = `
|
|
827
|
+
margin: 0;
|
|
828
|
+
font-size: 15px;
|
|
829
|
+
font-weight: 500;
|
|
830
|
+
color: ${analysis.totalIssuesFound === 0 ? '#86efac' : '#fed7aa'};
|
|
831
|
+
`;
|
|
832
|
+
summaryText.textContent = analysis.summary;
|
|
833
|
+
summaryDiv.appendChild(summaryText);
|
|
834
|
+
body.appendChild(summaryDiv);
|
|
835
|
+
// Stats
|
|
836
|
+
const statsDiv = document.createElement('div');
|
|
837
|
+
statsDiv.style.cssText = `
|
|
838
|
+
display: grid;
|
|
839
|
+
grid-template-columns: repeat(4, 1fr);
|
|
840
|
+
gap: 12px;
|
|
841
|
+
margin-bottom: 24px;
|
|
842
|
+
`;
|
|
843
|
+
const stats = [
|
|
844
|
+
{ label: 'Critical', count: analysis.criticalCount, color: '#ef4444' },
|
|
845
|
+
{ label: 'High', count: analysis.highCount, color: '#f97316' },
|
|
846
|
+
{ label: 'Medium', count: analysis.mediumCount, color: '#eab308' },
|
|
847
|
+
{ label: 'Low', count: analysis.lowCount, color: '#22c55e' },
|
|
848
|
+
];
|
|
849
|
+
stats.forEach(stat => {
|
|
850
|
+
const statCard = document.createElement('div');
|
|
851
|
+
statCard.style.cssText = `
|
|
852
|
+
background-color: rgba(15, 23, 42, 0.8);
|
|
853
|
+
border: 1px solid rgba(71, 85, 105, 0.3);
|
|
854
|
+
border-radius: 6px;
|
|
855
|
+
padding: 12px;
|
|
856
|
+
text-align: center;
|
|
857
|
+
`;
|
|
858
|
+
const count = document.createElement('div');
|
|
859
|
+
count.style.cssText = `
|
|
860
|
+
font-size: 24px;
|
|
861
|
+
font-weight: 600;
|
|
862
|
+
color: ${stat.color};
|
|
863
|
+
margin-bottom: 4px;
|
|
864
|
+
`;
|
|
865
|
+
count.textContent = String(stat.count);
|
|
866
|
+
const label = document.createElement('div');
|
|
867
|
+
label.style.cssText = `
|
|
868
|
+
font-size: 12px;
|
|
869
|
+
color: #94a3b8;
|
|
870
|
+
text-transform: uppercase;
|
|
871
|
+
`;
|
|
872
|
+
label.textContent = stat.label;
|
|
873
|
+
statCard.appendChild(count);
|
|
874
|
+
statCard.appendChild(label);
|
|
875
|
+
statsDiv.appendChild(statCard);
|
|
876
|
+
});
|
|
877
|
+
body.appendChild(statsDiv);
|
|
878
|
+
// Issues list
|
|
879
|
+
if (analysis.issues.length > 0) {
|
|
880
|
+
const issuesTitle = document.createElement('h3');
|
|
881
|
+
issuesTitle.style.cssText = `
|
|
882
|
+
margin: 0 0 16px 0;
|
|
883
|
+
font-size: 16px;
|
|
884
|
+
font-weight: 600;
|
|
885
|
+
color: #ffffff;
|
|
886
|
+
`;
|
|
887
|
+
issuesTitle.textContent = `${analysis.issues.length} Issue${analysis.issues.length !== 1 ? 's' : ''} Found`;
|
|
888
|
+
body.appendChild(issuesTitle);
|
|
889
|
+
analysis.issues.forEach((issue) => {
|
|
890
|
+
const issueCard = document.createElement('div');
|
|
891
|
+
issueCard.style.cssText = `
|
|
892
|
+
background-color: rgba(15, 23, 42, 0.6);
|
|
893
|
+
border-left: 4px solid ${issue.severity === 'critical'
|
|
894
|
+
? '#ef4444'
|
|
895
|
+
: issue.severity === 'high'
|
|
896
|
+
? '#f97316'
|
|
897
|
+
: issue.severity === 'medium'
|
|
898
|
+
? '#eab308'
|
|
899
|
+
: '#22c55e'};
|
|
900
|
+
border-radius: 6px;
|
|
901
|
+
padding: 12px;
|
|
902
|
+
margin-bottom: 12px;
|
|
903
|
+
`;
|
|
904
|
+
issueCard.innerHTML = `
|
|
905
|
+
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 8px;">
|
|
906
|
+
<div style="font-weight: 600; color: #ffffff;">${escapeHtml(issue.type)}</div>
|
|
907
|
+
<div style="background-color: rgba(71, 85, 105, 0.3); padding: 2px 8px; border-radius: 4px; font-size: 12px; color: #94a3b8; text-transform: uppercase;">${issue.severity}</div>
|
|
908
|
+
</div>
|
|
909
|
+
<div style="font-size: 13px; color: #cbd5e1; margin-bottom: 8px;">${escapeHtml(issue.message)}</div>
|
|
910
|
+
<div style="font-size: 12px; color: #64748b; margin-bottom: 8px;">
|
|
911
|
+
📍 ${escapeHtml(issue.file)}:${issue.line}:${issue.column}
|
|
912
|
+
</div>
|
|
913
|
+
<div style="background-color: rgba(0, 0, 0, 0.3); padding: 8px; border-radius: 4px; font-family: monospace; font-size: 12px; color: #cbd5e1; overflow-x: auto;">
|
|
914
|
+
${escapeHtml(issue.code)}
|
|
915
|
+
</div>
|
|
916
|
+
`;
|
|
917
|
+
body.appendChild(issueCard);
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
else {
|
|
921
|
+
const noIssues = document.createElement('div');
|
|
922
|
+
noIssues.style.cssText = `
|
|
923
|
+
text-align: center;
|
|
924
|
+
padding: 40px 20px;
|
|
925
|
+
color: #86efac;
|
|
926
|
+
`;
|
|
927
|
+
noIssues.innerHTML = `
|
|
928
|
+
<div style="font-size: 48px; margin-bottom: 16px;">✅</div>
|
|
929
|
+
<p style="margin: 0; font-size: 16px; font-weight: 500;">No security issues detected!</p>
|
|
930
|
+
<p style="margin: 8px 0 0 0; font-size: 14px; color: #64748b;">Your code looks great.</p>
|
|
931
|
+
`;
|
|
932
|
+
body.appendChild(noIssues);
|
|
933
|
+
}
|
|
934
|
+
// Footer
|
|
935
|
+
const footer = document.createElement('div');
|
|
936
|
+
footer.style.cssText = `
|
|
937
|
+
padding: 16px 24px;
|
|
938
|
+
background-color: #000000;
|
|
939
|
+
border-top: 1px solid #1e293b;
|
|
940
|
+
font-size: 12px;
|
|
941
|
+
color: #64748b;
|
|
942
|
+
text-align: center;
|
|
943
|
+
`;
|
|
944
|
+
footer.textContent = `Generated: ${new Date(analysis.timestamp).toLocaleString()}`;
|
|
945
|
+
modal.appendChild(header);
|
|
946
|
+
modal.appendChild(body);
|
|
947
|
+
modal.appendChild(footer);
|
|
948
|
+
overlay.appendChild(modal);
|
|
949
|
+
// Close on overlay click
|
|
950
|
+
overlay.addEventListener('click', e => {
|
|
951
|
+
if (e.target === overlay) {
|
|
952
|
+
overlay.remove();
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
// Close on escape
|
|
956
|
+
document.addEventListener('keydown', e => {
|
|
957
|
+
if (e.key === 'Escape' && document.getElementById('spektre-analysis-modal')) {
|
|
958
|
+
overlay.remove();
|
|
959
|
+
}
|
|
960
|
+
}, { once: true });
|
|
961
|
+
document.body.appendChild(overlay);
|
|
962
|
+
}
|
|
963
|
+
function escapeHtml(text) {
|
|
964
|
+
const map = {
|
|
965
|
+
'&': '&',
|
|
966
|
+
'<': '<',
|
|
967
|
+
'>': '>',
|
|
968
|
+
'"': '"',
|
|
969
|
+
"'": ''',
|
|
970
|
+
};
|
|
971
|
+
return text.replace(/[&<>"']/g, m => map[m]);
|
|
972
|
+
}
|
|
440
973
|
function initializeDevUI(version) {
|
|
441
974
|
// Set version if provided
|
|
442
975
|
{
|
|
@@ -544,6 +1077,15 @@ function openDevModal(scanResult) {
|
|
|
544
1077
|
body.appendChild(infoText);
|
|
545
1078
|
body.appendChild(statusBadge);
|
|
546
1079
|
body.appendChild(toggleContainer);
|
|
1080
|
+
// Add analysis button
|
|
1081
|
+
const analysisButton = document.createElement('button');
|
|
1082
|
+
analysisButton.className = 'spektre-analysis-button';
|
|
1083
|
+
analysisButton.textContent = '📊 Run Full Code Analysis';
|
|
1084
|
+
analysisButton.addEventListener('click', () => {
|
|
1085
|
+
modal.remove();
|
|
1086
|
+
openAnalysisModal();
|
|
1087
|
+
});
|
|
1088
|
+
body.appendChild(analysisButton);
|
|
547
1089
|
body.appendChild(reloadMessage);
|
|
548
1090
|
// Add security scan results if available
|
|
549
1091
|
if (scanResult) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|