mdefender-pro 1.1.0 → 1.2.1
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/README.md +110 -138
- package/bin/mdefender.js +80 -0
- package/block-page.html +541 -0
- package/client.mjs +446 -0
- package/config-loader.js +35 -35
- package/index.d.ts +37 -37
- package/index.js +228 -173
- package/package.json +57 -46
- package/vite.js +18 -0
- package/vite.mjs +17 -0
package/index.js
CHANGED
|
@@ -1,40 +1,84 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const http = require('http');
|
|
4
|
+
const https = require('https');
|
|
4
5
|
const path = require('path');
|
|
5
6
|
const fs = require('fs');
|
|
6
7
|
const crypto = require('crypto');
|
|
8
|
+
const { URL } = require('url');
|
|
9
|
+
|
|
10
|
+
const httpAgent = new http.Agent({ keepAlive: true, timeout: 5000 });
|
|
11
|
+
const httpsAgent = new https.Agent({ keepAlive: true, timeout: 5000 });
|
|
12
|
+
|
|
13
|
+
// In-memory cache for bundled block page with mtime check
|
|
14
|
+
let cachedDefaultTemplate = null;
|
|
15
|
+
let cachedTemplateMtime = 0;
|
|
16
|
+
|
|
17
|
+
function getDefaultTemplate() {
|
|
18
|
+
try {
|
|
19
|
+
const templatePath = path.join(__dirname, 'block-page.html');
|
|
20
|
+
if (fs.existsSync(templatePath)) {
|
|
21
|
+
const stats = fs.statSync(templatePath);
|
|
22
|
+
if (cachedDefaultTemplate === null || stats.mtimeMs > cachedTemplateMtime) {
|
|
23
|
+
cachedDefaultTemplate = fs.readFileSync(templatePath, 'utf8');
|
|
24
|
+
cachedTemplateMtime = stats.mtimeMs;
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
cachedDefaultTemplate = '';
|
|
28
|
+
}
|
|
29
|
+
} catch (e) {
|
|
30
|
+
if (!cachedDefaultTemplate) cachedDefaultTemplate = '';
|
|
31
|
+
}
|
|
32
|
+
return cachedDefaultTemplate;
|
|
33
|
+
}
|
|
7
34
|
|
|
8
35
|
const DEFAULT_CONFIG = {
|
|
9
36
|
apiKey: '',
|
|
10
37
|
domain: '',
|
|
11
|
-
apiEndpoint: '
|
|
38
|
+
apiEndpoint: 'http://127.0.0.1:8000',
|
|
12
39
|
mode: 'block', // 'block' | 'monitor' | 'off'
|
|
13
40
|
blockStatusCode: 403,
|
|
14
41
|
timeout: 5000,
|
|
15
42
|
maxBodySize: 1024 * 1024, // 1MB
|
|
16
43
|
logBlocked: true,
|
|
17
|
-
customBlockPage: null,
|
|
44
|
+
customBlockPage: null,
|
|
18
45
|
skipPaths: ['/health', '/favicon.ico'],
|
|
19
46
|
skipUserAgents: [],
|
|
20
47
|
skipMethods: [],
|
|
21
|
-
headers: true,
|
|
22
|
-
onError: 'allow',
|
|
48
|
+
headers: true,
|
|
49
|
+
onError: 'allow', // 'allow' | 'block'
|
|
23
50
|
};
|
|
24
51
|
|
|
25
52
|
function loadConfig(overrides = {}) {
|
|
26
53
|
let fileConfig = {};
|
|
27
54
|
|
|
55
|
+
// Try config-loader
|
|
56
|
+
try {
|
|
57
|
+
const configLoader = require('./config-loader');
|
|
58
|
+
const loaded = configLoader.load();
|
|
59
|
+
if (loaded) fileConfig = loaded;
|
|
60
|
+
} catch (e) {}
|
|
61
|
+
|
|
28
62
|
// Try mdefender.config.js
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
63
|
+
if (!fileConfig.apiKey) {
|
|
64
|
+
const jsPath = path.resolve(process.cwd(), 'mdefender.config.js');
|
|
65
|
+
if (fs.existsSync(jsPath)) {
|
|
66
|
+
try {
|
|
67
|
+
fileConfig = require(jsPath);
|
|
68
|
+
} catch (e) {
|
|
69
|
+
console.warn('[MDefender] Failed reading mdefender.config.js:', e.message);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
32
72
|
}
|
|
33
73
|
|
|
34
74
|
// Try mdefender.json
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
75
|
+
if (!fileConfig.apiKey) {
|
|
76
|
+
const jsonPath = path.resolve(process.cwd(), 'mdefender.json');
|
|
77
|
+
if (fs.existsSync(jsonPath)) {
|
|
78
|
+
try {
|
|
79
|
+
fileConfig = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
|
|
80
|
+
} catch (e) {}
|
|
81
|
+
}
|
|
38
82
|
}
|
|
39
83
|
|
|
40
84
|
// Try package.json "mdefender" key
|
|
@@ -45,154 +89,154 @@ function loadConfig(overrides = {}) {
|
|
|
45
89
|
} catch (e) {}
|
|
46
90
|
}
|
|
47
91
|
|
|
48
|
-
|
|
92
|
+
// Environment variables
|
|
93
|
+
const envConfig = {};
|
|
94
|
+
if (process.env.MDEFENDER_API_KEY) envConfig.apiKey = process.env.MDEFENDER_API_KEY;
|
|
95
|
+
if (process.env.MDEFENDER_DOMAIN) envConfig.domain = process.env.MDEFENDER_DOMAIN;
|
|
96
|
+
if (process.env.MDEFENDER_API_ENDPOINT || process.env.MDEFENDER_ENDPOINT) {
|
|
97
|
+
envConfig.apiEndpoint = process.env.MDEFENDER_API_ENDPOINT || process.env.MDEFENDER_ENDPOINT;
|
|
98
|
+
}
|
|
99
|
+
if (process.env.MDEFENDER_MODE) envConfig.mode = process.env.MDEFENDER_MODE;
|
|
100
|
+
|
|
101
|
+
return { ...DEFAULT_CONFIG, ...fileConfig, ...envConfig, ...overrides };
|
|
49
102
|
}
|
|
50
103
|
|
|
51
104
|
function getClientIP(req) {
|
|
52
|
-
|
|
53
|
-
|
|
105
|
+
const forwarded = req.headers['x-forwarded-for'];
|
|
106
|
+
if (forwarded) {
|
|
107
|
+
return forwarded.split(',')[0].trim();
|
|
108
|
+
}
|
|
109
|
+
return req.headers['x-real-ip']
|
|
54
110
|
|| req.connection?.remoteAddress
|
|
55
111
|
|| req.socket?.remoteAddress
|
|
56
|
-
|| '
|
|
112
|
+
|| '127.0.0.1';
|
|
57
113
|
}
|
|
58
114
|
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
body_field_values: '',
|
|
78
|
-
timestamp: new Date().toISOString(),
|
|
79
|
-
};
|
|
115
|
+
function extractBody(req) {
|
|
116
|
+
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
|
|
117
|
+
return { raw: '', fields: {}, values: '' };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (req.body) {
|
|
121
|
+
if (typeof req.body === 'object') {
|
|
122
|
+
try {
|
|
123
|
+
const raw = JSON.stringify(req.body);
|
|
124
|
+
const values = Object.values(req.body).map(v => typeof v === 'object' ? JSON.stringify(v) : String(v)).join(' ');
|
|
125
|
+
return { raw, fields: req.body, values };
|
|
126
|
+
} catch (e) {
|
|
127
|
+
return { raw: '', fields: {}, values: '' };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const str = String(req.body);
|
|
131
|
+
return { raw: str, fields: {}, values: str };
|
|
132
|
+
}
|
|
80
133
|
|
|
81
|
-
return
|
|
134
|
+
return { raw: '', fields: {}, values: '' };
|
|
82
135
|
}
|
|
83
136
|
|
|
84
|
-
function
|
|
85
|
-
return new Promise((resolve) => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
137
|
+
function sendAnalyzeRequest(endpointUrl, apiKey, data, timeoutMs = 5000) {
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
try {
|
|
140
|
+
const parsed = new URL('/api/analyze', endpointUrl);
|
|
141
|
+
const postData = JSON.stringify(data);
|
|
142
|
+
const isHttps = parsed.protocol === 'https:';
|
|
143
|
+
const transport = isHttps ? https : http;
|
|
144
|
+
const agent = isHttps ? httpsAgent : httpAgent;
|
|
91
145
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
146
|
+
const options = {
|
|
147
|
+
hostname: parsed.hostname,
|
|
148
|
+
port: parsed.port || (isHttps ? 443 : 80),
|
|
149
|
+
path: parsed.pathname,
|
|
150
|
+
method: 'POST',
|
|
151
|
+
agent: agent,
|
|
152
|
+
timeout: timeoutMs,
|
|
153
|
+
headers: {
|
|
154
|
+
'Content-Type': 'application/json',
|
|
155
|
+
'Content-Length': Buffer.byteLength(postData),
|
|
156
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
157
|
+
'X-MDefender-Version': '1.1.0'
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const req = transport.request(options, (res) => {
|
|
162
|
+
let body = '';
|
|
163
|
+
res.setEncoding('utf8');
|
|
164
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
165
|
+
res.on('end', () => {
|
|
166
|
+
try {
|
|
167
|
+
const json = JSON.parse(body);
|
|
168
|
+
resolve({ statusCode: res.statusCode, data: json });
|
|
169
|
+
} catch (e) {
|
|
170
|
+
resolve({ statusCode: res.statusCode, data: { status: res.statusCode === 200 ? 'allowed' : 'error', raw: body } });
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
req.on('error', (err) => {
|
|
176
|
+
reject(err);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
req.on('timeout', () => {
|
|
180
|
+
req.destroy(new Error(`WAF request timed out after ${timeoutMs}ms`));
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
req.write(postData);
|
|
184
|
+
req.end();
|
|
185
|
+
} catch (err) {
|
|
186
|
+
reject(err);
|
|
120
187
|
}
|
|
121
188
|
});
|
|
122
189
|
}
|
|
123
190
|
|
|
124
|
-
function
|
|
125
|
-
if (!
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (contentType && contentType.includes('application/x-www-form-urlencoded')) {
|
|
133
|
-
const params = new URLSearchParams(body);
|
|
134
|
-
const fields = Object.fromEntries(params);
|
|
135
|
-
return { fields, values: Object.values(fields).join(' ') };
|
|
136
|
-
}
|
|
137
|
-
} catch (e) {}
|
|
138
|
-
|
|
139
|
-
return { fields: {}, values: body };
|
|
191
|
+
function escapeHtml(str) {
|
|
192
|
+
if (!str) return '';
|
|
193
|
+
return String(str)
|
|
194
|
+
.replace(/&/g, '&')
|
|
195
|
+
.replace(/</g, '<')
|
|
196
|
+
.replace(/>/g, '>')
|
|
197
|
+
.replace(/"/g, '"')
|
|
198
|
+
.replace(/'/g, ''');
|
|
140
199
|
}
|
|
141
200
|
|
|
142
|
-
function renderBlockPage(config, result) {
|
|
143
|
-
|
|
144
|
-
|
|
201
|
+
function renderBlockPage(config, result, req) {
|
|
202
|
+
let template = '';
|
|
203
|
+
if (config.customBlockPage) {
|
|
204
|
+
try {
|
|
205
|
+
if (fs.existsSync(config.customBlockPage)) {
|
|
206
|
+
template = fs.readFileSync(config.customBlockPage, 'utf-8');
|
|
207
|
+
}
|
|
208
|
+
} catch (e) {}
|
|
145
209
|
}
|
|
146
210
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
body
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
.
|
|
164
|
-
.
|
|
165
|
-
|
|
166
|
-
.
|
|
167
|
-
.
|
|
168
|
-
|
|
169
|
-
.details p { font-size: 14px; color: rgba(255,255,255,0.8); margin: 8px 0; }
|
|
170
|
-
.details strong { color: #e74c3c; }
|
|
171
|
-
.footer { font-size: 12px; color: rgba(255,255,255,0.4); }
|
|
172
|
-
</style>
|
|
173
|
-
</head>
|
|
174
|
-
<body>
|
|
175
|
-
<div class="container">
|
|
176
|
-
<div class="icon">🚫</div>
|
|
177
|
-
<h1>Access Blocked</h1>
|
|
178
|
-
<p class="subtitle">This request has been blocked by MDefender Pro WAF</p>
|
|
179
|
-
<div class="details">
|
|
180
|
-
<p><strong>Attack Type:</strong> ${attackType}</p>
|
|
181
|
-
<p><strong>Confidence:</strong> ${(confidence * 100).toFixed(1)}%</p>
|
|
182
|
-
<p><strong>Reference ID:</strong> ${referenceId}</p>
|
|
183
|
-
<p><strong>Time:</strong> ${new Date().toISOString()}</p>
|
|
184
|
-
</div>
|
|
185
|
-
<p class="footer">MDefender Pro - Web Application Firewall</p>
|
|
186
|
-
</div>
|
|
187
|
-
</body>
|
|
188
|
-
</html>`;
|
|
211
|
+
if (!template) {
|
|
212
|
+
template = getDefaultTemplate();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const attackType = result.attack_type || 'Malicious Payload Detected';
|
|
216
|
+
const clientIp = getClientIP(req);
|
|
217
|
+
const reason = result.reason || result.message || 'Request blocked by MDefender Pro WAF security policies.';
|
|
218
|
+
const referenceId = result.reference_id || ('MDF-' + crypto.randomBytes(4).toString('hex').toUpperCase());
|
|
219
|
+
const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
220
|
+
const siteName = config.domain || req.headers.host || 'Protected Website';
|
|
221
|
+
|
|
222
|
+
if (!template) {
|
|
223
|
+
return `<!DOCTYPE html><html><head><title>403 Access Denied</title></head><body style="background:#060913;color:#fff;font-family:sans-serif;padding:40px;text-align:center"><h1>403 Forbidden</h1><p>${escapeHtml(reason)}</p><p>Incident ID: ${escapeHtml(referenceId)}</p></body></html>`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return template
|
|
227
|
+
.replace(/\{\{(REFERENCE_ID|reference_id)\}\}/g, escapeHtml(referenceId))
|
|
228
|
+
.replace(/\{\{(ATTACK_TYPE|attack_type)\}\}/g, escapeHtml(attackType))
|
|
229
|
+
.replace(/\{\{(CLIENT_IP|client_ip)\}\}/g, escapeHtml(clientIp))
|
|
230
|
+
.replace(/\{\{(SITE_NAME|site_name|website_name)\}\}/g, escapeHtml(siteName))
|
|
231
|
+
.replace(/\{\{(TIMESTAMP|timestamp)\}\}/g, escapeHtml(timestamp))
|
|
232
|
+
.replace(/\{\{(REASON|reason)\}\}/g, escapeHtml(reason));
|
|
189
233
|
}
|
|
190
234
|
|
|
191
235
|
function mdefender(overrides = {}) {
|
|
192
236
|
const config = loadConfig(overrides);
|
|
193
237
|
|
|
194
238
|
if (!config.apiKey) {
|
|
195
|
-
console.error('[MDefender]
|
|
239
|
+
console.error('[MDefender] WARNING: No API key provided. Running in bypass mode.');
|
|
196
240
|
return (req, res, next) => next();
|
|
197
241
|
}
|
|
198
242
|
|
|
@@ -200,79 +244,89 @@ function mdefender(overrides = {}) {
|
|
|
200
244
|
return (req, res, next) => next();
|
|
201
245
|
}
|
|
202
246
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
'Authorization': `Bearer ${config.apiKey}`,
|
|
208
|
-
'Content-Type': 'application/json',
|
|
209
|
-
'X-MDefender-Version': '1.0.0',
|
|
210
|
-
},
|
|
211
|
-
});
|
|
247
|
+
console.log(`[MDefender] WAF active & protecting domain: "${config.domain || 'default'}" via ${config.apiEndpoint}`);
|
|
248
|
+
|
|
249
|
+
// Warm-up in-memory block page template cache on initialization
|
|
250
|
+
getDefaultTemplate();
|
|
212
251
|
|
|
213
252
|
return async function mdefenderMiddleware(req, res, next) {
|
|
214
|
-
// Skip
|
|
215
|
-
|
|
253
|
+
// 1. Skip paths
|
|
254
|
+
const urlPath = (req.originalUrl || req.url || '').split('?')[0];
|
|
255
|
+
if (config.skipPaths.some(p => urlPath.startsWith(p))) return next();
|
|
216
256
|
|
|
217
|
-
// Skip
|
|
257
|
+
// 2. Skip methods
|
|
218
258
|
if (config.skipMethods.includes(req.method)) return next();
|
|
219
259
|
|
|
220
|
-
// Skip
|
|
260
|
+
// 3. Skip user agents
|
|
221
261
|
const ua = req.headers['user-agent'] || '';
|
|
222
262
|
if (config.skipUserAgents.some(s => ua.toLowerCase().includes(s.toLowerCase()))) return next();
|
|
223
263
|
|
|
224
264
|
try {
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
// Build payload
|
|
231
|
-
const payload = buildPayload(req, config);
|
|
232
|
-
payload.body = rawBody;
|
|
233
|
-
payload.body_fields = fields;
|
|
234
|
-
payload.body_field_values = values;
|
|
235
|
-
|
|
236
|
-
// Send to MDefender API
|
|
237
|
-
const response = await client.post('/api/analyze', {
|
|
238
|
-
request: payload,
|
|
239
|
-
domain: config.domain,
|
|
240
|
-
});
|
|
265
|
+
const clientIp = getClientIP(req);
|
|
266
|
+
const host = req.headers.host || 'localhost';
|
|
267
|
+
const parsedUrl = new URL(req.originalUrl || req.url, `http://${host}`);
|
|
268
|
+
const bodyInfo = extractBody(req);
|
|
241
269
|
|
|
242
|
-
const
|
|
270
|
+
const payload = {
|
|
271
|
+
domain: config.domain || host.split(':')[0],
|
|
272
|
+
request: {
|
|
273
|
+
method: req.method,
|
|
274
|
+
url: parsedUrl.pathname,
|
|
275
|
+
query_string: parsedUrl.search || '',
|
|
276
|
+
query_params: Object.fromEntries(parsedUrl.searchParams),
|
|
277
|
+
ip: clientIp,
|
|
278
|
+
headers: req.headers || {},
|
|
279
|
+
user_agent: ua,
|
|
280
|
+
referer: req.headers['referer'] || req.headers['referrer'] || '',
|
|
281
|
+
content_type: req.headers['content-type'] || '',
|
|
282
|
+
body: bodyInfo.raw,
|
|
283
|
+
body_fields: bodyInfo.fields,
|
|
284
|
+
body_field_values: bodyInfo.values,
|
|
285
|
+
timestamp: new Date().toISOString(),
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const resp = await sendAnalyzeRequest(config.apiEndpoint, config.apiKey, payload, config.timeout);
|
|
290
|
+
const result = resp.data;
|
|
243
291
|
|
|
244
292
|
if (result.status === 'blocked') {
|
|
245
293
|
if (config.logBlocked) {
|
|
246
|
-
console.
|
|
294
|
+
console.warn(`[MDefender] BLOCKED ${req.method} ${parsedUrl.pathname} - ${result.attack_type || 'Malicious Payload'} (${((result.confidence || 0.95) * 100).toFixed(0)}%) [IP: ${clientIp}]`);
|
|
247
295
|
}
|
|
248
296
|
|
|
249
|
-
const blockPage = result.block_page || renderBlockPage(config, result);
|
|
250
|
-
|
|
297
|
+
const blockPage = result.block_page || renderBlockPage(config, result, req);
|
|
298
|
+
const headers = {
|
|
251
299
|
'Content-Type': 'text/html; charset=utf-8',
|
|
252
300
|
'X-MDefender-Status': 'blocked',
|
|
253
301
|
'X-MDefender-Attack-Type': result.attack_type || 'unknown',
|
|
254
|
-
|
|
302
|
+
'X-MDefender-Ref': result.reference_id || 'MDF-BLOCKED',
|
|
303
|
+
};
|
|
304
|
+
if (req.headers.origin) {
|
|
305
|
+
headers['Access-Control-Allow-Origin'] = req.headers.origin;
|
|
306
|
+
headers['Access-Control-Allow-Credentials'] = 'true';
|
|
307
|
+
}
|
|
308
|
+
res.writeHead(config.blockStatusCode || 403, headers);
|
|
255
309
|
return res.end(blockPage);
|
|
256
310
|
}
|
|
257
311
|
|
|
258
|
-
//
|
|
312
|
+
// Safe request
|
|
259
313
|
req.mdefender = {
|
|
260
314
|
status: 'allowed',
|
|
261
315
|
threat_score: result.threat_score || 0,
|
|
262
|
-
|
|
316
|
+
reference_id: result.reference_id || null,
|
|
263
317
|
};
|
|
264
318
|
|
|
265
319
|
return next();
|
|
266
320
|
|
|
267
321
|
} catch (error) {
|
|
268
|
-
console.error(`[MDefender]
|
|
322
|
+
console.error(`[MDefender] WAF Warning: ${error.message}`);
|
|
269
323
|
|
|
270
324
|
if (config.onError === 'block') {
|
|
271
325
|
res.writeHead(503, { 'Content-Type': 'text/html' });
|
|
272
|
-
return res.end('<
|
|
326
|
+
return res.end('<h1>503 Service Unavailable</h1><p>WAF security service unreachable.</p>');
|
|
273
327
|
}
|
|
274
328
|
|
|
275
|
-
// Default: allow
|
|
329
|
+
// Default: Fail-safe allow legitimate traffic to continue
|
|
276
330
|
return next();
|
|
277
331
|
}
|
|
278
332
|
};
|
|
@@ -280,5 +334,6 @@ function mdefender(overrides = {}) {
|
|
|
280
334
|
|
|
281
335
|
mdefender.loadConfig = loadConfig;
|
|
282
336
|
mdefender.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
|
337
|
+
mdefender.sendAnalyzeRequest = sendAnalyzeRequest;
|
|
283
338
|
|
|
284
339
|
module.exports = mdefender;
|
package/package.json
CHANGED
|
@@ -1,46 +1,57 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "mdefender-pro",
|
|
3
|
-
"version": "1.1
|
|
4
|
-
"description": "MDefender Pro - Web Application Firewall middleware for Node.js/Express. Protects against XSS, SQLi, CSRF, and other OWASP Top 10 attacks.",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"types": "index.d.ts",
|
|
7
|
-
"keywords": [
|
|
8
|
-
"waf",
|
|
9
|
-
"web-application-firewall",
|
|
10
|
-
"security",
|
|
11
|
-
"firewall",
|
|
12
|
-
"xss",
|
|
13
|
-
"sqli",
|
|
14
|
-
"csrf",
|
|
15
|
-
"protection",
|
|
16
|
-
"middleware",
|
|
17
|
-
"express",
|
|
18
|
-
"nodejs",
|
|
19
|
-
"owasp"
|
|
20
|
-
],
|
|
21
|
-
"author": "MDefender Pro",
|
|
22
|
-
"license": "MIT",
|
|
23
|
-
"engines": {
|
|
24
|
-
"node": ">=14.0.0"
|
|
25
|
-
},
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
},
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"index.
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "mdefender-pro",
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "MDefender Pro - Web Application Firewall middleware for Node.js/Express. Protects against XSS, SQLi, CSRF, and other OWASP Top 10 attacks.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"waf",
|
|
9
|
+
"web-application-firewall",
|
|
10
|
+
"security",
|
|
11
|
+
"firewall",
|
|
12
|
+
"xss",
|
|
13
|
+
"sqli",
|
|
14
|
+
"csrf",
|
|
15
|
+
"protection",
|
|
16
|
+
"middleware",
|
|
17
|
+
"express",
|
|
18
|
+
"nodejs",
|
|
19
|
+
"owasp"
|
|
20
|
+
],
|
|
21
|
+
"author": "MDefender Pro",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=14.0.0"
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"mdefender-pro": "./bin/mdefender.js"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"express": "^4.18.0 || ^5.0.0"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"index.js",
|
|
35
|
+
"index.d.ts",
|
|
36
|
+
"README.md",
|
|
37
|
+
"config-loader.js",
|
|
38
|
+
"client.mjs",
|
|
39
|
+
"vite.js",
|
|
40
|
+
"vite.mjs",
|
|
41
|
+
"block-page.html",
|
|
42
|
+
"bin"
|
|
43
|
+
],
|
|
44
|
+
"exports": {
|
|
45
|
+
".": "./index.js",
|
|
46
|
+
"./client": "./client.mjs",
|
|
47
|
+
"./vite": "./vite.mjs"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "https://github.com/mdefender/mdefender"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://mdefender-pro-6e3r.onrender.com",
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/mdefender/mdefender/issues"
|
|
56
|
+
}
|
|
57
|
+
}
|
package/vite.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// vite.js - Official MDefender Pro Vite Plugin (CJS)
|
|
2
|
+
const mdefender = require('./index.js');
|
|
3
|
+
|
|
4
|
+
function mdefenderVite(options = {}) {
|
|
5
|
+
const middleware = mdefender(options);
|
|
6
|
+
return {
|
|
7
|
+
name: 'mdefender-vite-plugin',
|
|
8
|
+
configureServer(server) {
|
|
9
|
+
server.middlewares.use(middleware);
|
|
10
|
+
},
|
|
11
|
+
configurePreviewServer(server) {
|
|
12
|
+
server.middlewares.use(middleware);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = { mdefenderVite };
|
|
18
|
+
module.exports.default = mdefenderVite;
|
package/vite.mjs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// vite.mjs - Official MDefender Pro Vite Plugin for React/Vue/Svelte SPAs
|
|
2
|
+
import mdefender from './index.js';
|
|
3
|
+
|
|
4
|
+
export function mdefenderVite(options = {}) {
|
|
5
|
+
const middleware = mdefender(options);
|
|
6
|
+
return {
|
|
7
|
+
name: 'mdefender-vite-plugin',
|
|
8
|
+
configureServer(server) {
|
|
9
|
+
server.middlewares.use(middleware);
|
|
10
|
+
},
|
|
11
|
+
configurePreviewServer(server) {
|
|
12
|
+
server.middlewares.use(middleware);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default mdefenderVite;
|