yusufkhon-guard 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +734 -0
  3. package/bin/cli.js +39 -0
  4. package/bin/renew.js +149 -0
  5. package/bin/scan.js +124 -0
  6. package/bin/ssl.js +181 -0
  7. package/package.json +80 -0
  8. package/src/index.js +206 -0
  9. package/src/modules/acme/client.js +240 -0
  10. package/src/modules/acme/csr.js +172 -0
  11. package/src/modules/acme/detectDomains.js +132 -0
  12. package/src/modules/acme/index.js +211 -0
  13. package/src/modules/acme/jose.js +125 -0
  14. package/src/modules/acme/renew.js +89 -0
  15. package/src/modules/adaptiveRateLimiter.js +154 -0
  16. package/src/modules/alerts.js +168 -0
  17. package/src/modules/bodyLimit.js +106 -0
  18. package/src/modules/botDetector.js +144 -0
  19. package/src/modules/connectionGuard.js +128 -0
  20. package/src/modules/csrf.js +130 -0
  21. package/src/modules/dashboard.js +257 -0
  22. package/src/modules/dashboardHtml.js +235 -0
  23. package/src/modules/datacenterRanges.js +121 -0
  24. package/src/modules/ddosGuard.js +323 -0
  25. package/src/modules/firewall.js +239 -0
  26. package/src/modules/geoBlock.js +169 -0
  27. package/src/modules/headers.js +130 -0
  28. package/src/modules/honeypot.js +149 -0
  29. package/src/modules/httpsRedirect.js +67 -0
  30. package/src/modules/integrity.js +164 -0
  31. package/src/modules/ipBlocker.js +372 -0
  32. package/src/modules/ja3.js +155 -0
  33. package/src/modules/logExport.js +134 -0
  34. package/src/modules/nosqlGuard.js +143 -0
  35. package/src/modules/osFirewall.js +182 -0
  36. package/src/modules/rateLimiter.js +196 -0
  37. package/src/modules/sanitizer.js +253 -0
  38. package/src/modules/threatDetector.js +202 -0
  39. package/src/scanner/checks/dependencies.js +125 -0
  40. package/src/scanner/checks/environment.js +118 -0
  41. package/src/scanner/checks/exposure.js +99 -0
  42. package/src/scanner/checks/headers.js +119 -0
  43. package/src/scanner/checks/ssl.js +151 -0
  44. package/src/scanner/index.js +209 -0
  45. package/src/utils/cidr.js +140 -0
  46. package/src/utils/logger.js +194 -0
  47. package/src/utils/privilege.js +212 -0
@@ -0,0 +1,239 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @file firewall.js
5
+ * @description "Blue Team" aktiv himoya yadrosi. `threatDetector` (aniqlash) va
6
+ * `ipBlocker` (bloklash) ni birlashtiradi hamda serverni 24/7
7
+ * kuzatib boradi. Har bir so'rovni real vaqtda tahlil qiladi,
8
+ * tahdid sezilsa avtomat aks-ta'sir ko'rsatadi (IP'ni bloklaydi)
9
+ * va hodisalarni tashqariga (webhook/telegram/email) uzatish uchun
10
+ * EventEmitter chiqaradi.
11
+ * @module modules/firewall
12
+ */
13
+
14
+ const { EventEmitter } = require('events');
15
+ const threatDetector = require('./threatDetector');
16
+ const { IpBlocker } = require('./ipBlocker');
17
+ const { defaultKeyGenerator } = require('./rateLimiter');
18
+ const { normalizeIp } = require('../utils/cidr');
19
+ const { defaultLogger } = require('../utils/logger');
20
+
21
+ /**
22
+ * So'rovning HAQIQIY ulanish (TCP soket) manzilini qaytaradi. Whitelist
23
+ * tekshiruvi shu manzil bo'yicha bo'lishi kerak — `X-Forwarded-For` sarlavhasi
24
+ * mijoz tomonidan soxtalashtirilishi mumkin, shu bois uni whitelist uchun
25
+ * ishonchli deb bo'lmaydi (aks holda hujumchi o'zini localhost qilib ko'rsatib
26
+ * himoyani chetlab o'tadi).
27
+ * @param {import('express').Request} req
28
+ * @returns {string} Normallashtirilgan soket manzili yoki bo'sh satr.
29
+ */
30
+ function realClientIp(req) {
31
+ const sock = req.socket || req.connection;
32
+ const addr = sock && sock.remoteAddress;
33
+ return addr ? normalizeIp(addr) : '';
34
+ }
35
+
36
+ /**
37
+ * @typedef {Object} FirewallOptions
38
+ * @property {number} [banThreshold=100] - Bloklash uchun kerakli to'plangan ball.
39
+ * @property {number} [instantBanScore=80] - Bitta so'rovda shu balldan oshsa — darhol blok.
40
+ * @property {number} [strikeWindowMs=600000] - Ballar to'planadigan oyna (ms).
41
+ * @property {string[]} [whitelist] - Hech qachon bloklanmaydigan IP'lar.
42
+ * @property {string|null} [persistPath=null] - Bloklarni saqlash uchun fayl yo'li.
43
+ * @property {number} [statusCode=403] - Bloklangan IP uchun HTTP status.
44
+ * @property {string} [message] - Bloklangan IP uchun xabar.
45
+ * @property {boolean} [monitor=true] - 24/7 davriy holat monitoringini yoqish.
46
+ * @property {number} [monitorIntervalMs=300000] - Monitoring hisoboti oralig'i (5 daqiqa).
47
+ * @property {import('../utils/logger').Logger} [logger=defaultLogger] - Logger.
48
+ * @property {(req: import('express').Request) => string} [keyGenerator] - IP aniqlash.
49
+ */
50
+
51
+ /**
52
+ * Standart sozlamalar.
53
+ * @type {FirewallOptions}
54
+ */
55
+ const DEFAULT_OPTIONS = {
56
+ banThreshold: 100,
57
+ instantBanScore: 80,
58
+ strikeWindowMs: 10 * 60 * 1000,
59
+ statusCode: 403,
60
+ message: 'Access denied — bu IP xavfsizlik tizimi tomonidan bloklangan.',
61
+ monitor: true,
62
+ monitorIntervalMs: 5 * 60 * 1000,
63
+ };
64
+
65
+ /**
66
+ * Aktiv himoya (firewall) tizimi.
67
+ * `EventEmitter` dan meros oladi — quyidagi hodisalarni chiqaradi:
68
+ * - `'threat'` ({ ip, report, req }) — tahdid aniqlandi.
69
+ * - `'ban'` ({ ip, reason, record }) — IP bloklandi.
70
+ * - `'blocked'` ({ ip, path }) — bloklangan IP qaytarildi.
71
+ * - `'tick'` (stats) — davriy monitoring hisoboti.
72
+ *
73
+ * @class
74
+ * @extends EventEmitter
75
+ */
76
+ class Firewall extends EventEmitter {
77
+ /**
78
+ * @param {FirewallOptions} [userOptions={}]
79
+ */
80
+ constructor(userOptions = {}) {
81
+ super();
82
+ /** @type {FirewallOptions} */
83
+ this.options = { ...DEFAULT_OPTIONS, ...userOptions };
84
+ /** @type {import('../utils/logger').Logger} */
85
+ this.logger = this.options.logger || defaultLogger;
86
+ /** @type {(req: import('express').Request) => string} */
87
+ this.keyGenerator = this.options.keyGenerator || defaultKeyGenerator;
88
+
89
+ /** @type {IpBlocker} */
90
+ this.blocker = new IpBlocker({
91
+ banThreshold: this.options.banThreshold,
92
+ strikeWindowMs: this.options.strikeWindowMs,
93
+ whitelist: this.options.whitelist,
94
+ persistPath: this.options.persistPath,
95
+ });
96
+
97
+ /** @type {{requests: number, threats: number, bans: number, blocked: number}} */
98
+ this.metrics = { requests: 0, threats: 0, bans: 0, blocked: 0 };
99
+
100
+ if (this.options.monitor) this._startMonitor();
101
+ }
102
+
103
+ /**
104
+ * 24/7 davriy monitoringni ishga tushiradi — holat statistikasini loglaydi.
105
+ * @private
106
+ * @returns {void}
107
+ */
108
+ _startMonitor() {
109
+ this._monitorTimer = setInterval(() => {
110
+ const stats = { ...this.metrics, ...this.blocker.stats() };
111
+ this.logger.info('🛡️ Firewall monitoring', stats);
112
+ this.emit('tick', stats);
113
+ }, this.options.monitorIntervalMs);
114
+ if (typeof this._monitorTimer.unref === 'function') this._monitorTimer.unref();
115
+ }
116
+
117
+ /**
118
+ * Monitoringni to'xtatadi (test/graceful shutdown uchun).
119
+ * @returns {void}
120
+ */
121
+ stop() {
122
+ if (this._monitorTimer) clearInterval(this._monitorTimer);
123
+ }
124
+
125
+ /**
126
+ * Bloklangan IP uchun standart javob yuboradi.
127
+ * @private
128
+ * @param {import('express').Response} res
129
+ * @param {string} ip
130
+ * @returns {void}
131
+ */
132
+ _reject(res, ip) {
133
+ const secondsLeft = this.blocker.banSecondsLeft(ip);
134
+ if (secondsLeft > 0) res.setHeader('Retry-After', secondsLeft);
135
+ res.status(this.options.statusCode).json({
136
+ success: false,
137
+ error: this.options.message,
138
+ bannedFor: secondsLeft === -1 ? 'permanent' : `${secondsLeft}s`,
139
+ });
140
+ }
141
+
142
+ /**
143
+ * Firewall Express middleware'ini qaytaradi.
144
+ * @returns {import('express').RequestHandler}
145
+ */
146
+ middleware() {
147
+ const self = this;
148
+
149
+ /**
150
+ * @param {import('express').Request} req
151
+ * @param {import('express').Response} res
152
+ * @param {import('express').NextFunction} next
153
+ */
154
+ return function firewallMiddleware(req, res, next) {
155
+ self.metrics.requests += 1;
156
+ const ip = self.keyGenerator(req);
157
+ const realIp = realClientIp(req);
158
+ const reqPath = req.originalUrl || req.url;
159
+
160
+ // 1. Ishonchli IP — tekshiruvsiz o'tkazamiz.
161
+ // MUHIM: whitelist HAQIQIY ulanish manzili (socket) bo'yicha tekshiriladi.
162
+ // Bu spoofed `X-Forwarded-For: 127.0.0.1` orqali o'zini whitelist qilib
163
+ // firewall'ni chetlab o'tishning oldini oladi. Soket manzili bo'lmasa
164
+ // (kamdan-kam holat), atributsiya kalitiga qaytamiz.
165
+ if (realIp ? self.blocker.isWhitelisted(realIp) : self.blocker.isWhitelisted(ip)) {
166
+ return next();
167
+ }
168
+
169
+ // 2. Allaqachon bloklangan bo'lsa — darhol rad etamiz.
170
+ if (self.blocker.isBanned(ip)) {
171
+ self.metrics.blocked += 1;
172
+ self.logger.blocked(ip, 'Bloklangan IP qayta urindi', { path: reqPath });
173
+ self.emit('blocked', { ip, path: reqPath });
174
+ return self._reject(res, ip);
175
+ }
176
+
177
+ // 3. So'rovni tahlil qilamiz.
178
+ const report = threatDetector.analyze(req);
179
+
180
+ if (report.score > 0) {
181
+ self.metrics.threats += 1;
182
+ self.logger.warn(`Tahdid aniqlandi [${report.severity}] ${ip}`, {
183
+ score: report.score,
184
+ path: reqPath,
185
+ types: report.signals.map((s) => s.type),
186
+ });
187
+ self.emit('threat', { ip, report, req });
188
+
189
+ // 3a. Kritik tahdid — darhol bloklaymiz.
190
+ const instant = report.score >= self.options.instantBanScore;
191
+ const primaryReason = report.signals[0] ? report.signals[0].detail : 'Threat detected';
192
+
193
+ const outcome = instant
194
+ ? { banned: true, record: self.blocker.ban(ip, primaryReason) }
195
+ : self.blocker.addStrike(ip, report.score, primaryReason);
196
+
197
+ if (outcome.banned) {
198
+ self.metrics.bans += 1;
199
+ self.logger.blocked(ip, `AVTO-BLOK (${report.severity})`, {
200
+ path: reqPath,
201
+ reason: primaryReason,
202
+ });
203
+ self.emit('ban', { ip, reason: primaryReason, record: outcome.record });
204
+ return self._reject(res, ip);
205
+ }
206
+ }
207
+
208
+ next();
209
+ };
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Firewall middleware yaratuvchi qulay funksiya.
215
+ * Yaratilgan middleware'ga `.firewall` orqali Firewall nusxasi biriktiriladi
216
+ * (hodisalarga obuna bo'lish va boshqarish uchun).
217
+ *
218
+ * @example
219
+ * const { firewall } = require('yusufkhon-guard');
220
+ * const fw = firewall({ persistPath: './logs/bans.json' });
221
+ * fw.firewall.on('ban', ({ ip, reason }) => {
222
+ * // Telegram/Email/Webhook orqali xabar berish
223
+ * console.log('Bloklandi:', ip, reason);
224
+ * });
225
+ * app.use(fw);
226
+ *
227
+ * @param {FirewallOptions} [options={}]
228
+ * @returns {import('express').RequestHandler & { firewall: Firewall }}
229
+ */
230
+ function firewall(options = {}) {
231
+ const instance = new Firewall(options);
232
+ const mw = instance.middleware();
233
+ mw.firewall = instance;
234
+ return mw;
235
+ }
236
+
237
+ module.exports = firewall;
238
+ module.exports.firewall = firewall;
239
+ module.exports.Firewall = Firewall;
@@ -0,0 +1,169 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @file geoBlock.js
5
+ * @description Hududiy (Geo-IP) va datacenter/VPN himoyasi. Faqat ruxsat berilgan
6
+ * davlatlardan kirishga ruxsat berish, yoki ma'lum davlatlarni
7
+ * bloklash, hamda VPN/proxy/datacenter IP'larini cheklash.
8
+ *
9
+ * Davlatni aniqlash uchun `lookup(ip) => countryCode` funksiyasi
10
+ * beriladi. Agar berilmasa va ixtiyoriy `geoip-lite` paketi
11
+ * o'rnatilgan bo'lsa — o'sha ishlatiladi (zero-dependency saqlanadi).
12
+ * @module modules/geoBlock
13
+ */
14
+
15
+ const datacenter = require('./datacenterRanges');
16
+ const { normalizeIp } = require('../utils/cidr');
17
+ const { defaultLogger } = require('../utils/logger');
18
+
19
+ /**
20
+ * So'rovni baholash uchun ISHONCHLI mijoz IP'sini aniqlaydi.
21
+ * Ustuvorlik: `req.ip` (Express'ning `trust proxy` sozlamasiga bo'ysunadi —
22
+ * administrator belgilagan ishonchli manba), so'ng haqiqiy TCP soket manzili.
23
+ * Xom `X-Forwarded-For` sarlavhasiga TAYANMAYDI — u mijoz tomonidan
24
+ * soxtalashtirilishi mumkin (spoofing).
25
+ * @private
26
+ * @param {import('express').Request} req
27
+ * @returns {string} Normallashtirilgan IP yoki bo'sh satr.
28
+ */
29
+ function trustedClientIp(req) {
30
+ if (req.ip) return normalizeIp(req.ip);
31
+ const sock = req.socket || req.connection;
32
+ const addr = sock && sock.remoteAddress;
33
+ return addr ? normalizeIp(addr) : '';
34
+ }
35
+
36
+ /**
37
+ * `geoip-lite` (ixtiyoriy peer) mavjud bo'lsa, undan lookup funksiyasi yasaydi.
38
+ * @private
39
+ * @returns {((ip: string) => string|null)|null}
40
+ */
41
+ function tryGeoipLite() {
42
+ try {
43
+ // eslint-disable-next-line global-require
44
+ const geoip = require('geoip-lite');
45
+ return (ip) => {
46
+ const r = geoip.lookup(ip);
47
+ return r ? r.country : null;
48
+ };
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * @typedef {Object} GeoBlockOptions
56
+ * @property {string[]} [allowCountries] - Faqat shu davlatlardan (ISO-2, masalan 'UZ','US') ruxsat.
57
+ * @property {string[]} [denyCountries] - Shu davlatlarni bloklash (allowCountries berilmaganda).
58
+ * @property {boolean} [blockDatacenter=false] - Datacenter/hosting IP'larini bloklash.
59
+ * @property {boolean} [blockVpn=false] - Ma'lum VPN diapazonlarini bloklash (blockDatacenter ichida ham bor).
60
+ * @property {(ip: string) => (string|null)} [lookup] - IP -> davlat kodi funksiyasi.
61
+ * @property {string[]} [whitelist] - Hech qachon bloklanmaydigan IP'lar.
62
+ * @property {boolean} [allowUnknown] - Davlati aniqlanmagan (null) IP'ga ruxsat. STANDART: denylist'da `true`, allowlist'da `false` (xavfsiz).
63
+ * @property {boolean} [failClosed] - `lookup` XATO (exception) tashlasa RAD etilsinmi. STANDART: allowlist'da `true`, denylist'da `false`.
64
+ * @property {number} [statusCode=403] - Bloklashda status kodi.
65
+ * @property {string} [message] - Bloklash xabari.
66
+ * @property {import('./firewall').Firewall} [firewall] - Berilsa, bloklangan IP firewall'ga strike qo'shadi.
67
+ * @property {import('../utils/logger').Logger} [logger=defaultLogger] - Logger.
68
+ */
69
+
70
+ /**
71
+ * Geo-IP / datacenter / VPN himoya middleware'ini yaratadi.
72
+ *
73
+ * @example
74
+ * const { geoBlock } = require('yusufkhon-guard');
75
+ * app.use(geoBlock({ allowCountries: ['UZ','US'], blockDatacenter: true }));
76
+ *
77
+ * @param {GeoBlockOptions} [options={}]
78
+ * @returns {import('express').RequestHandler}
79
+ */
80
+ function geoBlock(options = {}) {
81
+ const logger = options.logger || defaultLogger;
82
+ const allow = (options.allowCountries || []).map((c) => c.toUpperCase());
83
+ const deny = (options.denyCountries || []).map((c) => c.toUpperCase());
84
+ const whitelist = new Set(options.whitelist || ['127.0.0.1', '::1']);
85
+
86
+ // XAVFSIZ-BY-DEFAULT: allowlist (allowCountries) rejimida davlatni aniqlab
87
+ // bo'lmasa (null natija YOKI lookup xatosi) — RAD etamiz; aks holda geo-bazada
88
+ // bo'lmagan istalgan IP allowlist'ni jimgina chetlab o'tar edi. Denylist
89
+ // rejimida esa noma'lumlarni o'tkazamiz (permissive). Ikkalasini ham ochiq
90
+ // override qilish mumkin (allowUnknown / failClosed).
91
+ const isAllowlist = allow.length > 0;
92
+ const allowUnknown = options.allowUnknown !== undefined ? options.allowUnknown : !isAllowlist;
93
+ const failClosed = options.failClosed !== undefined ? options.failClosed : isAllowlist;
94
+
95
+ const statusCode = options.statusCode || 403;
96
+ const message = options.message || 'Access denied — sizning hududingizdan kirish cheklangan.';
97
+
98
+ const lookup = options.lookup || tryGeoipLite();
99
+ if ((allow.length || deny.length) && !lookup) {
100
+ logger.warn('geoBlock: davlat bo\'yicha filtrlash uchun `lookup` funksiyasi yoki `geoip-lite` kerak. Geo-qism o\'tkazib yuboriladi.');
101
+ }
102
+
103
+ /**
104
+ * @param {import('express').Request} req
105
+ * @param {import('express').Response} res
106
+ * @param {import('express').NextFunction} next
107
+ */
108
+ return function geoBlockMiddleware(req, res, next) {
109
+ // ISHONCHLI IP (req.ip yoki soket) — spooflanadigan xom XFF'ga tayanmaymiz.
110
+ const ip = trustedClientIp(req);
111
+ if (!ip || whitelist.has(ip)) return next();
112
+
113
+ /**
114
+ * So'rovni rad etadi (va ixtiyoriy firewall'ga strike qo'shadi).
115
+ * @param {string} reason
116
+ */
117
+ const reject = (reason) => {
118
+ logger.blocked(ip, `Geo/DC blok: ${reason}`, { path: req.originalUrl || req.url });
119
+ if (options.firewall) {
120
+ options.firewall.blocker.addStrike(ip, 60, `Geo/DC: ${reason}`);
121
+ }
122
+ res.status ? res.status(statusCode).json({ success: false, error: message, reason })
123
+ : (res.statusCode = statusCode, res.end(JSON.stringify({ success: false, error: message })));
124
+ };
125
+
126
+ // 1. Datacenter / VPN tekshiruvi.
127
+ if (options.blockDatacenter || options.blockVpn) {
128
+ const dc = datacenter.detect(ip);
129
+ if (dc) {
130
+ return reject(`${dc.provider} (datacenter/VPN)`);
131
+ }
132
+ }
133
+
134
+ // 2. Davlat bo'yicha filtrlash.
135
+ if (lookup && (allow.length || deny.length)) {
136
+ let country = null;
137
+ let lookupFailed = false;
138
+ try { country = lookup(ip); } catch { lookupFailed = true; }
139
+
140
+ // lookup XATO tashladi. Qat'iy rejimda (failClosed) — RAD. Aks holda
141
+ // "davlat noaniq" sifatida allowUnknown qoidasiga o'tamiz.
142
+ if (lookupFailed && failClosed) {
143
+ return reject('geo-lookup xatosi (fail-closed)');
144
+ }
145
+
146
+ if (!country) {
147
+ if (!allowUnknown) return reject('davlat aniqlanmadi');
148
+ return next();
149
+ }
150
+
151
+ country = country.toUpperCase();
152
+
153
+ // Allow-list rejimi: faqat ruxsat berilganlar o'tadi.
154
+ if (allow.length && !allow.includes(country)) {
155
+ return reject(`davlat ${country} ruxsat etilmagan`);
156
+ }
157
+ // Deny-list rejimi.
158
+ if (deny.length && deny.includes(country)) {
159
+ return reject(`davlat ${country} bloklangan`);
160
+ }
161
+ }
162
+
163
+ next();
164
+ };
165
+ }
166
+
167
+ module.exports = geoBlock;
168
+ module.exports.geoBlock = geoBlock;
169
+ module.exports.datacenter = datacenter;
@@ -0,0 +1,130 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @file headers.js
5
+ * @description Server javobiga zamonaviy xavfsizlik sarlavhalarini
6
+ * (Security Headers) avtomatik biriktiruvchi Express middleware.
7
+ * @module modules/headers
8
+ */
9
+
10
+ /**
11
+ * @typedef {Object} HeadersOptions
12
+ * @property {boolean} [frameGuard=true] - `X-Frame-Options: DENY` (clickjacking himoyasi).
13
+ * @property {boolean} [noSniff=true] - `X-Content-Type-Options: nosniff` (MIME-sniffing himoyasi).
14
+ * @property {boolean} [xssProtection=true] - `X-XSS-Protection: 1; mode=block`.
15
+ * @property {boolean|Object} [hsts=true] - HTTP Strict Transport Security sozlamasi.
16
+ * @property {number} [hsts.maxAge=15552000] - HSTS amal qilish muddati (soniyalarda, ~180 kun).
17
+ * @property {boolean} [hsts.includeSubDomains=true] - Subdomenlarni ham qamrab olsinmi.
18
+ * @property {boolean} [hsts.preload=false] - `preload` bayrog'ini qo'shsinmi.
19
+ * @property {boolean|string} [contentSecurityPolicy="default-src 'self'"] - CSP qiymati (false — o'chirilgan).
20
+ * @property {string} [poweredBy='yusufkhon-guard'] - `X-Powered-By` sarlavhasi qiymati.
21
+ * @property {string} [referrerPolicy='no-referrer'] - `Referrer-Policy` qiymati.
22
+ */
23
+
24
+ /**
25
+ * Standart (default) sozlamalar.
26
+ * @type {HeadersOptions}
27
+ */
28
+ const DEFAULT_OPTIONS = {
29
+ frameGuard: true,
30
+ noSniff: true,
31
+ xssProtection: true,
32
+ hsts: true,
33
+ contentSecurityPolicy: "default-src 'self'",
34
+ poweredBy: 'yusufkhon-guard',
35
+ referrerPolicy: 'no-referrer',
36
+ };
37
+
38
+ /**
39
+ * HSTS uchun standart qiymatlar.
40
+ * @type {{maxAge: number, includeSubDomains: boolean, preload: boolean}}
41
+ */
42
+ const DEFAULT_HSTS = {
43
+ maxAge: 15552000, // ~180 kun
44
+ includeSubDomains: true,
45
+ preload: false,
46
+ };
47
+
48
+ /**
49
+ * HSTS sarlavhasi qiymatini tuzadi.
50
+ * @private
51
+ * @param {boolean|Object} hsts - Foydalanuvchi bergan HSTS sozlamasi.
52
+ * @returns {string|null} Tayyor sarlavha qiymati yoki null (o'chirilgan bo'lsa).
53
+ */
54
+ function buildHstsValue(hsts) {
55
+ if (!hsts) return null;
56
+
57
+ const conf = hsts === true ? DEFAULT_HSTS : { ...DEFAULT_HSTS, ...hsts };
58
+ const parts = [`max-age=${conf.maxAge}`];
59
+
60
+ if (conf.includeSubDomains) parts.push('includeSubDomains');
61
+ if (conf.preload) parts.push('preload');
62
+
63
+ return parts.join('; ');
64
+ }
65
+
66
+ /**
67
+ * Xavfsizlik sarlavhalarini o'rnatuvchi Express middleware yaratadi.
68
+ *
69
+ * @example
70
+ * const { securityHeaders } = require('yusufkhon-guard');
71
+ * app.use(securityHeaders({ contentSecurityPolicy: "default-src 'self'" }));
72
+ *
73
+ * @param {HeadersOptions} [userOptions={}] - Sozlamalar (standart qiymatlar bilan birlashtiriladi).
74
+ * @returns {import('express').RequestHandler} Express middleware funksiyasi.
75
+ */
76
+ function securityHeaders(userOptions = {}) {
77
+ const options = { ...DEFAULT_OPTIONS, ...userOptions };
78
+ const hstsValue = buildHstsValue(options.hsts);
79
+
80
+ /**
81
+ * @param {import('express').Request} req
82
+ * @param {import('express').Response} res
83
+ * @param {import('express').NextFunction} next
84
+ */
85
+ return function securityHeadersMiddleware(req, res, next) {
86
+ // Clickjacking himoyasi.
87
+ if (options.frameGuard) {
88
+ res.setHeader('X-Frame-Options', 'DENY');
89
+ }
90
+
91
+ // MIME-sniffing himoyasi.
92
+ if (options.noSniff) {
93
+ res.setHeader('X-Content-Type-Options', 'nosniff');
94
+ }
95
+
96
+ // Eski brauzerlar uchun XSS filtri.
97
+ if (options.xssProtection) {
98
+ res.setHeader('X-XSS-Protection', '1; mode=block');
99
+ }
100
+
101
+ // HTTPS'ni majburlash (faqat HTTPS orqali kelgan bo'lsa mantiqan to'g'ri,
102
+ // lekin sarlavhani har doim qo'yish xavfsiz — brauzer HTTPS'da hisobga oladi).
103
+ if (hstsValue) {
104
+ res.setHeader('Strict-Transport-Security', hstsValue);
105
+ }
106
+
107
+ // Content Security Policy.
108
+ if (options.contentSecurityPolicy) {
109
+ res.setHeader('Content-Security-Policy', options.contentSecurityPolicy);
110
+ }
111
+
112
+ // Referrer siyosati.
113
+ if (options.referrerPolicy) {
114
+ res.setHeader('Referrer-Policy', options.referrerPolicy);
115
+ }
116
+
117
+ // Server texnologiyasini yashirib, o'z brendimizni qo'yamiz.
118
+ if (options.poweredBy) {
119
+ res.setHeader('X-Powered-By', options.poweredBy);
120
+ } else {
121
+ res.removeHeader('X-Powered-By');
122
+ }
123
+
124
+ next();
125
+ };
126
+ }
127
+
128
+ module.exports = securityHeaders;
129
+ module.exports.securityHeaders = securityHeaders;
130
+ module.exports.buildHstsValue = buildHstsValue;
@@ -0,0 +1,149 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @file honeypot.js
5
+ * @description HoneyPot (tuzoq) tizimi. Saytda ko'rinmaydigan, lekin avtomatik
6
+ * skanerlar va hujumchilar qidiradigan soxta yo'llar (masalan,
7
+ * `/.env`, `/admin/config.php`, `/wp-login.php`) o'rnatadi.
8
+ *
9
+ * Bunday yo'lga so'rov yuborilishi 100% zararli niyat belgisidir —
10
+ * shu bois so'rov yuboruvchi IP DARHOL bloklanadi (firewall +
11
+ * ixtiyoriy OS firewall orqali).
12
+ * @module modules/honeypot
13
+ */
14
+
15
+ const { defaultLogger } = require('../utils/logger');
16
+ const { defaultKeyGenerator } = require('./rateLimiter');
17
+
18
+ /**
19
+ * Standart tuzoq yo'llari — real ilova ulardan foydalanmasligi kerak.
20
+ * @type {string[]}
21
+ */
22
+ const DEFAULT_TRAPS = [
23
+ '/.env',
24
+ '/.env.local',
25
+ '/.git/config',
26
+ '/wp-login.php',
27
+ '/wp-admin',
28
+ '/admin/config.php',
29
+ '/phpmyadmin',
30
+ '/config.php',
31
+ '/.aws/credentials',
32
+ '/backup.sql',
33
+ '/.ssh/id_rsa',
34
+ '/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php',
35
+ ];
36
+
37
+ /**
38
+ * Yo'lni (bounded) iterativ dekodlaydi — hujumchi tuzoqni URL-encoding bilan
39
+ * yashirishga urinishi mumkin (masalan `/%2eenv` yoki ikki marta kodlangan
40
+ * `/%252eenv` -> `/.env`). Dekodlash barqarorlashguncha (yoki 5 martagacha)
41
+ * takrorlanadi. Buzuq (invalid) foizli kodlashda o'zgarishsiz qaytadi.
42
+ * @private
43
+ * @param {string} value
44
+ * @returns {string}
45
+ */
46
+ function decodePathDeep(value) {
47
+ let cur = value;
48
+ let prev;
49
+ let i = 0;
50
+ do {
51
+ prev = cur;
52
+ try {
53
+ cur = decodeURIComponent(cur);
54
+ } catch {
55
+ break; // buzuq kodlash — bor holicha qoldiramiz
56
+ }
57
+ i += 1;
58
+ } while (cur !== prev && i < 5);
59
+ return cur;
60
+ }
61
+
62
+ /**
63
+ * @typedef {Object} HoneypotOptions
64
+ * @property {string[]} [traps] - Tuzoq yo'llari (standart ro'yxat qo'shiladi).
65
+ * @property {boolean} [useDefaults=true] - Standart tuzoqlarni ham qo'shish.
66
+ * @property {import('./firewall').Firewall} [firewall] - Darhol bloklash uchun (majburiy tavsiya etiladi).
67
+ * @property {import('./osFirewall')} [osFirewall] - Berilsa, OS firewall'da ham bloklaydi.
68
+ * @property {boolean} [osBlock=false] - OS darajasida ham bloklansinmi (rozilik/huquq talab qiladi).
69
+ * @property {number} [statusCode=404] - Tuzoq javobi (odatda 404 — tabiiy ko'rinishi uchun).
70
+ * @property {import('../utils/logger').Logger} [logger=defaultLogger] - Logger.
71
+ * @property {(info: {ip:string, path:string}) => void} [onTrap] - Tuzoqqa tushganda chaqiriladigan hook.
72
+ */
73
+
74
+ /**
75
+ * Honeypot middleware'ini yaratadi.
76
+ *
77
+ * @example
78
+ * const { honeypot } = require('yusufkhon-guard');
79
+ * app.use(honeypot({ firewall: shield.firewall }));
80
+ *
81
+ * @param {HoneypotOptions} [options={}]
82
+ * @returns {import('express').RequestHandler}
83
+ */
84
+ function honeypot(options = {}) {
85
+ const logger = options.logger || defaultLogger;
86
+ const keyGen = defaultKeyGenerator;
87
+ const statusCode = options.statusCode || 404;
88
+
89
+ const traps = new Set(
90
+ (options.useDefaults === false ? [] : DEFAULT_TRAPS).concat(options.traps || [])
91
+ .map((t) => t.toLowerCase())
92
+ );
93
+
94
+ /**
95
+ * @param {import('express').Request} req
96
+ * @param {import('express').Response} res
97
+ * @param {import('express').NextFunction} next
98
+ */
99
+ return function honeypotMiddleware(req, res, next) {
100
+ const raw = (req.originalUrl || req.url || '').split('?')[0];
101
+ // URL-encoding (va ikki marta kodlash) bilan chetlab o'tishga qarshi:
102
+ // yo'lni dekodlab, so'ng registrsizlantiramiz.
103
+ const rawPath = decodePathDeep(raw).toLowerCase();
104
+
105
+ // To'liq yo'l yoki uning prefiksi tuzoqqa mos kelsa.
106
+ let trapped = traps.has(rawPath);
107
+ if (!trapped) {
108
+ for (const t of traps) {
109
+ if (rawPath === t || rawPath.startsWith(t + '/')) { trapped = true; break; }
110
+ }
111
+ }
112
+
113
+ if (!trapped) return next();
114
+
115
+ const ip = keyGen(req);
116
+ logger.blocked(ip, 'HONEYPOT tuzog\'iga tushdi (instant ban)', { path: rawPath });
117
+
118
+ // 1. Firewall'da darhol bloklash.
119
+ if (options.firewall) {
120
+ // `ban()` haqiqiy BanRecord qaytaradi — uni 'ban' hodisasida ham yuboramiz,
121
+ // shunda iste'molchilar (dashboard/Alerter/custom handler) record.offenseCount
122
+ // kabi maydonlarga tayana oladi (firewall'ning o'z bani bilan izchil).
123
+ const record = options.firewall.blocker.ban(ip, `Honeypot: ${rawPath}`);
124
+ if (typeof options.firewall.emit === 'function') {
125
+ options.firewall.emit('ban', { ip, reason: `Honeypot: ${rawPath}`, record });
126
+ }
127
+ }
128
+
129
+ // 2. OS firewall'da ham bloklash (ixtiyoriy).
130
+ if (options.osBlock && options.osFirewall) {
131
+ Promise.resolve(options.osFirewall.block(ip, { autoApprove: true }))
132
+ .catch((e) => logger.error(`Honeypot OS-blok xatosi: ${e.message}`));
133
+ }
134
+
135
+ // 3. Foydalanuvchi hook'i.
136
+ if (typeof options.onTrap === 'function') {
137
+ try { options.onTrap({ ip, path: rawPath }); } catch { /* ignore */ }
138
+ }
139
+
140
+ // Tabiiy ko'rinishi uchun oddiy 404 qaytaramiz (hujumchiga bilintirmaymiz).
141
+ return res.status
142
+ ? res.status(statusCode).json({ error: 'Not found' })
143
+ : (res.statusCode = statusCode, res.end('Not found'));
144
+ };
145
+ }
146
+
147
+ module.exports = honeypot;
148
+ module.exports.honeypot = honeypot;
149
+ module.exports.DEFAULT_TRAPS = DEFAULT_TRAPS;