@spektre/veil 0.1.6 → 0.1.8
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/SecurityGate.d.ts +5 -0
- package/dist/devUI.d.ts +7 -0
- package/dist/index.esm.js +568 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +568 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/SecurityGate.d.ts
CHANGED
package/dist/devUI.d.ts
ADDED
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,558 @@
|
|
|
1
1
|
import React, { createContext, useState, useEffect, useContext } from 'react';
|
|
2
2
|
|
|
3
|
+
const DEV_UI_STORAGE_KEY = 'spektre_dev_enabled';
|
|
4
|
+
// Inject styles directly into the document
|
|
5
|
+
function injectDevUIStyles() {
|
|
6
|
+
if (document.getElementById('spektre-dev-ui-styles')) {
|
|
7
|
+
return; // Already injected
|
|
8
|
+
}
|
|
9
|
+
const styleSheet = document.createElement('style');
|
|
10
|
+
styleSheet.id = 'spektre-dev-ui-styles';
|
|
11
|
+
styleSheet.textContent = `
|
|
12
|
+
/* Spektre Dev UI Dot */
|
|
13
|
+
.spektre-dev-dot {
|
|
14
|
+
position: fixed;
|
|
15
|
+
bottom: 24px;
|
|
16
|
+
right: 24px;
|
|
17
|
+
width: 16px;
|
|
18
|
+
height: 16px;
|
|
19
|
+
background-color: #f97316;
|
|
20
|
+
border-radius: 50%;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
z-index: 9998;
|
|
23
|
+
box-shadow: 0 4px 12px rgba(249, 115, 22, 0.4);
|
|
24
|
+
transition: all 0.2s ease;
|
|
25
|
+
outline: none;
|
|
26
|
+
border: none;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.spektre-dev-dot:hover {
|
|
30
|
+
width: 20px;
|
|
31
|
+
height: 20px;
|
|
32
|
+
bottom: 22px;
|
|
33
|
+
right: 22px;
|
|
34
|
+
box-shadow: 0 6px 16px rgba(249, 115, 22, 0.6);
|
|
35
|
+
transform: scale(1.1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.spektre-dev-dot.disabled {
|
|
39
|
+
background-color: #64748b;
|
|
40
|
+
box-shadow: 0 4px 12px rgba(100, 116, 139, 0.4);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.spektre-dev-dot.disabled:hover {
|
|
44
|
+
box-shadow: 0 6px 16px rgba(100, 116, 139, 0.6);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.spektre-dev-dot.warning {
|
|
48
|
+
background-color: #ef4444;
|
|
49
|
+
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.6);
|
|
50
|
+
animation: spektre-pulse 2s infinite;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@keyframes spektre-pulse {
|
|
54
|
+
0%, 100% {
|
|
55
|
+
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.6);
|
|
56
|
+
}
|
|
57
|
+
50% {
|
|
58
|
+
box-shadow: 0 4px 20px rgba(239, 68, 68, 0.8);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Modal Overlay */
|
|
63
|
+
.spektre-dev-modal-overlay {
|
|
64
|
+
position: fixed;
|
|
65
|
+
top: 0;
|
|
66
|
+
left: 0;
|
|
67
|
+
right: 0;
|
|
68
|
+
bottom: 0;
|
|
69
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
justify-content: center;
|
|
73
|
+
z-index: 9999;
|
|
74
|
+
animation: spektre-fade-in 0.2s ease;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@keyframes spektre-fade-in {
|
|
78
|
+
from {
|
|
79
|
+
opacity: 0;
|
|
80
|
+
}
|
|
81
|
+
to {
|
|
82
|
+
opacity: 1;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Modal Container */
|
|
87
|
+
.spektre-dev-modal {
|
|
88
|
+
background-color: #000000;
|
|
89
|
+
border: 1px solid #1e293b;
|
|
90
|
+
border-radius: 12px;
|
|
91
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.8);
|
|
92
|
+
width: 90%;
|
|
93
|
+
max-width: 400px;
|
|
94
|
+
overflow: hidden;
|
|
95
|
+
animation: spektre-slide-up 0.3s ease;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@keyframes spektre-slide-up {
|
|
99
|
+
from {
|
|
100
|
+
transform: translateY(20px);
|
|
101
|
+
opacity: 0;
|
|
102
|
+
}
|
|
103
|
+
to {
|
|
104
|
+
transform: translateY(0);
|
|
105
|
+
opacity: 1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* Modal Header */
|
|
110
|
+
.spektre-dev-modal-header {
|
|
111
|
+
display: flex;
|
|
112
|
+
justify-content: space-between;
|
|
113
|
+
align-items: center;
|
|
114
|
+
padding: 20px;
|
|
115
|
+
border-bottom: 1px solid #1e293b;
|
|
116
|
+
background: linear-gradient(135deg, rgba(249, 115, 22, 0.1) 0%, rgba(0, 0, 0, 0.5) 100%);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.spektre-dev-modal-header h2 {
|
|
120
|
+
margin: 0;
|
|
121
|
+
font-size: 18px;
|
|
122
|
+
font-weight: 600;
|
|
123
|
+
color: #ffffff;
|
|
124
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.spektre-dev-modal-close {
|
|
128
|
+
background: none;
|
|
129
|
+
border: none;
|
|
130
|
+
color: #94a3b8;
|
|
131
|
+
font-size: 24px;
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
padding: 0;
|
|
134
|
+
width: 28px;
|
|
135
|
+
height: 28px;
|
|
136
|
+
display: flex;
|
|
137
|
+
align-items: center;
|
|
138
|
+
justify-content: center;
|
|
139
|
+
transition: color 0.2s ease;
|
|
140
|
+
border-radius: 4px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.spektre-dev-modal-close:hover {
|
|
144
|
+
color: #f97316;
|
|
145
|
+
background-color: rgba(249, 115, 22, 0.1);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* Modal Body */
|
|
149
|
+
.spektre-dev-modal-body {
|
|
150
|
+
padding: 24px;
|
|
151
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
152
|
+
max-height: 60vh;
|
|
153
|
+
overflow-y: auto;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.spektre-dev-modal-info {
|
|
157
|
+
margin: 0 0 16px 0;
|
|
158
|
+
font-size: 14px;
|
|
159
|
+
color: #cbd5e1;
|
|
160
|
+
line-height: 1.5;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* Status Badge */
|
|
164
|
+
.spektre-dev-modal-status {
|
|
165
|
+
display: inline-block;
|
|
166
|
+
padding: 8px 12px;
|
|
167
|
+
border-radius: 6px;
|
|
168
|
+
font-size: 14px;
|
|
169
|
+
font-weight: 500;
|
|
170
|
+
margin-bottom: 20px;
|
|
171
|
+
font-family: monospace;
|
|
172
|
+
transition: all 0.2s ease;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.spektre-dev-modal-status.enabled {
|
|
176
|
+
background-color: rgba(34, 197, 94, 0.15);
|
|
177
|
+
color: #86efac;
|
|
178
|
+
border: 1px solid rgba(34, 197, 94, 0.3);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.spektre-dev-modal-status.disabled {
|
|
182
|
+
background-color: rgba(100, 116, 139, 0.15);
|
|
183
|
+
color: #cbd5e1;
|
|
184
|
+
border: 1px solid rgba(100, 116, 139, 0.3);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Toggle Container */
|
|
188
|
+
.spektre-dev-modal-toggle-container {
|
|
189
|
+
display: flex;
|
|
190
|
+
align-items: center;
|
|
191
|
+
margin-bottom: 16px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.spektre-dev-toggle-label {
|
|
195
|
+
display: flex;
|
|
196
|
+
align-items: center;
|
|
197
|
+
gap: 12px;
|
|
198
|
+
cursor: pointer;
|
|
199
|
+
font-size: 14px;
|
|
200
|
+
color: #e2e8f0;
|
|
201
|
+
user-select: none;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* Toggle Switch */
|
|
205
|
+
.spektre-dev-toggle {
|
|
206
|
+
appearance: none;
|
|
207
|
+
-webkit-appearance: none;
|
|
208
|
+
width: 44px;
|
|
209
|
+
height: 24px;
|
|
210
|
+
background-color: #1e293b;
|
|
211
|
+
border: 1px solid #334155;
|
|
212
|
+
border-radius: 12px;
|
|
213
|
+
cursor: pointer;
|
|
214
|
+
position: relative;
|
|
215
|
+
transition: all 0.3s ease;
|
|
216
|
+
padding: 2px;
|
|
217
|
+
flex-shrink: 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.spektre-dev-toggle:checked {
|
|
221
|
+
background-color: #f97316;
|
|
222
|
+
border-color: #f97316;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.spektre-dev-toggle::before {
|
|
226
|
+
content: '';
|
|
227
|
+
position: absolute;
|
|
228
|
+
width: 18px;
|
|
229
|
+
height: 18px;
|
|
230
|
+
background-color: white;
|
|
231
|
+
border-radius: 10px;
|
|
232
|
+
top: 3px;
|
|
233
|
+
left: 3px;
|
|
234
|
+
transition: left 0.3s ease;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.spektre-dev-toggle:checked::before {
|
|
238
|
+
left: 23px;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.spektre-dev-toggle:focus {
|
|
242
|
+
outline: none;
|
|
243
|
+
box-shadow: 0 0 0 3px rgba(249, 115, 22, 0.2);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* Reload Message */
|
|
247
|
+
.spektre-dev-modal-reload-msg {
|
|
248
|
+
margin: 0;
|
|
249
|
+
padding: 12px;
|
|
250
|
+
background-color: rgba(249, 115, 22, 0.15);
|
|
251
|
+
border: 1px solid rgba(249, 115, 22, 0.3);
|
|
252
|
+
border-radius: 6px;
|
|
253
|
+
font-size: 13px;
|
|
254
|
+
color: #fed7aa;
|
|
255
|
+
text-align: center;
|
|
256
|
+
transition: opacity 0.2s ease;
|
|
257
|
+
font-weight: 500;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/* Security Scan Results */
|
|
261
|
+
.spektre-security-scan-container {
|
|
262
|
+
margin-top: 16px;
|
|
263
|
+
padding: 16px;
|
|
264
|
+
background-color: rgba(239, 68, 68, 0.1);
|
|
265
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
266
|
+
border-radius: 8px;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.spektre-security-scan-title {
|
|
270
|
+
font-size: 14px;
|
|
271
|
+
font-weight: 600;
|
|
272
|
+
color: #fca5a5;
|
|
273
|
+
margin: 0 0 12px 0;
|
|
274
|
+
display: flex;
|
|
275
|
+
align-items: center;
|
|
276
|
+
gap: 8px;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.spektre-security-scan-issues {
|
|
280
|
+
display: flex;
|
|
281
|
+
flex-direction: column;
|
|
282
|
+
gap: 8px;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.spektre-security-issue {
|
|
286
|
+
font-size: 13px;
|
|
287
|
+
color: #fed7aa;
|
|
288
|
+
padding: 8px;
|
|
289
|
+
background-color: rgba(239, 68, 68, 0.15);
|
|
290
|
+
border-left: 3px solid #ef4444;
|
|
291
|
+
border-radius: 4px;
|
|
292
|
+
line-height: 1.4;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.spektre-security-scan-clean {
|
|
296
|
+
font-size: 13px;
|
|
297
|
+
color: #86efac;
|
|
298
|
+
padding: 12px;
|
|
299
|
+
background-color: rgba(34, 197, 94, 0.15);
|
|
300
|
+
border: 1px solid rgba(34, 197, 94, 0.3);
|
|
301
|
+
border-radius: 6px;
|
|
302
|
+
text-align: center;
|
|
303
|
+
font-weight: 500;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/* Accessibility */
|
|
307
|
+
@media (prefers-reduced-motion: reduce) {
|
|
308
|
+
.spektre-dev-dot,
|
|
309
|
+
.spektre-dev-modal-overlay,
|
|
310
|
+
.spektre-dev-modal,
|
|
311
|
+
.spektre-dev-toggle,
|
|
312
|
+
.spektre-dev-toggle::before {
|
|
313
|
+
animation: none;
|
|
314
|
+
transition: none;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* Mobile responsiveness */
|
|
319
|
+
@media (max-width: 480px) {
|
|
320
|
+
.spektre-dev-dot {
|
|
321
|
+
bottom: 16px;
|
|
322
|
+
right: 16px;
|
|
323
|
+
width: 14px;
|
|
324
|
+
height: 14px;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.spektre-dev-dot:hover {
|
|
328
|
+
width: 18px;
|
|
329
|
+
height: 18px;
|
|
330
|
+
bottom: 14px;
|
|
331
|
+
right: 14px;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.spektre-dev-modal {
|
|
335
|
+
width: 85%;
|
|
336
|
+
max-width: 320px;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.spektre-dev-modal-header h2 {
|
|
340
|
+
font-size: 16px;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.spektre-dev-modal-body {
|
|
344
|
+
padding: 16px;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
`;
|
|
348
|
+
document.head.appendChild(styleSheet);
|
|
349
|
+
}
|
|
350
|
+
function runSecurityScan() {
|
|
351
|
+
const issues = [];
|
|
352
|
+
// 1. Check for hardcoded API keys/tokens
|
|
353
|
+
const sensitivePatterns = [
|
|
354
|
+
{ pattern: /api[_-]?key\s*[:=]\s*['"]\S+['"]/gi, label: 'API Key' },
|
|
355
|
+
{ pattern: /token\s*[:=]\s*['"]\S+['"]/gi, label: 'Token' },
|
|
356
|
+
{ pattern: /password\s*[:=]\s*['"]\S+['"]/gi, label: 'Password' },
|
|
357
|
+
{ pattern: /secret\s*[:=]\s*['"]\S+['"]/gi, label: 'Secret' },
|
|
358
|
+
{ pattern: /bearer\s+[A-Za-z0-9\-._~+/]+=*/gi, label: 'Bearer Token' },
|
|
359
|
+
];
|
|
360
|
+
const pageHTML = document.documentElement.outerHTML;
|
|
361
|
+
const scriptTags = Array.from(document.querySelectorAll('script'));
|
|
362
|
+
const allText = pageHTML + scriptTags.map(s => s.textContent).join('\n');
|
|
363
|
+
sensitivePatterns.forEach(({ pattern, label }) => {
|
|
364
|
+
if (pattern.test(allText)) {
|
|
365
|
+
issues.push(`⚠️ Found hardcoded ${label} in page code`);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
// 2. Check for dangerous functions
|
|
369
|
+
const dangerousPatterns = [
|
|
370
|
+
{ pattern: /eval\s*\(/gi, label: 'eval() usage' },
|
|
371
|
+
{ pattern: /innerHTML\s*=/gi, label: 'innerHTML manipulation' },
|
|
372
|
+
{ pattern: /dangerouslySetInnerHTML/gi, label: 'dangerouslySetInnerHTML' },
|
|
373
|
+
{ pattern: /document\.write/gi, label: 'document.write()' },
|
|
374
|
+
{ pattern: /setTimeout\s*\(\s*['"`].*['"`]/gi, label: 'setTimeout with string' },
|
|
375
|
+
];
|
|
376
|
+
dangerousPatterns.forEach(({ pattern, label }) => {
|
|
377
|
+
if (pattern.test(allText)) {
|
|
378
|
+
issues.push(`⚠️ Potential security risk: ${label}`);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
// 3. Check localStorage for sensitive data
|
|
382
|
+
try {
|
|
383
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
384
|
+
const key = localStorage.key(i);
|
|
385
|
+
if (key) {
|
|
386
|
+
const value = localStorage.getItem(key);
|
|
387
|
+
if (value) {
|
|
388
|
+
if (/token|password|secret|key|auth/i.test(key) &&
|
|
389
|
+
value.length > 20) {
|
|
390
|
+
issues.push(`⚠️ Sensitive data in localStorage: "${key}"`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
catch (e) {
|
|
397
|
+
// localStorage access denied, skip
|
|
398
|
+
}
|
|
399
|
+
// 4. Check for missing CSP headers (via console warning detection)
|
|
400
|
+
const scripts = scriptTags.filter(s => s.src && !s.src.includes('spektre'));
|
|
401
|
+
if (scripts.length > 10) {
|
|
402
|
+
issues.push(`⚠️ Many external scripts loaded (${scripts.length})`);
|
|
403
|
+
}
|
|
404
|
+
return {
|
|
405
|
+
hasIssues: issues.length > 0,
|
|
406
|
+
issues,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function initializeDevUI() {
|
|
410
|
+
// Inject styles first
|
|
411
|
+
injectDevUIStyles();
|
|
412
|
+
// Check if Spektre is enabled in localStorage (default to true)
|
|
413
|
+
const isEnabled = localStorage.getItem(DEV_UI_STORAGE_KEY);
|
|
414
|
+
const spektreEnabled = isEnabled === null ? true : isEnabled === 'true';
|
|
415
|
+
// If disabled, prevent core Spektre from initializing
|
|
416
|
+
if (!spektreEnabled) {
|
|
417
|
+
window.__spektreDevDisabled = true;
|
|
418
|
+
}
|
|
419
|
+
// Create and inject the orange dot
|
|
420
|
+
createDevDot();
|
|
421
|
+
// Run security scan after a short delay to ensure DOM is ready
|
|
422
|
+
setTimeout(() => {
|
|
423
|
+
const scanResult = runSecurityScan();
|
|
424
|
+
if (scanResult.hasIssues) {
|
|
425
|
+
// Update dot to warning state and auto-open modal
|
|
426
|
+
const dot = document.getElementById('spektre-dev-dot');
|
|
427
|
+
if (dot) {
|
|
428
|
+
dot.classList.add('warning');
|
|
429
|
+
}
|
|
430
|
+
openDevModal(scanResult);
|
|
431
|
+
}
|
|
432
|
+
}, 500);
|
|
433
|
+
}
|
|
434
|
+
function createDevDot() {
|
|
435
|
+
const dot = document.createElement('div');
|
|
436
|
+
dot.id = 'spektre-dev-dot';
|
|
437
|
+
dot.className = 'spektre-dev-dot';
|
|
438
|
+
dot.setAttribute('aria-label', 'Spektre development environment controls');
|
|
439
|
+
dot.addEventListener('click', () => {
|
|
440
|
+
openDevModal();
|
|
441
|
+
});
|
|
442
|
+
document.body.appendChild(dot);
|
|
443
|
+
}
|
|
444
|
+
function openDevModal(scanResult) {
|
|
445
|
+
// Check if modal already exists
|
|
446
|
+
if (document.getElementById('spektre-dev-modal')) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const isEnabled = localStorage.getItem(DEV_UI_STORAGE_KEY) !== 'false';
|
|
450
|
+
const modal = document.createElement('div');
|
|
451
|
+
modal.id = 'spektre-dev-modal';
|
|
452
|
+
modal.className = 'spektre-dev-modal-overlay';
|
|
453
|
+
const modalContent = document.createElement('div');
|
|
454
|
+
modalContent.className = 'spektre-dev-modal';
|
|
455
|
+
const header = document.createElement('div');
|
|
456
|
+
header.className = 'spektre-dev-modal-header';
|
|
457
|
+
header.innerHTML = '<h2>Spektre Development Mode</h2>';
|
|
458
|
+
const closeButton = document.createElement('button');
|
|
459
|
+
closeButton.className = 'spektre-dev-modal-close';
|
|
460
|
+
closeButton.innerHTML = '✕';
|
|
461
|
+
closeButton.setAttribute('aria-label', 'Close modal');
|
|
462
|
+
closeButton.addEventListener('click', () => {
|
|
463
|
+
modal.remove();
|
|
464
|
+
});
|
|
465
|
+
header.appendChild(closeButton);
|
|
466
|
+
const body = document.createElement('div');
|
|
467
|
+
body.className = 'spektre-dev-modal-body';
|
|
468
|
+
const infoText = document.createElement('p');
|
|
469
|
+
infoText.className = 'spektre-dev-modal-info';
|
|
470
|
+
infoText.textContent = 'You are in a development environment. Spektre is currently:';
|
|
471
|
+
const statusBadge = document.createElement('div');
|
|
472
|
+
statusBadge.className = `spektre-dev-modal-status ${isEnabled ? 'enabled' : 'disabled'}`;
|
|
473
|
+
statusBadge.textContent = isEnabled ? '● Enabled' : '● Disabled';
|
|
474
|
+
const toggleContainer = document.createElement('div');
|
|
475
|
+
toggleContainer.className = 'spektre-dev-modal-toggle-container';
|
|
476
|
+
const toggleLabel = document.createElement('label');
|
|
477
|
+
toggleLabel.className = 'spektre-dev-toggle-label';
|
|
478
|
+
const toggleSwitch = document.createElement('input');
|
|
479
|
+
toggleSwitch.type = 'checkbox';
|
|
480
|
+
toggleSwitch.className = 'spektre-dev-toggle';
|
|
481
|
+
toggleSwitch.checked = isEnabled;
|
|
482
|
+
toggleSwitch.setAttribute('aria-label', 'Toggle Spektre');
|
|
483
|
+
toggleSwitch.addEventListener('change', (e) => {
|
|
484
|
+
const enabled = e.target.checked;
|
|
485
|
+
localStorage.setItem(DEV_UI_STORAGE_KEY, String(enabled));
|
|
486
|
+
// Update status badge
|
|
487
|
+
statusBadge.className = `spektre-dev-modal-status ${enabled ? 'enabled' : 'disabled'}`;
|
|
488
|
+
statusBadge.textContent = enabled ? '● Enabled' : '● Disabled';
|
|
489
|
+
// Update dot visual
|
|
490
|
+
const dot = document.getElementById('spektre-dev-dot');
|
|
491
|
+
if (dot) {
|
|
492
|
+
dot.classList.toggle('disabled', !enabled);
|
|
493
|
+
}
|
|
494
|
+
// Show reload message
|
|
495
|
+
const reloadMsg = document.querySelector('.spektre-dev-modal-reload-msg');
|
|
496
|
+
if (reloadMsg) {
|
|
497
|
+
reloadMsg.textContent = 'Reload the page for changes to take effect';
|
|
498
|
+
reloadMsg.style.opacity = '1';
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
toggleLabel.appendChild(toggleSwitch);
|
|
502
|
+
toggleLabel.appendChild(document.createTextNode('Enable Spektre'));
|
|
503
|
+
toggleContainer.appendChild(toggleLabel);
|
|
504
|
+
const reloadMessage = document.createElement('p');
|
|
505
|
+
reloadMessage.className = 'spektre-dev-modal-reload-msg';
|
|
506
|
+
reloadMessage.style.opacity = '0';
|
|
507
|
+
body.appendChild(infoText);
|
|
508
|
+
body.appendChild(statusBadge);
|
|
509
|
+
body.appendChild(toggleContainer);
|
|
510
|
+
body.appendChild(reloadMessage);
|
|
511
|
+
// Add security scan results if available
|
|
512
|
+
if (scanResult) {
|
|
513
|
+
const scanContainer = document.createElement('div');
|
|
514
|
+
scanContainer.className = 'spektre-security-scan-container';
|
|
515
|
+
const scanTitle = document.createElement('div');
|
|
516
|
+
scanTitle.className = 'spektre-security-scan-title';
|
|
517
|
+
scanTitle.innerHTML = '🔒 Security Check';
|
|
518
|
+
scanContainer.appendChild(scanTitle);
|
|
519
|
+
if (scanResult.hasIssues) {
|
|
520
|
+
const issuesContainer = document.createElement('div');
|
|
521
|
+
issuesContainer.className = 'spektre-security-scan-issues';
|
|
522
|
+
scanResult.issues.forEach(issue => {
|
|
523
|
+
const issueElement = document.createElement('div');
|
|
524
|
+
issueElement.className = 'spektre-security-issue';
|
|
525
|
+
issueElement.textContent = issue;
|
|
526
|
+
issuesContainer.appendChild(issueElement);
|
|
527
|
+
});
|
|
528
|
+
scanContainer.appendChild(issuesContainer);
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
const cleanMessage = document.createElement('div');
|
|
532
|
+
cleanMessage.className = 'spektre-security-scan-clean';
|
|
533
|
+
cleanMessage.textContent = '✓ No security issues detected';
|
|
534
|
+
scanContainer.appendChild(cleanMessage);
|
|
535
|
+
}
|
|
536
|
+
body.appendChild(scanContainer);
|
|
537
|
+
}
|
|
538
|
+
modalContent.appendChild(header);
|
|
539
|
+
modalContent.appendChild(body);
|
|
540
|
+
modal.appendChild(modalContent);
|
|
541
|
+
// Close on overlay click
|
|
542
|
+
modal.addEventListener('click', (e) => {
|
|
543
|
+
if (e.target === modal) {
|
|
544
|
+
modal.remove();
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
// Close on escape key
|
|
548
|
+
document.addEventListener('keydown', (e) => {
|
|
549
|
+
if (e.key === 'Escape' && document.getElementById('spektre-dev-modal')) {
|
|
550
|
+
modal.remove();
|
|
551
|
+
}
|
|
552
|
+
}, { once: true });
|
|
553
|
+
document.body.appendChild(modal);
|
|
554
|
+
}
|
|
555
|
+
|
|
3
556
|
const SpektreContext = createContext(null);
|
|
4
557
|
SpektreContext.displayName = 'SpektreContext';
|
|
5
558
|
|
|
@@ -16,6 +569,16 @@ const SecurityGate = ({ apiKey, children, config, fallback, loadingComponent, })
|
|
|
16
569
|
sessionId: null,
|
|
17
570
|
});
|
|
18
571
|
useEffect(() => {
|
|
572
|
+
// Check if Spektre is disabled in dev mode
|
|
573
|
+
if (window.__spektreDevDisabled) {
|
|
574
|
+
setState({
|
|
575
|
+
isVerified: true,
|
|
576
|
+
isLoading: false,
|
|
577
|
+
error: null,
|
|
578
|
+
sessionId: 'dev-disabled',
|
|
579
|
+
});
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
19
582
|
// Set tampering detection marker
|
|
20
583
|
sessionStorage.setItem('spektre_js_enabled', 'true');
|
|
21
584
|
sessionStorage.setItem('spektre_init_time', Date.now().toString());
|
|
@@ -430,5 +993,10 @@ const useProtectedFetch = () => {
|
|
|
430
993
|
return protectedFetch;
|
|
431
994
|
};
|
|
432
995
|
|
|
996
|
+
// Check if we're in an iframe (development environment) and initialize dev UI
|
|
997
|
+
if (typeof window !== 'undefined' && window.self !== window.top) {
|
|
998
|
+
initializeDevUI();
|
|
999
|
+
}
|
|
1000
|
+
|
|
433
1001
|
export { SecurityGate, SpektreContext, SpektreProvider, useProtectedFetch, useSpektre };
|
|
434
1002
|
//# sourceMappingURL=index.esm.js.map
|
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,559 @@
|
|
|
2
2
|
|
|
3
3
|
var React = require('react');
|
|
4
4
|
|
|
5
|
+
const DEV_UI_STORAGE_KEY = 'spektre_dev_enabled';
|
|
6
|
+
// Inject styles directly into the document
|
|
7
|
+
function injectDevUIStyles() {
|
|
8
|
+
if (document.getElementById('spektre-dev-ui-styles')) {
|
|
9
|
+
return; // Already injected
|
|
10
|
+
}
|
|
11
|
+
const styleSheet = document.createElement('style');
|
|
12
|
+
styleSheet.id = 'spektre-dev-ui-styles';
|
|
13
|
+
styleSheet.textContent = `
|
|
14
|
+
/* Spektre Dev UI Dot */
|
|
15
|
+
.spektre-dev-dot {
|
|
16
|
+
position: fixed;
|
|
17
|
+
bottom: 24px;
|
|
18
|
+
right: 24px;
|
|
19
|
+
width: 16px;
|
|
20
|
+
height: 16px;
|
|
21
|
+
background-color: #f97316;
|
|
22
|
+
border-radius: 50%;
|
|
23
|
+
cursor: pointer;
|
|
24
|
+
z-index: 9998;
|
|
25
|
+
box-shadow: 0 4px 12px rgba(249, 115, 22, 0.4);
|
|
26
|
+
transition: all 0.2s ease;
|
|
27
|
+
outline: none;
|
|
28
|
+
border: none;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.spektre-dev-dot:hover {
|
|
32
|
+
width: 20px;
|
|
33
|
+
height: 20px;
|
|
34
|
+
bottom: 22px;
|
|
35
|
+
right: 22px;
|
|
36
|
+
box-shadow: 0 6px 16px rgba(249, 115, 22, 0.6);
|
|
37
|
+
transform: scale(1.1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.spektre-dev-dot.disabled {
|
|
41
|
+
background-color: #64748b;
|
|
42
|
+
box-shadow: 0 4px 12px rgba(100, 116, 139, 0.4);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.spektre-dev-dot.disabled:hover {
|
|
46
|
+
box-shadow: 0 6px 16px rgba(100, 116, 139, 0.6);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.spektre-dev-dot.warning {
|
|
50
|
+
background-color: #ef4444;
|
|
51
|
+
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.6);
|
|
52
|
+
animation: spektre-pulse 2s infinite;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@keyframes spektre-pulse {
|
|
56
|
+
0%, 100% {
|
|
57
|
+
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.6);
|
|
58
|
+
}
|
|
59
|
+
50% {
|
|
60
|
+
box-shadow: 0 4px 20px rgba(239, 68, 68, 0.8);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Modal Overlay */
|
|
65
|
+
.spektre-dev-modal-overlay {
|
|
66
|
+
position: fixed;
|
|
67
|
+
top: 0;
|
|
68
|
+
left: 0;
|
|
69
|
+
right: 0;
|
|
70
|
+
bottom: 0;
|
|
71
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
justify-content: center;
|
|
75
|
+
z-index: 9999;
|
|
76
|
+
animation: spektre-fade-in 0.2s ease;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@keyframes spektre-fade-in {
|
|
80
|
+
from {
|
|
81
|
+
opacity: 0;
|
|
82
|
+
}
|
|
83
|
+
to {
|
|
84
|
+
opacity: 1;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Modal Container */
|
|
89
|
+
.spektre-dev-modal {
|
|
90
|
+
background-color: #000000;
|
|
91
|
+
border: 1px solid #1e293b;
|
|
92
|
+
border-radius: 12px;
|
|
93
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.8);
|
|
94
|
+
width: 90%;
|
|
95
|
+
max-width: 400px;
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
animation: spektre-slide-up 0.3s ease;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@keyframes spektre-slide-up {
|
|
101
|
+
from {
|
|
102
|
+
transform: translateY(20px);
|
|
103
|
+
opacity: 0;
|
|
104
|
+
}
|
|
105
|
+
to {
|
|
106
|
+
transform: translateY(0);
|
|
107
|
+
opacity: 1;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Modal Header */
|
|
112
|
+
.spektre-dev-modal-header {
|
|
113
|
+
display: flex;
|
|
114
|
+
justify-content: space-between;
|
|
115
|
+
align-items: center;
|
|
116
|
+
padding: 20px;
|
|
117
|
+
border-bottom: 1px solid #1e293b;
|
|
118
|
+
background: linear-gradient(135deg, rgba(249, 115, 22, 0.1) 0%, rgba(0, 0, 0, 0.5) 100%);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.spektre-dev-modal-header h2 {
|
|
122
|
+
margin: 0;
|
|
123
|
+
font-size: 18px;
|
|
124
|
+
font-weight: 600;
|
|
125
|
+
color: #ffffff;
|
|
126
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.spektre-dev-modal-close {
|
|
130
|
+
background: none;
|
|
131
|
+
border: none;
|
|
132
|
+
color: #94a3b8;
|
|
133
|
+
font-size: 24px;
|
|
134
|
+
cursor: pointer;
|
|
135
|
+
padding: 0;
|
|
136
|
+
width: 28px;
|
|
137
|
+
height: 28px;
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
justify-content: center;
|
|
141
|
+
transition: color 0.2s ease;
|
|
142
|
+
border-radius: 4px;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.spektre-dev-modal-close:hover {
|
|
146
|
+
color: #f97316;
|
|
147
|
+
background-color: rgba(249, 115, 22, 0.1);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* Modal Body */
|
|
151
|
+
.spektre-dev-modal-body {
|
|
152
|
+
padding: 24px;
|
|
153
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
154
|
+
max-height: 60vh;
|
|
155
|
+
overflow-y: auto;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.spektre-dev-modal-info {
|
|
159
|
+
margin: 0 0 16px 0;
|
|
160
|
+
font-size: 14px;
|
|
161
|
+
color: #cbd5e1;
|
|
162
|
+
line-height: 1.5;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* Status Badge */
|
|
166
|
+
.spektre-dev-modal-status {
|
|
167
|
+
display: inline-block;
|
|
168
|
+
padding: 8px 12px;
|
|
169
|
+
border-radius: 6px;
|
|
170
|
+
font-size: 14px;
|
|
171
|
+
font-weight: 500;
|
|
172
|
+
margin-bottom: 20px;
|
|
173
|
+
font-family: monospace;
|
|
174
|
+
transition: all 0.2s ease;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.spektre-dev-modal-status.enabled {
|
|
178
|
+
background-color: rgba(34, 197, 94, 0.15);
|
|
179
|
+
color: #86efac;
|
|
180
|
+
border: 1px solid rgba(34, 197, 94, 0.3);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.spektre-dev-modal-status.disabled {
|
|
184
|
+
background-color: rgba(100, 116, 139, 0.15);
|
|
185
|
+
color: #cbd5e1;
|
|
186
|
+
border: 1px solid rgba(100, 116, 139, 0.3);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* Toggle Container */
|
|
190
|
+
.spektre-dev-modal-toggle-container {
|
|
191
|
+
display: flex;
|
|
192
|
+
align-items: center;
|
|
193
|
+
margin-bottom: 16px;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.spektre-dev-toggle-label {
|
|
197
|
+
display: flex;
|
|
198
|
+
align-items: center;
|
|
199
|
+
gap: 12px;
|
|
200
|
+
cursor: pointer;
|
|
201
|
+
font-size: 14px;
|
|
202
|
+
color: #e2e8f0;
|
|
203
|
+
user-select: none;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* Toggle Switch */
|
|
207
|
+
.spektre-dev-toggle {
|
|
208
|
+
appearance: none;
|
|
209
|
+
-webkit-appearance: none;
|
|
210
|
+
width: 44px;
|
|
211
|
+
height: 24px;
|
|
212
|
+
background-color: #1e293b;
|
|
213
|
+
border: 1px solid #334155;
|
|
214
|
+
border-radius: 12px;
|
|
215
|
+
cursor: pointer;
|
|
216
|
+
position: relative;
|
|
217
|
+
transition: all 0.3s ease;
|
|
218
|
+
padding: 2px;
|
|
219
|
+
flex-shrink: 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.spektre-dev-toggle:checked {
|
|
223
|
+
background-color: #f97316;
|
|
224
|
+
border-color: #f97316;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.spektre-dev-toggle::before {
|
|
228
|
+
content: '';
|
|
229
|
+
position: absolute;
|
|
230
|
+
width: 18px;
|
|
231
|
+
height: 18px;
|
|
232
|
+
background-color: white;
|
|
233
|
+
border-radius: 10px;
|
|
234
|
+
top: 3px;
|
|
235
|
+
left: 3px;
|
|
236
|
+
transition: left 0.3s ease;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.spektre-dev-toggle:checked::before {
|
|
240
|
+
left: 23px;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.spektre-dev-toggle:focus {
|
|
244
|
+
outline: none;
|
|
245
|
+
box-shadow: 0 0 0 3px rgba(249, 115, 22, 0.2);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* Reload Message */
|
|
249
|
+
.spektre-dev-modal-reload-msg {
|
|
250
|
+
margin: 0;
|
|
251
|
+
padding: 12px;
|
|
252
|
+
background-color: rgba(249, 115, 22, 0.15);
|
|
253
|
+
border: 1px solid rgba(249, 115, 22, 0.3);
|
|
254
|
+
border-radius: 6px;
|
|
255
|
+
font-size: 13px;
|
|
256
|
+
color: #fed7aa;
|
|
257
|
+
text-align: center;
|
|
258
|
+
transition: opacity 0.2s ease;
|
|
259
|
+
font-weight: 500;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* Security Scan Results */
|
|
263
|
+
.spektre-security-scan-container {
|
|
264
|
+
margin-top: 16px;
|
|
265
|
+
padding: 16px;
|
|
266
|
+
background-color: rgba(239, 68, 68, 0.1);
|
|
267
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
268
|
+
border-radius: 8px;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.spektre-security-scan-title {
|
|
272
|
+
font-size: 14px;
|
|
273
|
+
font-weight: 600;
|
|
274
|
+
color: #fca5a5;
|
|
275
|
+
margin: 0 0 12px 0;
|
|
276
|
+
display: flex;
|
|
277
|
+
align-items: center;
|
|
278
|
+
gap: 8px;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.spektre-security-scan-issues {
|
|
282
|
+
display: flex;
|
|
283
|
+
flex-direction: column;
|
|
284
|
+
gap: 8px;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.spektre-security-issue {
|
|
288
|
+
font-size: 13px;
|
|
289
|
+
color: #fed7aa;
|
|
290
|
+
padding: 8px;
|
|
291
|
+
background-color: rgba(239, 68, 68, 0.15);
|
|
292
|
+
border-left: 3px solid #ef4444;
|
|
293
|
+
border-radius: 4px;
|
|
294
|
+
line-height: 1.4;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.spektre-security-scan-clean {
|
|
298
|
+
font-size: 13px;
|
|
299
|
+
color: #86efac;
|
|
300
|
+
padding: 12px;
|
|
301
|
+
background-color: rgba(34, 197, 94, 0.15);
|
|
302
|
+
border: 1px solid rgba(34, 197, 94, 0.3);
|
|
303
|
+
border-radius: 6px;
|
|
304
|
+
text-align: center;
|
|
305
|
+
font-weight: 500;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* Accessibility */
|
|
309
|
+
@media (prefers-reduced-motion: reduce) {
|
|
310
|
+
.spektre-dev-dot,
|
|
311
|
+
.spektre-dev-modal-overlay,
|
|
312
|
+
.spektre-dev-modal,
|
|
313
|
+
.spektre-dev-toggle,
|
|
314
|
+
.spektre-dev-toggle::before {
|
|
315
|
+
animation: none;
|
|
316
|
+
transition: none;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* Mobile responsiveness */
|
|
321
|
+
@media (max-width: 480px) {
|
|
322
|
+
.spektre-dev-dot {
|
|
323
|
+
bottom: 16px;
|
|
324
|
+
right: 16px;
|
|
325
|
+
width: 14px;
|
|
326
|
+
height: 14px;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.spektre-dev-dot:hover {
|
|
330
|
+
width: 18px;
|
|
331
|
+
height: 18px;
|
|
332
|
+
bottom: 14px;
|
|
333
|
+
right: 14px;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.spektre-dev-modal {
|
|
337
|
+
width: 85%;
|
|
338
|
+
max-width: 320px;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.spektre-dev-modal-header h2 {
|
|
342
|
+
font-size: 16px;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.spektre-dev-modal-body {
|
|
346
|
+
padding: 16px;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
`;
|
|
350
|
+
document.head.appendChild(styleSheet);
|
|
351
|
+
}
|
|
352
|
+
function runSecurityScan() {
|
|
353
|
+
const issues = [];
|
|
354
|
+
// 1. Check for hardcoded API keys/tokens
|
|
355
|
+
const sensitivePatterns = [
|
|
356
|
+
{ pattern: /api[_-]?key\s*[:=]\s*['"]\S+['"]/gi, label: 'API Key' },
|
|
357
|
+
{ pattern: /token\s*[:=]\s*['"]\S+['"]/gi, label: 'Token' },
|
|
358
|
+
{ pattern: /password\s*[:=]\s*['"]\S+['"]/gi, label: 'Password' },
|
|
359
|
+
{ pattern: /secret\s*[:=]\s*['"]\S+['"]/gi, label: 'Secret' },
|
|
360
|
+
{ pattern: /bearer\s+[A-Za-z0-9\-._~+/]+=*/gi, label: 'Bearer Token' },
|
|
361
|
+
];
|
|
362
|
+
const pageHTML = document.documentElement.outerHTML;
|
|
363
|
+
const scriptTags = Array.from(document.querySelectorAll('script'));
|
|
364
|
+
const allText = pageHTML + scriptTags.map(s => s.textContent).join('\n');
|
|
365
|
+
sensitivePatterns.forEach(({ pattern, label }) => {
|
|
366
|
+
if (pattern.test(allText)) {
|
|
367
|
+
issues.push(`⚠️ Found hardcoded ${label} in page code`);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
// 2. Check for dangerous functions
|
|
371
|
+
const dangerousPatterns = [
|
|
372
|
+
{ pattern: /eval\s*\(/gi, label: 'eval() usage' },
|
|
373
|
+
{ pattern: /innerHTML\s*=/gi, label: 'innerHTML manipulation' },
|
|
374
|
+
{ pattern: /dangerouslySetInnerHTML/gi, label: 'dangerouslySetInnerHTML' },
|
|
375
|
+
{ pattern: /document\.write/gi, label: 'document.write()' },
|
|
376
|
+
{ pattern: /setTimeout\s*\(\s*['"`].*['"`]/gi, label: 'setTimeout with string' },
|
|
377
|
+
];
|
|
378
|
+
dangerousPatterns.forEach(({ pattern, label }) => {
|
|
379
|
+
if (pattern.test(allText)) {
|
|
380
|
+
issues.push(`⚠️ Potential security risk: ${label}`);
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
// 3. Check localStorage for sensitive data
|
|
384
|
+
try {
|
|
385
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
386
|
+
const key = localStorage.key(i);
|
|
387
|
+
if (key) {
|
|
388
|
+
const value = localStorage.getItem(key);
|
|
389
|
+
if (value) {
|
|
390
|
+
if (/token|password|secret|key|auth/i.test(key) &&
|
|
391
|
+
value.length > 20) {
|
|
392
|
+
issues.push(`⚠️ Sensitive data in localStorage: "${key}"`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
catch (e) {
|
|
399
|
+
// localStorage access denied, skip
|
|
400
|
+
}
|
|
401
|
+
// 4. Check for missing CSP headers (via console warning detection)
|
|
402
|
+
const scripts = scriptTags.filter(s => s.src && !s.src.includes('spektre'));
|
|
403
|
+
if (scripts.length > 10) {
|
|
404
|
+
issues.push(`⚠️ Many external scripts loaded (${scripts.length})`);
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
hasIssues: issues.length > 0,
|
|
408
|
+
issues,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function initializeDevUI() {
|
|
412
|
+
// Inject styles first
|
|
413
|
+
injectDevUIStyles();
|
|
414
|
+
// Check if Spektre is enabled in localStorage (default to true)
|
|
415
|
+
const isEnabled = localStorage.getItem(DEV_UI_STORAGE_KEY);
|
|
416
|
+
const spektreEnabled = isEnabled === null ? true : isEnabled === 'true';
|
|
417
|
+
// If disabled, prevent core Spektre from initializing
|
|
418
|
+
if (!spektreEnabled) {
|
|
419
|
+
window.__spektreDevDisabled = true;
|
|
420
|
+
}
|
|
421
|
+
// Create and inject the orange dot
|
|
422
|
+
createDevDot();
|
|
423
|
+
// Run security scan after a short delay to ensure DOM is ready
|
|
424
|
+
setTimeout(() => {
|
|
425
|
+
const scanResult = runSecurityScan();
|
|
426
|
+
if (scanResult.hasIssues) {
|
|
427
|
+
// Update dot to warning state and auto-open modal
|
|
428
|
+
const dot = document.getElementById('spektre-dev-dot');
|
|
429
|
+
if (dot) {
|
|
430
|
+
dot.classList.add('warning');
|
|
431
|
+
}
|
|
432
|
+
openDevModal(scanResult);
|
|
433
|
+
}
|
|
434
|
+
}, 500);
|
|
435
|
+
}
|
|
436
|
+
function createDevDot() {
|
|
437
|
+
const dot = document.createElement('div');
|
|
438
|
+
dot.id = 'spektre-dev-dot';
|
|
439
|
+
dot.className = 'spektre-dev-dot';
|
|
440
|
+
dot.setAttribute('aria-label', 'Spektre development environment controls');
|
|
441
|
+
dot.addEventListener('click', () => {
|
|
442
|
+
openDevModal();
|
|
443
|
+
});
|
|
444
|
+
document.body.appendChild(dot);
|
|
445
|
+
}
|
|
446
|
+
function openDevModal(scanResult) {
|
|
447
|
+
// Check if modal already exists
|
|
448
|
+
if (document.getElementById('spektre-dev-modal')) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const isEnabled = localStorage.getItem(DEV_UI_STORAGE_KEY) !== 'false';
|
|
452
|
+
const modal = document.createElement('div');
|
|
453
|
+
modal.id = 'spektre-dev-modal';
|
|
454
|
+
modal.className = 'spektre-dev-modal-overlay';
|
|
455
|
+
const modalContent = document.createElement('div');
|
|
456
|
+
modalContent.className = 'spektre-dev-modal';
|
|
457
|
+
const header = document.createElement('div');
|
|
458
|
+
header.className = 'spektre-dev-modal-header';
|
|
459
|
+
header.innerHTML = '<h2>Spektre Development Mode</h2>';
|
|
460
|
+
const closeButton = document.createElement('button');
|
|
461
|
+
closeButton.className = 'spektre-dev-modal-close';
|
|
462
|
+
closeButton.innerHTML = '✕';
|
|
463
|
+
closeButton.setAttribute('aria-label', 'Close modal');
|
|
464
|
+
closeButton.addEventListener('click', () => {
|
|
465
|
+
modal.remove();
|
|
466
|
+
});
|
|
467
|
+
header.appendChild(closeButton);
|
|
468
|
+
const body = document.createElement('div');
|
|
469
|
+
body.className = 'spektre-dev-modal-body';
|
|
470
|
+
const infoText = document.createElement('p');
|
|
471
|
+
infoText.className = 'spektre-dev-modal-info';
|
|
472
|
+
infoText.textContent = 'You are in a development environment. Spektre is currently:';
|
|
473
|
+
const statusBadge = document.createElement('div');
|
|
474
|
+
statusBadge.className = `spektre-dev-modal-status ${isEnabled ? 'enabled' : 'disabled'}`;
|
|
475
|
+
statusBadge.textContent = isEnabled ? '● Enabled' : '● Disabled';
|
|
476
|
+
const toggleContainer = document.createElement('div');
|
|
477
|
+
toggleContainer.className = 'spektre-dev-modal-toggle-container';
|
|
478
|
+
const toggleLabel = document.createElement('label');
|
|
479
|
+
toggleLabel.className = 'spektre-dev-toggle-label';
|
|
480
|
+
const toggleSwitch = document.createElement('input');
|
|
481
|
+
toggleSwitch.type = 'checkbox';
|
|
482
|
+
toggleSwitch.className = 'spektre-dev-toggle';
|
|
483
|
+
toggleSwitch.checked = isEnabled;
|
|
484
|
+
toggleSwitch.setAttribute('aria-label', 'Toggle Spektre');
|
|
485
|
+
toggleSwitch.addEventListener('change', (e) => {
|
|
486
|
+
const enabled = e.target.checked;
|
|
487
|
+
localStorage.setItem(DEV_UI_STORAGE_KEY, String(enabled));
|
|
488
|
+
// Update status badge
|
|
489
|
+
statusBadge.className = `spektre-dev-modal-status ${enabled ? 'enabled' : 'disabled'}`;
|
|
490
|
+
statusBadge.textContent = enabled ? '● Enabled' : '● Disabled';
|
|
491
|
+
// Update dot visual
|
|
492
|
+
const dot = document.getElementById('spektre-dev-dot');
|
|
493
|
+
if (dot) {
|
|
494
|
+
dot.classList.toggle('disabled', !enabled);
|
|
495
|
+
}
|
|
496
|
+
// Show reload message
|
|
497
|
+
const reloadMsg = document.querySelector('.spektre-dev-modal-reload-msg');
|
|
498
|
+
if (reloadMsg) {
|
|
499
|
+
reloadMsg.textContent = 'Reload the page for changes to take effect';
|
|
500
|
+
reloadMsg.style.opacity = '1';
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
toggleLabel.appendChild(toggleSwitch);
|
|
504
|
+
toggleLabel.appendChild(document.createTextNode('Enable Spektre'));
|
|
505
|
+
toggleContainer.appendChild(toggleLabel);
|
|
506
|
+
const reloadMessage = document.createElement('p');
|
|
507
|
+
reloadMessage.className = 'spektre-dev-modal-reload-msg';
|
|
508
|
+
reloadMessage.style.opacity = '0';
|
|
509
|
+
body.appendChild(infoText);
|
|
510
|
+
body.appendChild(statusBadge);
|
|
511
|
+
body.appendChild(toggleContainer);
|
|
512
|
+
body.appendChild(reloadMessage);
|
|
513
|
+
// Add security scan results if available
|
|
514
|
+
if (scanResult) {
|
|
515
|
+
const scanContainer = document.createElement('div');
|
|
516
|
+
scanContainer.className = 'spektre-security-scan-container';
|
|
517
|
+
const scanTitle = document.createElement('div');
|
|
518
|
+
scanTitle.className = 'spektre-security-scan-title';
|
|
519
|
+
scanTitle.innerHTML = '🔒 Security Check';
|
|
520
|
+
scanContainer.appendChild(scanTitle);
|
|
521
|
+
if (scanResult.hasIssues) {
|
|
522
|
+
const issuesContainer = document.createElement('div');
|
|
523
|
+
issuesContainer.className = 'spektre-security-scan-issues';
|
|
524
|
+
scanResult.issues.forEach(issue => {
|
|
525
|
+
const issueElement = document.createElement('div');
|
|
526
|
+
issueElement.className = 'spektre-security-issue';
|
|
527
|
+
issueElement.textContent = issue;
|
|
528
|
+
issuesContainer.appendChild(issueElement);
|
|
529
|
+
});
|
|
530
|
+
scanContainer.appendChild(issuesContainer);
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
const cleanMessage = document.createElement('div');
|
|
534
|
+
cleanMessage.className = 'spektre-security-scan-clean';
|
|
535
|
+
cleanMessage.textContent = '✓ No security issues detected';
|
|
536
|
+
scanContainer.appendChild(cleanMessage);
|
|
537
|
+
}
|
|
538
|
+
body.appendChild(scanContainer);
|
|
539
|
+
}
|
|
540
|
+
modalContent.appendChild(header);
|
|
541
|
+
modalContent.appendChild(body);
|
|
542
|
+
modal.appendChild(modalContent);
|
|
543
|
+
// Close on overlay click
|
|
544
|
+
modal.addEventListener('click', (e) => {
|
|
545
|
+
if (e.target === modal) {
|
|
546
|
+
modal.remove();
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
// Close on escape key
|
|
550
|
+
document.addEventListener('keydown', (e) => {
|
|
551
|
+
if (e.key === 'Escape' && document.getElementById('spektre-dev-modal')) {
|
|
552
|
+
modal.remove();
|
|
553
|
+
}
|
|
554
|
+
}, { once: true });
|
|
555
|
+
document.body.appendChild(modal);
|
|
556
|
+
}
|
|
557
|
+
|
|
5
558
|
const SpektreContext = React.createContext(null);
|
|
6
559
|
SpektreContext.displayName = 'SpektreContext';
|
|
7
560
|
|
|
@@ -18,6 +571,16 @@ const SecurityGate = ({ apiKey, children, config, fallback, loadingComponent, })
|
|
|
18
571
|
sessionId: null,
|
|
19
572
|
});
|
|
20
573
|
React.useEffect(() => {
|
|
574
|
+
// Check if Spektre is disabled in dev mode
|
|
575
|
+
if (window.__spektreDevDisabled) {
|
|
576
|
+
setState({
|
|
577
|
+
isVerified: true,
|
|
578
|
+
isLoading: false,
|
|
579
|
+
error: null,
|
|
580
|
+
sessionId: 'dev-disabled',
|
|
581
|
+
});
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
21
584
|
// Set tampering detection marker
|
|
22
585
|
sessionStorage.setItem('spektre_js_enabled', 'true');
|
|
23
586
|
sessionStorage.setItem('spektre_init_time', Date.now().toString());
|
|
@@ -432,6 +995,11 @@ const useProtectedFetch = () => {
|
|
|
432
995
|
return protectedFetch;
|
|
433
996
|
};
|
|
434
997
|
|
|
998
|
+
// Check if we're in an iframe (development environment) and initialize dev UI
|
|
999
|
+
if (typeof window !== 'undefined' && window.self !== window.top) {
|
|
1000
|
+
initializeDevUI();
|
|
1001
|
+
}
|
|
1002
|
+
|
|
435
1003
|
exports.SecurityGate = SecurityGate;
|
|
436
1004
|
exports.SpektreContext = SpektreContext;
|
|
437
1005
|
exports.SpektreProvider = SpektreProvider;
|
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|