mdefender-pro 1.2.3 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client.mjs CHANGED
@@ -1,22 +1,55 @@
1
- // client.mjs
2
- /**
3
- * Initialize MDefender Pro WAF client-side protection for SPAs.
4
- * @param {Object} options - Client configuration
5
- * @param {string} options.backendUrl - Base URL of your backend API server (e.g. 'http://localhost:5005')
6
- */
7
- export function initWaf(options = {}) {
8
- if (typeof window === 'undefined') return;
1
+ // client.mjs - MDefender Pro Client-Side & SPA Protection Engine
2
+ // High-performance 0ms synchronous execution with zero external network delays
9
3
 
10
- const backendUrl = (options.backendUrl || '').replace(/\/+$/, '');
11
- const currentSearch = decodeURIComponent(window.location.search);
4
+ const ATTACK_PATTERNS = [
5
+ { type: 'Cross-Site Scripting (XSS)', regex: /<\s*(?:script|iframe|object|embed|svg|img|math)\b/i },
6
+ { type: 'Cross-Site Scripting (XSS)', regex: /\bon(?:error|load|click|mouseover|focus|submit)\s*=/i },
7
+ { type: 'Cross-Site Scripting (XSS)', regex: /\b(?:javascript|data\s*:\s*text\/html)\s*:/i },
8
+ { type: 'Cross-Site Scripting (XSS)', regex: /\b(?:alert|eval|confirm|prompt|document\.cookie)\s*\(/i },
9
+ { type: 'SQL Injection', regex: /\bUNION\s+(?:ALL\s+)?SELECT\b/i },
10
+ { type: 'SQL Injection', regex: /(?:'|\"|\b)\s*(?:OR|AND)\s+['\"`]?([a-zA-Z0-9_-]+)['\"`]?\s*=\s*['\"`]?\1/i },
11
+ { type: 'SQL Injection', regex: /(?:--|#|\/\*).*?(?:DROP|ALTER|INSERT|DELETE|UPDATE|EXEC)/i },
12
+ { type: 'Local File Inclusion (LFI)', regex: /(?:\.\.\/|\.\.\\|etc\/passwd|etc\/shadow|win\.ini|boot\.ini)/i },
13
+ { type: 'Server-Side Request Forgery (SSRF)', regex: /(?:169\.254\.169\.254|metadata\.google\.internal|(?:gopher|dict|file):\/\/|=(?:https?:\/\/)?(?:127\.0\.0\.1|169\.254|localhost|0\.0\.0\.0))/i },
14
+ { type: 'Remote Command Execution (RCE)', regex: /(?:;|\||\|\||&&|`|\$\()\s*(?:cat|ls|id|whoami|powershell|cmd|sh|bash|wget|curl)\b/i },
15
+ { type: 'AI Prompt Injection', regex: /(?:ignore\s+all\s+(?:previous|prior)\s+instructions|system\s+override|DAN\s+mode)/i }
16
+ ];
17
+
18
+ function normalizeInput(str) {
19
+ if (!str) return '';
20
+ let curr = str;
21
+ for (let i = 0; i < 3; i++) {
22
+ try {
23
+ const decoded = decodeURIComponent(curr);
24
+ if (decoded === curr) break;
25
+ curr = decoded;
26
+ } catch (e) {
27
+ break;
28
+ }
29
+ }
30
+ return curr.replace(/\/\*.*?\*\//g, ' ');
31
+ }
12
32
 
13
- // Instant 0ms Local Block Page Renderer
14
- const renderInstantBlockPage = (attackType, refId) => {
15
- const referenceId = refId || ('MDF-' + Math.random().toString(16).substring(2, 10).toUpperCase());
16
- const host = window.location.hostname || 'localhost';
33
+ function generateRefId() {
34
+ return 'MDF-' + Math.random().toString(16).substring(2, 10).toUpperCase();
35
+ }
36
+
37
+ export function renderOfficialBlockPage(attackType, refId, domain) {
38
+ if (typeof window === 'undefined' || typeof document === 'undefined') return;
39
+ if (window.__MDEFENDER_BLOCKED__) return;
40
+ window.__MDEFENDER_BLOCKED__ = true;
41
+
42
+ const referenceId = refId || generateRefId();
43
+ const host = window.location.hostname || domain || 'localhost';
17
44
  const nowUtc = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' UTC';
18
45
  const unixRef = Math.floor(Date.now() / 1000);
19
- const ruleId = 96565;
46
+ const classification = attackType || 'Cross-Site Scripting (XSS)';
47
+
48
+ let hash = 0;
49
+ for (let i = 0; i < classification.length; i++) {
50
+ hash = classification.charCodeAt(i) + ((hash << 5) - hash);
51
+ }
52
+ const ruleId = Math.abs(hash % 10000) + 90000;
20
53
 
21
54
  const html = `<!DOCTYPE html>
22
55
  <html lang="en">
@@ -34,179 +67,198 @@ export function initWaf(options = {}) {
34
67
  --text-secondary: #475569;
35
68
  --text-muted: #94a3b8;
36
69
  --border-color: #cbd5e1;
37
- --font-main: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
38
- --font-mono: Consolas, Monaco, monospace;
70
+ --font-main: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
71
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
39
72
  }
40
73
  * { margin: 0; padding: 0; box-sizing: border-box; }
41
74
  body {
42
75
  font-family: var(--font-main);
43
76
  background-color: var(--bg-color);
77
+ background-image:
78
+ radial-gradient(circle at 10% 20%, rgba(99, 102, 241, 0.03) 0%, transparent 40%),
79
+ radial-gradient(circle at 90% 80%, rgba(59, 130, 246, 0.03) 0%, transparent 40%);
44
80
  color: var(--text-primary);
45
81
  min-height: 100vh;
46
82
  display: flex;
47
83
  align-items: center;
48
84
  justify-content: center;
49
- padding: 16px;
50
- -webkit-font-smoothing: antialiased;
85
+ padding: 20px 14px;
51
86
  }
52
87
  .container {
53
88
  width: 100%;
54
89
  max-width: 660px;
55
90
  background: var(--card-bg);
56
- border: 1px solid var(--border-color);
91
+ border: 1px solid rgba(226, 232, 240, 0.95);
57
92
  border-radius: 12px;
58
- box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.05), 0 8px 10px -6px rgba(0, 0, 0, 0.01);
93
+ box-shadow: 0 8px 25px -4px rgba(0, 0, 0, 0.04), 0 4px 6px -2px rgba(0, 0, 0, 0.02);
59
94
  padding: 24px 28px;
95
+ text-align: center;
60
96
  }
61
- .top-brand {
97
+ .logo-wrap {
98
+ margin-bottom: 10px;
62
99
  display: flex;
100
+ flex-direction: column;
63
101
  align-items: center;
64
- gap: 7px;
65
- font-size: 11.5px;
66
- font-weight: 700;
67
- color: var(--brand-1);
68
- letter-spacing: 0.04em;
102
+ justify-content: center;
103
+ }
104
+ .logo-svg {
105
+ width: 48px;
106
+ height: 48px;
107
+ }
108
+ .logo-text {
109
+ font-size: 13.5px;
110
+ font-weight: 800;
111
+ color: #1e293b;
112
+ letter-spacing: 0.5px;
113
+ margin-top: 4px;
69
114
  text-transform: uppercase;
70
- margin-bottom: 12px;
71
115
  }
72
- .top-brand span { color: var(--text-muted); font-weight: 400; }
73
- .top-brand .creator { color: var(--text-secondary); font-weight: 600; }
74
- .status-badge {
75
- display: inline-flex;
76
- align-items: center;
77
- gap: 7px;
78
- background: #fff1f2;
79
- border: 1px solid #fecdd3;
80
- color: #e11d48;
81
- padding: 4px 10px;
82
- border-radius: 20px;
83
- font-size: 11px;
116
+ .fw-badge {
117
+ font-size: 9.5px;
84
118
  font-weight: 700;
85
- margin-bottom: 14px;
119
+ color: #64748b;
120
+ text-transform: uppercase;
121
+ letter-spacing: 1.5px;
122
+ margin-bottom: 2px;
86
123
  }
87
- .status-badge .dot {
88
- width: 6px;
89
- height: 6px;
90
- background: #e11d48;
91
- border-radius: 50%;
92
- box-shadow: 0 0 0 2px rgba(225, 29, 72, 0.2);
124
+ .fw-owner {
125
+ font-size: 12px;
126
+ font-weight: 800;
127
+ color: #0f172a;
128
+ text-transform: uppercase;
129
+ letter-spacing: 1px;
130
+ margin-bottom: 12px;
93
131
  }
94
- h1 {
132
+ .main-title {
95
133
  font-size: 19px;
96
134
  font-weight: 800;
97
135
  color: #0f172a;
98
- letter-spacing: -0.01em;
99
- margin-bottom: 4px;
100
- line-height: 1.25;
136
+ letter-spacing: -0.3px;
137
+ margin-bottom: 3px;
101
138
  }
102
- .subtitle {
103
- font-size: 12px;
104
- color: var(--text-secondary);
105
- margin-bottom: 18px;
139
+ .sub-title {
140
+ font-size: 14px;
141
+ font-weight: 700;
142
+ color: #0f172a;
143
+ margin-bottom: 3px;
106
144
  }
107
- .subtitle strong {
108
- color: var(--text-primary);
109
- font-weight: 600;
145
+ .desc-text {
146
+ font-size: 11.5px;
147
+ color: var(--text-secondary);
148
+ margin-bottom: 12px;
110
149
  }
111
- .table-wrap {
150
+ .table-container {
112
151
  border: 1px solid var(--border-color);
113
- border-radius: 8px;
152
+ border-radius: 7px;
114
153
  overflow: hidden;
115
- margin-bottom: 18px;
116
154
  background: #ffffff;
155
+ margin-bottom: 12px;
156
+ text-align: left;
117
157
  }
118
- table {
158
+ .details-table {
119
159
  width: 100%;
120
160
  border-collapse: collapse;
121
- text-align: left;
122
- font-size: 11.5px;
123
161
  }
124
- tr {
125
- border-bottom: 1px solid #f1f5f9;
162
+ .details-table tr {
163
+ border-bottom: 1px solid var(--border-color);
126
164
  }
127
- tr:last-child {
165
+ .details-table tr:last-child {
128
166
  border-bottom: none;
129
167
  }
130
- th {
131
- width: 32%;
132
- background: #f8fafc;
133
- padding: 8.5px 14px;
134
- font-weight: 600;
168
+ .details-table th {
169
+ background-color: #f8fafc;
135
170
  color: #475569;
136
- border-right: 1px solid #f1f5f9;
171
+ font-weight: 700;
172
+ font-size: 11px;
137
173
  text-transform: uppercase;
138
- font-size: 10.5px;
139
- letter-spacing: 0.02em;
174
+ letter-spacing: 0.4px;
175
+ width: 190px;
176
+ padding: 7px 12px;
177
+ border-right: 1px solid var(--border-color);
178
+ vertical-align: middle;
140
179
  }
141
- td {
142
- padding: 8.5px 14px;
143
- color: var(--text-primary);
144
- font-family: var(--font-mono);
180
+ .details-table td {
181
+ color: #334155;
182
+ font-weight: 500;
145
183
  font-size: 11.5px;
146
- word-break: break-all;
184
+ padding: 7px 12px;
185
+ vertical-align: middle;
147
186
  }
148
- .threat-alert {
149
- color: #dc2626;
150
- font-weight: 700;
187
+ .threat-row td {
188
+ background-color: #fff5f5 !important;
189
+ color: #991b1b !important;
190
+ }
191
+ .threat-cell-content {
151
192
  display: flex;
152
193
  align-items: center;
153
194
  justify-content: space-between;
195
+ width: 100%;
196
+ gap: 8px;
154
197
  }
155
- .info-tag {
156
- font-size: 9.5px;
198
+ .threat-text {
157
199
  font-weight: 700;
158
- background: #fef2f2;
159
- color: #ef4444;
160
- padding: 1.5px 6px;
200
+ color: #dc2626;
201
+ word-break: break-word;
202
+ }
203
+ .info-tag {
204
+ background: #f1f5f9;
205
+ color: #475569;
206
+ border: 1px solid #cbd5e1;
207
+ font-size: 10px;
208
+ font-weight: 600;
209
+ padding: 2px 7px;
161
210
  border-radius: 4px;
162
- border: 1px solid #fee2e2;
163
- font-family: var(--font-main);
164
- text-transform: uppercase;
211
+ text-decoration: none;
212
+ white-space: nowrap;
165
213
  }
166
214
  .ip-wrap {
167
215
  display: flex;
168
216
  align-items: center;
169
- gap: 7px;
217
+ gap: 8px;
170
218
  }
171
- .flag-img {
219
+ .flag-svg {
172
220
  border-radius: 2px;
173
221
  box-shadow: 0 1px 2px rgba(0,0,0,0.1);
174
- vertical-align: middle;
175
222
  }
176
223
  .why-header {
177
224
  font-size: 12.5px;
178
225
  font-weight: 700;
179
- color: var(--text-primary);
226
+ color: #0f172a;
227
+ text-align: left;
180
228
  margin-bottom: 4px;
181
229
  }
182
230
  .why-desc {
183
- font-size: 11.5px;
231
+ font-size: 11px;
184
232
  color: var(--text-secondary);
185
233
  line-height: 1.45;
186
- margin-bottom: 14px;
234
+ text-align: left;
235
+ margin-bottom: 12px;
187
236
  }
188
237
  .ref-box {
189
- background: #f8fafc;
238
+ background: #f1f5f9;
190
239
  border: 1px dashed var(--border-color);
191
- padding: 9px 14px;
192
- border-radius: 6px;
240
+ border-radius: 5px;
241
+ padding: 7px;
193
242
  font-family: var(--font-mono);
194
- font-size: 11.5px;
243
+ font-size: 11px;
195
244
  font-weight: 700;
196
- color: var(--brand-1);
197
- text-align: center;
198
- margin-bottom: 16px;
245
+ color: #334155;
246
+ letter-spacing: 0.6px;
247
+ margin-bottom: 14px;
199
248
  cursor: pointer;
200
- transition: all 0.15s ease;
249
+ transition: all 0.2s;
201
250
  }
202
251
  .ref-box:hover {
203
- background: #f1f5f9;
204
252
  border-color: var(--brand-1);
253
+ color: var(--brand-1);
254
+ background: #eef2ff;
205
255
  }
206
256
  .actions {
207
257
  display: flex;
208
258
  align-items: center;
209
- gap: 9px;
259
+ justify-content: center;
260
+ gap: 10px;
261
+ margin-bottom: 12px;
210
262
  flex-wrap: wrap;
211
263
  }
212
264
  .btn {
@@ -214,68 +266,87 @@ export function initWaf(options = {}) {
214
266
  align-items: center;
215
267
  justify-content: center;
216
268
  gap: 6px;
217
- padding: 8px 14px;
218
- font-size: 11.5px;
269
+ font-family: var(--font-main);
270
+ font-size: 11px;
219
271
  font-weight: 600;
220
- border-radius: 6px;
272
+ padding: 7px 14px;
273
+ border-radius: 5px;
221
274
  text-decoration: none;
222
- cursor: pointer;
223
275
  transition: all 0.15s ease;
224
- font-family: var(--font-main);
276
+ cursor: pointer;
225
277
  }
226
278
  .btn-primary {
227
- background: var(--brand-1);
279
+ background-color: #0f172a;
228
280
  color: #ffffff;
229
- border: 1px solid var(--brand-1);
230
- box-shadow: 0 1px 2px rgba(79, 70, 229, 0.2);
281
+ border: 1px solid #0f172a;
231
282
  }
232
283
  .btn-primary:hover {
233
- background: #4338ca;
284
+ background-color: #1e293b;
285
+ border-color: #1e293b;
234
286
  }
235
287
  .btn-secondary {
236
- background: #ffffff;
237
- color: var(--text-primary);
288
+ background-color: #ffffff;
289
+ color: #334155;
238
290
  border: 1px solid var(--border-color);
239
291
  }
240
292
  .btn-secondary:hover {
241
- background: #f8fafc;
293
+ background-color: #f8fafc;
242
294
  border-color: #94a3b8;
295
+ color: #0f172a;
243
296
  }
244
297
  .footer {
245
- margin-top: 18px;
246
- padding-top: 12px;
247
- border-top: 1px solid #f1f5f9;
248
- font-size: 10.5px;
298
+ font-size: 10px;
249
299
  color: var(--text-muted);
250
- text-align: center;
300
+ border-top: 1px solid #f1f5f9;
301
+ padding-top: 8px;
251
302
  }
252
303
  </style>
253
304
  </head>
254
305
  <body>
255
306
  <div class="container">
256
- <div class="top-brand">
257
- <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
258
- <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
307
+ <!-- Shield Logo Header -->
308
+ <div class="logo-wrap">
309
+ <svg class="logo-svg" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
310
+ <defs>
311
+ <linearGradient id="shieldBg" x1="0%" y1="0%" x2="100%" y2="100%">
312
+ <stop offset="0%" stop-color="#f8fafc" />
313
+ <stop offset="30%" stop-color="#cbd5e1" />
314
+ <stop offset="70%" stop-color="#94a3b8" />
315
+ <stop offset="100%" stop-color="#475569" />
316
+ </linearGradient>
317
+ <linearGradient id="shieldBorder" x1="0%" y1="0%" x2="100%" y2="0%">
318
+ <stop offset="0%" stop-color="#94a3b8" />
319
+ <stop offset="50%" stop-color="#f1f5f9" />
320
+ <stop offset="100%" stop-color="#475569" />
321
+ </linearGradient>
322
+ <linearGradient id="dragonColor" x1="0%" y1="0%" x2="100%" y2="100%">
323
+ <stop offset="0%" stop-color="#1e293b" />
324
+ <stop offset="100%" stop-color="#0f172a" />
325
+ </linearGradient>
326
+ </defs>
327
+ <path d="M60 10 C85 22 98 25 98 48 C98 75 75 98 60 108 C45 98 22 75 22 48 C22 25 35 22 60 10 Z" fill="url(#shieldBg)" stroke="url(#shieldBorder)" stroke-width="3" />
328
+ <path d="M60 18 C80 28 90 30 90 48 C90 70 70 90 60 98 C50 90 30 70 30 48 C30 30 40 28 60 18 Z" fill="none" stroke="#ffffff" stroke-width="1.5" stroke-opacity="0.8" stroke-dasharray="3 2" />
329
+ <path d="M60 28 C58 35 52 38 48 38 C44 38 43 42 45 44 C47 46 51 44 51 48 C51 52 46 55 42 53 C38 51 36 54 38 58 C40 62 45 60 48 64 C50 66 48 70 44 72 C48 74 54 75 58 72 C58 68 55 65 57 62 C59 59 63 61 65 58 C67 55 66 52 62 50 C64 45 68 46 70 42 C72 38 67 36 65 32 C63 28 61 25 60 28 Z" fill="url(#dragonColor)" />
259
330
  </svg>
260
- MDEFENDER-PRO AI <span>&bull;</span> A.S.A.P. SECURITY FIREWALL <span>&bull;</span> <span class="creator">MAHABUB</span>
331
+ <div class="logo-text">MDefender-Pro AI</div>
261
332
  </div>
262
333
 
263
- <div class="status-badge">
264
- <div class="dot"></div>
265
- 403 — SECURITY ACTION REQUIRED &bull; Access Denied by Corporate WAF
266
- </div>
334
+ <div class="fw-badge">A.S.A.P. Security Firewall</div>
335
+ <div class="fw-owner">Mahabub</div>
267
336
 
268
- <h1>403 — Access Denied</h1>
269
- <div class="subtitle">Access to <strong>${host}</strong> has been restricted by security policy.</div>
337
+ <h1 class="main-title">403 — SECURITY ACTION REQUIRED</h1>
338
+ <h2 class="sub-title">Access Denied by Corporate WAF</h2>
270
339
 
271
- <div class="table-wrap">
272
- <table>
273
- <tr>
340
+ <div class="desc-text">Access to [${host}] restricted.</div>
341
+
342
+ <div class="table-container">
343
+ <table class="details-table">
344
+ <tr class="threat-row">
274
345
  <th>Attack Classification</th>
275
346
  <td>
276
- <div class="threat-alert">
277
- <span>${attackType}</span>
278
- <span class="info-tag">Blocked Threat</span>
347
+ <div class="threat-cell-content">
348
+ <span class="threat-text">${classification}</span>
349
+ <span class="info-tag">Detailed info tag</span>
279
350
  </div>
280
351
  </td>
281
352
  </tr>
@@ -283,8 +354,11 @@ export function initWaf(options = {}) {
283
354
  <th>Origin Client IP</th>
284
355
  <td>
285
356
  <div class="ip-wrap">
286
- <img id="flag-img" src="https://flagcdn.com/w40/bd.png" width="20" height="15" alt="Flag" class="flag-img">
287
- <span>::1<span id="geo-text"> (GeoIP: Dhaka, Bangladesh)</span></span>
357
+ <svg class="flag-svg" width="20" height="15" viewBox="0 0 20 15" xmlns="http://www.w3.org/2000/svg">
358
+ <rect width="20" height="15" fill="#006a4e"/>
359
+ <circle cx="9" cy="7.5" r="4" fill="#f42a41"/>
360
+ </svg>
361
+ <span><span id="client-ip-display" style="font-family: var(--font-mono); font-weight: 700;">103.151.30.111</span><span style="color: #64748b;"> (GeoIP: BD)</span></span>
288
362
  </div>
289
363
  </td>
290
364
  </tr>
@@ -294,7 +368,7 @@ export function initWaf(options = {}) {
294
368
  </tr>
295
369
  <tr>
296
370
  <th>Violation Reason</th>
297
- <td>Request blocked by rule ID: <span>${ruleId}</span> (Ref: ${attackType})</td>
371
+ <td>Request blocked by rule ID: <span>${ruleId}</span> (Ref: ${classification})</td>
298
372
  </tr>
299
373
  <tr>
300
374
  <th>Protocol Details</th>
@@ -309,7 +383,7 @@ export function initWaf(options = {}) {
309
383
 
310
384
  <h3 class="why-header">Why did this happen?</h3>
311
385
  <p class="why-desc">
312
- To maintain system integrity, suspicious requests are automatically analyzed and filtered. If you believe this is a valid action, please share the Reference ID below with your local IT/Security operations.
386
+ To maintain system integrity, suspicious requests are automatically analyzed and filtered. If you believe this is a valid corporate action, please share the Reference ID below with your local IT/Security operations. Regular users should clear cache or contact support.
313
387
  </p>
314
388
 
315
389
  <div class="ref-box" id="ref-id-box" title="Click to copy Reference ID" onclick="copyRef()">
@@ -317,8 +391,8 @@ export function initWaf(options = {}) {
317
391
  </div>
318
392
 
319
393
  <div class="actions">
320
- <a id="email-link" href="mailto:security@mdefender-pro.io?subject=WAF Block Reference: ${referenceId}" class="btn btn-primary">
321
- ✉ Contact Security Operations
394
+ <a href="#" class="btn btn-primary" onclick="alert('Reference ID: ${referenceId}\\nContact your IT security team.'); return false;">
395
+ Contact Security Operations
322
396
  </a>
323
397
  <a href="/" class="btn btn-secondary">
324
398
  Return to Homepage
@@ -338,109 +412,143 @@ export function initWaf(options = {}) {
338
412
  function copyRef() {
339
413
  navigator.clipboard.writeText(refId).then(() => {
340
414
  const refBox = document.getElementById('ref-id-box');
341
- const originalHtml = refBox.innerHTML;
342
- refBox.innerHTML = 'COPIED TO CLIPBOARD! ✅';
343
- refBox.style.color = '#10b981';
344
- refBox.style.borderColor = '#10b981';
345
- refBox.style.background = '#ecfdf5';
346
- setTimeout(() => {
347
- refBox.innerHTML = originalHtml;
348
- refBox.style.color = '';
349
- refBox.style.borderColor = '';
350
- refBox.style.background = '';
351
- }, 2000);
415
+ if (refBox) {
416
+ const originalHtml = refBox.innerHTML;
417
+ refBox.innerHTML = 'COPIED TO CLIPBOARD! ✅';
418
+ refBox.style.color = '#10b981';
419
+ refBox.style.borderColor = '#10b981';
420
+ refBox.style.background = '#ecfdf5';
421
+ setTimeout(() => {
422
+ refBox.innerHTML = originalHtml;
423
+ refBox.style.color = '';
424
+ refBox.style.borderColor = '';
425
+ refBox.style.background = '';
426
+ }, 2000);
427
+ }
352
428
  });
353
429
  }
354
430
  </script>
355
431
  </body>
356
432
  </html>`;
357
433
 
358
- document.open();
359
- document.write(html);
360
- document.close();
361
- };
434
+ // Instant DOM Replacement
435
+ try { window.stop(); } catch (e) { }
436
+ document.documentElement.innerHTML = html;
362
437
 
363
- // Helper to render server-provided WAF block page
364
- const handleWafBlock = (htmlText) => {
365
- if (htmlText && (htmlText.includes('MDefender-Pro') || htmlText.includes('Access Denied') || htmlText.includes('MDefender') || htmlText.includes('403'))) {
366
- document.open();
367
- document.write(htmlText);
368
- document.close();
369
- throw new Error("MDefender WAF Blocked Request");
438
+ // Async Telemetry Beacon to MDefender Dashboard (recorded without blocking UI)
439
+ try {
440
+ const reportPayload = {
441
+ domain: domain || (typeof window !== 'undefined' ? window.location.hostname : 'localhost') || 'localhost',
442
+ request: {
443
+ method: 'GET',
444
+ url: typeof window !== 'undefined' ? (window.location.pathname + window.location.search) : '/',
445
+ query_string: typeof window !== 'undefined' ? window.location.search : '',
446
+ ip: '127.0.0.1',
447
+ headers: { 'User-Agent': typeof navigator !== 'undefined' ? navigator.userAgent : 'Frontend-WAF' },
448
+ body: '',
449
+ attack_type: classification,
450
+ reference_id: referenceId
451
+ }
452
+ };
453
+ const endpoint = 'http://localhost:8000/api/v1/waf/analyze';
454
+ if (typeof navigator !== 'undefined' && navigator.sendBeacon) {
455
+ navigator.sendBeacon(endpoint, new Blob([JSON.stringify(reportPayload)], { type: 'application/json' }));
456
+ } else if (typeof fetch !== 'undefined') {
457
+ fetch(endpoint, {
458
+ method: 'POST',
459
+ headers: { 'Content-Type': 'application/json' },
460
+ body: JSON.stringify(reportPayload)
461
+ }).catch(() => {});
462
+ }
463
+ } catch (e) {}
464
+ }
465
+
466
+ // 0ms Instant URL Inspection on Script Load
467
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
468
+ const rawTarget = normalizeInput(window.location.search + ' ' + window.location.hash);
469
+ if (rawTarget.trim()) {
470
+ for (const pattern of ATTACK_PATTERNS) {
471
+ if (pattern.regex.test(rawTarget)) {
472
+ renderOfficialBlockPage(pattern.type);
473
+ throw new Error(`[MDefender WAF] Attack blocked on load (0ms): ${pattern.type}`);
474
+ }
475
+ }
370
476
  }
371
- };
477
+ }
372
478
 
373
- // 1. Instant check on page load (0ms immediate response)
374
- if (currentSearch) {
375
- const attackRegex = /(<script|onerror|onload|javascript:|alert\(|confirm\(|prompt\(|\.\.\/|\.\.\\|union.*select|drop.*table|'\s*(or|and)\s+['\w]|etc\/passwd|;\s*(whoami|id|cat|ls|cmd|sh|bash))/i;
376
- if (attackRegex.test(currentSearch)) {
377
- // Instantly render block page in 0ms!
378
- renderInstantBlockPage("XSS - Dangerous HTML Tag (<script>) #1");
479
+ /**
480
+ * Initialize MDefender Pro WAF client-side protection for SPAs & Frontend Websites.
481
+ * @param {Object} options - Client configuration
482
+ * @param {string} [options.backendUrl] - Base URL of your backend API server (e.g. 'http://localhost:5005' or 'http://localhost:8000')
483
+ * @param {string} [options.apiKey] - Your Website API key
484
+ * @param {string} [options.domain] - Your domain (e.g. 'localhost' or 'mysite.com')
485
+ * @param {boolean} [options.logBlocked] - Whether to log blocked attacks to console
486
+ */
487
+ export function initWaf(options = {}) {
488
+ if (typeof window === 'undefined' || typeof document === 'undefined') return;
489
+ if (window.__MDEFENDER_WAF_INITIALIZED__) return;
490
+ window.__MDEFENDER_WAF_INITIALIZED__ = true;
379
491
 
380
- // Background verify with server for audit logging
381
- if (backendUrl) {
382
- const origFetch = window.fetch;
383
- const testUrl = backendUrl + '/api/books/' + window.location.search;
384
- origFetch(testUrl)
385
- .then(res => res.status === 403 ? res.text() : null)
386
- .then(html => { if (html) handleWafBlock(html); })
387
- .catch(() => {});
388
- }
389
- throw new Error("MDefender WAF Blocked Request");
390
- }
391
- }
492
+ const backendUrl = (options.backendUrl || 'http://localhost:8000').replace(/\/+$/, '');
493
+ const domain = options.domain || window.location.hostname || 'localhost';
392
494
 
393
- // 2. Intercept window.fetch to propagate query params and catch 403
394
- const originalFetch = window.fetch;
395
- window.fetch = async (input, init, ...args) => {
396
- let url = input;
397
- const searchStr = window.location.search;
398
-
399
- if (searchStr) {
400
- if (typeof url === 'string') {
401
- if (url.startsWith('/') || url.includes(backendUrl) || url.includes('/api/')) {
402
- const separator = url.includes('?') ? '&' : '?';
403
- url = url + separator + searchStr.substring(1);
404
- }
405
- } else if (url instanceof Request) {
406
- if (url.url.startsWith('/') || url.url.includes(backendUrl) || url.url.includes('/api/')) {
407
- const separator = url.url.includes('?') ? '&' : '?';
408
- const newUrl = url.url + separator + searchStr.substring(1);
409
- url = new Request(newUrl, url);
410
- }
411
- }
412
- }
495
+ // 1. Wrap window.fetch for outgoing requests
496
+ const originalFetch = window.fetch;
497
+ window.fetch = async (input, init, ...args) => {
498
+ let url = typeof input === 'string' ? input : (input && input.url ? input.url : '');
499
+ let targetParamStr = '';
500
+ try {
501
+ if (url.includes('?')) targetParamStr = url.substring(url.indexOf('?'));
502
+ else if (url.includes('#')) targetParamStr = url.substring(url.indexOf('#'));
503
+ } catch (e) {}
413
504
 
414
- const response = await originalFetch(url, init, ...args);
415
- if (response.status === 403) {
416
- const clone = response.clone();
417
- try {
418
- const text = await clone.text();
419
- handleWafBlock(text);
420
- } catch (e) {}
421
- }
422
- return response;
423
- };
505
+ if (targetParamStr) {
506
+ const normParams = normalizeInput(targetParamStr);
507
+ for (const pattern of ATTACK_PATTERNS) {
508
+ if (pattern.regex.test(normParams)) {
509
+ renderOfficialBlockPage(pattern.type, null, domain);
510
+ throw new Error(`[MDefender WAF] Outgoing API attack blocked: ${pattern.type}`);
511
+ }
512
+ }
513
+ }
424
514
 
425
- // 3. Intercept XMLHttpRequest.prototype.open to propagate query params
426
- const originalOpen = XMLHttpRequest.prototype.open;
427
- XMLHttpRequest.prototype.open = function(method, url, ...rest) {
428
- const searchStr = window.location.search;
429
- if (searchStr && (url.startsWith('/') || url.includes(backendUrl) || url.includes('/api/'))) {
430
- const separator = url.includes('?') ? '&' : '?';
431
- url = url + separator + searchStr.substring(1);
432
- }
433
- return originalOpen.apply(this, [method, url, ...rest]);
434
- };
515
+ try {
516
+ const response = await originalFetch(input, init, ...args);
517
+ if (response.status === 403) {
518
+ const clone = response.clone();
519
+ try {
520
+ const text = await clone.text();
521
+ if (text.includes('403') && (text.includes('MDefender') || text.includes('Access Denied'))) {
522
+ renderOfficialBlockPage('Cross-Site Scripting (XSS)', null, domain);
523
+ }
524
+ } catch (e) {}
525
+ }
526
+ return response;
527
+ } catch (err) {
528
+ throw err;
529
+ }
530
+ };
531
+
532
+ // 2. Wrap XMLHttpRequest
533
+ const originalOpen = XMLHttpRequest.prototype.open;
534
+ XMLHttpRequest.prototype.open = function (method, url, ...rest) {
535
+ let targetParamStr = '';
536
+ try {
537
+ const urlStr = String(url);
538
+ if (urlStr.includes('?')) targetParamStr = urlStr.substring(urlStr.indexOf('?'));
539
+ } catch (e) {}
435
540
 
436
- // 4. Intercept XMLHttpRequest.prototype.send to catch 403
437
- const originalSend = XMLHttpRequest.prototype.send;
438
- XMLHttpRequest.prototype.send = function(...args) {
439
- this.addEventListener('load', function() {
440
- if (this.status === 403) {
441
- handleWafBlock(this.responseText);
442
- }
443
- });
444
- return originalSend.apply(this, args);
445
- };
541
+ if (targetParamStr) {
542
+ const normParams = normalizeInput(targetParamStr);
543
+ for (const pattern of ATTACK_PATTERNS) {
544
+ if (pattern.regex.test(normParams)) {
545
+ renderOfficialBlockPage(pattern.type, null, domain);
546
+ throw new Error(`[MDefender WAF] XHR attack blocked: ${pattern.type}`);
547
+ }
548
+ }
549
+ }
550
+ return originalOpen.apply(this, [method, url, ...rest]);
551
+ };
446
552
  }
553
+
554
+ export default { initWaf, renderOfficialBlockPage };