@riavzon/bot-detector-create 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.
@@ -0,0 +1,308 @@
1
+ #!/usr/bin/env node
2
+ import consola from "consola";
3
+ import { defineCommand, runMain } from "citty";
4
+ import fs from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { spawn } from "node:child_process";
7
+ //#region src/default.ts
8
+ const content = `import { defineConfiguration } from '@riavzon/bot-detector';
9
+
10
+ /**
11
+ * Bot Detector configuration — all values shown are the defaults.
12
+ * Tune penalties, enable/disable checkers, and swap adapters as needed.
13
+ *
14
+ * Next steps:
15
+ * Import this file before mounting the middleware in your app entry point
16
+ *
17
+ * Keep data sources fresh:
18
+ * npx @riavzon/bot-detector refresh
19
+ */
20
+ await defineConfiguration({
21
+
22
+ // ─── Database (SQLite default — swap for mysql-pool / postgresql in production)
23
+ store: {
24
+ main: {
25
+ driver: 'sqlite',
26
+ name: './bot_detector.sqlite',
27
+ },
28
+ },
29
+
30
+ // ─── Cache (default: in-process memory — uncomment to use a different one)
31
+ // storage: { driver: 'lru', max: 500, ttl: 1000 * 60 * 60 * 2 },
32
+ // storage: { driver: 'redis', url: process.env.REDIS_URL ?? 'redis://localhost:6379' },
33
+ // storage: { driver: 'upstash', url: process.env.UPSTASH_URL, token: process.env.UPSTASH_TOKEN },
34
+
35
+ // ─── Core scoring ──────────────────────────────────────────────────────────
36
+ banScore: 100,
37
+ maxScore: 100,
38
+ restoredReputationPoints: 10,
39
+ setNewComputedScore: false,
40
+
41
+ // ─── Whitelist (IPv4, IPv6, or CIDR) ───────────────────────────────────────
42
+ whiteList: [],
43
+
44
+ // ─── Re-check interval for returning visitors ──────────────────────────────
45
+ checksTimeRateControl: {
46
+ checkEveryRequest: true,
47
+ checkEvery: 1000 * 60 * 5,
48
+ },
49
+
50
+ // ─── Async batch write queue ───────────────────────────────────────────────
51
+ batchQueue: {
52
+ flushIntervalMs: 5000,
53
+ maxBufferSize: 100,
54
+ maxRetries: 3,
55
+ },
56
+
57
+ // ─── Logging ───────────────────────────────────────────────────────────────
58
+ logLevel: 'info',
59
+
60
+ // ─── Firewall ban (requires sudo ufw) ──────────────────────────────────────
61
+ punishmentType: {
62
+ enableFireWallBan: false,
63
+ },
64
+
65
+ // ─── Custom MMDB generation from visitor history ───────────────────────────
66
+ generator: {
67
+ scoreThreshold: 70,
68
+ generateTypes: false,
69
+ deleteAfterBuild: false,
70
+ mmdbctlPath: 'mmdbctl',
71
+ },
72
+
73
+ // ─── HTTP header anomaly penalties ─────────────────────────────────────────
74
+ headerOptions: {
75
+ weightPerMustHeader: 20,
76
+ missingBrowserEngine: 30,
77
+ postManOrInsomiaHeaders: 50,
78
+ AJAXHeaderExists: 30,
79
+ connectionHeaderIsClose: 20,
80
+ originHeaderIsNULL: 10,
81
+ originHeaderMismatch: 30,
82
+ omittedAcceptHeader: 30,
83
+ clientHintsMissingForBlink: 30,
84
+ teHeaderUnexpectedForBlink: 10,
85
+ clientHintsUnexpectedForGecko: 30,
86
+ teHeaderMissingForGecko: 20,
87
+ aggressiveCacheControlOnGet: 15,
88
+ crossSiteRequestMissingReferer: 10,
89
+ inconsistentSecFetchMode: 20,
90
+ hostMismatchWeight: 40,
91
+ },
92
+
93
+ // ─── Path traversal detection ──────────────────────────────────────────────
94
+ pathTraveler: {
95
+ maxIterations: 3,
96
+ maxPathLength: 1500,
97
+ pathLengthToLong: 100,
98
+ longDecoding: 100,
99
+ traversalDetected: 60,
100
+ },
101
+
102
+ // ─── Checkers (set enable: false to disable any individual checker) ─────────
103
+ checkers: {
104
+
105
+ localeMapsCheck: {
106
+ enable: true,
107
+ penalties: {
108
+ ipAndHeaderMismatch: 20,
109
+ missingHeader: 20,
110
+ missingGeoData: 20,
111
+ malformedHeader: 30,
112
+ },
113
+ },
114
+
115
+ knownBadUserAgents: {
116
+ enable: true,
117
+ penalties: {
118
+ criticalSeverity: 100,
119
+ highSeverity: 80,
120
+ mediumSeverity: 30,
121
+ lowSeverity: 10,
122
+ },
123
+ },
124
+
125
+ enableIpChecks: {
126
+ enable: true,
127
+ penalties: 10,
128
+ },
129
+
130
+ enableGoodBotsChecks: {
131
+ enable: true,
132
+ banUnlistedBots: true,
133
+ penalties: 100,
134
+ },
135
+
136
+ enableBehaviorRateCheck: {
137
+ enable: true,
138
+ behavioral_window: 60_000,
139
+ behavioral_threshold: 30,
140
+ penalties: 60,
141
+ },
142
+
143
+ enableProxyIspCookiesChecks: {
144
+ enable: true,
145
+ penalties: {
146
+ cookieMissing: 80,
147
+ proxyDetected: 40,
148
+ multiSourceBonus2to3: 10,
149
+ multiSourceBonus4plus: 20,
150
+ hostingDetected: 50,
151
+ ispUnknown: 10,
152
+ orgUnknown: 10,
153
+ },
154
+ },
155
+
156
+ enableUaAndHeaderChecks: {
157
+ enable: true,
158
+ penalties: {
159
+ headlessBrowser: 100,
160
+ shortUserAgent: 80,
161
+ tlsCheckFailed: 60,
162
+ badUaChecker: true,
163
+ },
164
+ },
165
+
166
+ enableBrowserAndDeviceChecks: {
167
+ enable: true,
168
+ penalties: {
169
+ cliOrLibrary: 100,
170
+ internetExplorer: 100,
171
+ linuxOs: 10,
172
+ impossibleBrowserCombinations: 30,
173
+ browserTypeUnknown: 10,
174
+ browserNameUnknown: 10,
175
+ desktopWithoutOS: 10,
176
+ deviceVendorUnknown: 10,
177
+ browserVersionUnknown: 10,
178
+ deviceModelUnknown: 5,
179
+ },
180
+ },
181
+
182
+ enableGeoChecks: {
183
+ enable: true,
184
+ bannedCountries: [],
185
+ penalties: {
186
+ countryUnknown: 10,
187
+ regionUnknown: 10,
188
+ latLonUnknown: 10,
189
+ districtUnknown: 10,
190
+ cityUnknown: 10,
191
+ timezoneUnknown: 10,
192
+ subregionUnknown: 10,
193
+ phoneUnknown: 10,
194
+ continentUnknown: 10,
195
+ },
196
+ },
197
+
198
+ enableKnownThreatsDetections: {
199
+ enable: true,
200
+ penalties: {
201
+ anonymiseNetwork: 20,
202
+ threatLevels: {
203
+ criticalLevel1: 40,
204
+ currentAttacksLevel2: 30,
205
+ threatLevel3: 20,
206
+ threatLevel4: 10,
207
+ },
208
+ },
209
+ },
210
+
211
+ enableAsnClassification: {
212
+ enable: true,
213
+ penalties: {
214
+ contentClassification: 20,
215
+ unknownClassification: 10,
216
+ lowVisibilityPenalty: 10,
217
+ lowVisibilityThreshold: 15,
218
+ comboHostingLowVisibility: 20,
219
+ },
220
+ },
221
+
222
+ enableTorAnalysis: {
223
+ enable: true,
224
+ penalties: {
225
+ runningNode: 15,
226
+ exitNode: 20,
227
+ webExitCapable: 15,
228
+ guardNode: 10,
229
+ badExit: 40,
230
+ obsoleteVersion: 10,
231
+ },
232
+ },
233
+
234
+ enableTimezoneConsistency: {
235
+ enable: true,
236
+ penalties: 20,
237
+ },
238
+
239
+ honeypot: {
240
+ enable: true,
241
+ paths: ['/.env', '/wp-admin', '/wp-login.php', '/.git/config', '/phpinfo.php'],
242
+ },
243
+
244
+ enableSessionCoherence: {
245
+ enable: true,
246
+ penalties: {
247
+ pathMismatch: 10,
248
+ missingReferer: 20,
249
+ domainMismatch: 30,
250
+ },
251
+ },
252
+
253
+ enableVelocityFingerprint: {
254
+ enable: true,
255
+ cvThreshold: 0.1,
256
+ penalties: 40,
257
+ },
258
+
259
+ enableKnownBadIpsCheck: {
260
+ enable: true,
261
+ highRiskPenalty: 30,
262
+ },
263
+ },
264
+ });
265
+ `;
266
+ //#endregion
267
+ //#region src/create.ts
268
+ function run(cmd, args) {
269
+ return new Promise((resolve, reject) => {
270
+ spawn(cmd, args, {
271
+ stdio: "inherit",
272
+ shell: true
273
+ }).on("close", (code) => {
274
+ if (code === 0) resolve();
275
+ else reject(/* @__PURE__ */ new Error(`${cmd} exited with code ${String(code)}`));
276
+ });
277
+ });
278
+ }
279
+ const start = defineCommand({
280
+ meta: {
281
+ name: "Create Bot Detector",
282
+ description: "Quick starter for @riavzon/bot-detector"
283
+ },
284
+ async run() {
285
+ const output = path.resolve(process.cwd(), "botDetectorConfig.ts");
286
+ consola.start("Installing dependencies...");
287
+ await run("npm", [
288
+ "install",
289
+ "express",
290
+ "cookie-parser",
291
+ "better-sqlite3"
292
+ ]);
293
+ consola.start("Installing @riavzon/bot-detector and data sources...");
294
+ await run("npm", ["install", "@riavzon/bot-detector"]);
295
+ consola.start("Writing botDetectorConfig.ts...");
296
+ await fs.writeFile(output, content, "utf-8");
297
+ consola.success("botDetectorConfig.ts created");
298
+ consola.start("Creating database tables...");
299
+ await run("npx", ["@riavzon/bot-detector", "load-schema"]);
300
+ consola.success("Setup complete. Import botDetectorConfig.ts at the top of your app entry point and mount the middleware.");
301
+ consola.log("");
302
+ consola.log("Keep data sources fresh (run daily or via cron):");
303
+ consola.log(" npx @riavzon/bot-detector refresh");
304
+ }
305
+ });
306
+ await runMain(start);
307
+ //#endregion
308
+ export { start };
@@ -0,0 +1,6 @@
1
+ import { defineStrictTSConfig } from '@riavzon/utils/eslint/strict';
2
+
3
+ export default defineStrictTSConfig({
4
+ rootDir: import.meta.dirname,
5
+ extraIgnores: ['tsdown.config.ts'],
6
+ });
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@riavzon/bot-detector-create",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "author": "Sergio contact@riavzon.com",
6
+ "license": "Apache-2.0",
7
+ "description": "Setup package for @riavzon/bot-detector",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/Sergo706/botDetector"
11
+ },
12
+ "homepage": "https://github.com/Sergo706/botDetector#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/Sergo706/botDetector/issues"
15
+ },
16
+ "keywords": [
17
+ "bot-detection",
18
+ "express-middleware",
19
+ "security",
20
+ "geoip",
21
+ "geolocation",
22
+ "threat-intelligence",
23
+ "mmdb",
24
+ "bot detector",
25
+ "detect bots"
26
+ ],
27
+ "bin": {
28
+ "create-bot-detector": "./dist/create.mjs"
29
+ },
30
+ "scripts": {
31
+ "build": "tsdown",
32
+ "prepublishOnly": "npm run build"
33
+ },
34
+ "dependencies": {
35
+ "citty": "0.2.1",
36
+ "consola": "3.4.2"
37
+ },
38
+ "devDependencies": {
39
+ "tsdown": "latest",
40
+ "typescript": "latest"
41
+ }
42
+ }
package/src/create.ts ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+
3
+ import consola from 'consola';
4
+ import { defineCommand, runMain } from 'citty';
5
+ import fs from 'node:fs/promises';
6
+ import path from 'node:path';
7
+ import { spawn } from 'node:child_process';
8
+ import { content } from './default.js';
9
+
10
+ function run(cmd: string, args: string[]): Promise<void> {
11
+ return new Promise((resolve, reject) => {
12
+ const child = spawn(cmd, args, { stdio: 'inherit', shell: true });
13
+ child.on('close', code => {
14
+ if (code === 0) { resolve(); } else { reject(new Error(`${cmd} exited with code ${String(code)}`)); }
15
+ });
16
+ });
17
+ }
18
+
19
+ export const start = defineCommand({
20
+ meta: {
21
+ name: 'Create Bot Detector',
22
+ description: 'Quick starter for @riavzon/bot-detector'
23
+ },
24
+
25
+ async run() {
26
+ const output = path.resolve(process.cwd(), 'botDetectorConfig.ts');
27
+
28
+ consola.start('Installing dependencies...');
29
+ await run('npm', ['install', 'express', 'cookie-parser', 'better-sqlite3']);
30
+
31
+ consola.start('Installing @riavzon/bot-detector and data sources...');
32
+ await run('npm', ['install', '@riavzon/bot-detector']);
33
+
34
+
35
+ consola.start('Writing botDetectorConfig.ts...');
36
+ await fs.writeFile(output, content, 'utf-8');
37
+ consola.success('botDetectorConfig.ts created');
38
+
39
+ consola.start('Creating database tables...');
40
+ await run('npx', ['@riavzon/bot-detector', 'load-schema']);
41
+
42
+ consola.success('Setup complete. Import botDetectorConfig.ts at the top of your app entry point and mount the middleware.');
43
+ consola.log('');
44
+ consola.log('Keep data sources fresh (run daily or via cron):');
45
+ consola.log(' npx @riavzon/bot-detector refresh');
46
+ }
47
+ });
48
+ await runMain(start);
package/src/default.ts ADDED
@@ -0,0 +1,258 @@
1
+ export const content = `import { defineConfiguration } from '@riavzon/bot-detector';
2
+
3
+ /**
4
+ * Bot Detector configuration — all values shown are the defaults.
5
+ * Tune penalties, enable/disable checkers, and swap adapters as needed.
6
+ *
7
+ * Next steps:
8
+ * Import this file before mounting the middleware in your app entry point
9
+ *
10
+ * Keep data sources fresh:
11
+ * npx @riavzon/bot-detector refresh
12
+ */
13
+ await defineConfiguration({
14
+
15
+ // ─── Database (SQLite default — swap for mysql-pool / postgresql in production)
16
+ store: {
17
+ main: {
18
+ driver: 'sqlite',
19
+ name: './bot_detector.sqlite',
20
+ },
21
+ },
22
+
23
+ // ─── Cache (default: in-process memory — uncomment to use a different one)
24
+ // storage: { driver: 'lru', max: 500, ttl: 1000 * 60 * 60 * 2 },
25
+ // storage: { driver: 'redis', url: process.env.REDIS_URL ?? 'redis://localhost:6379' },
26
+ // storage: { driver: 'upstash', url: process.env.UPSTASH_URL, token: process.env.UPSTASH_TOKEN },
27
+
28
+ // ─── Core scoring ──────────────────────────────────────────────────────────
29
+ banScore: 100,
30
+ maxScore: 100,
31
+ restoredReputationPoints: 10,
32
+ setNewComputedScore: false,
33
+
34
+ // ─── Whitelist (IPv4, IPv6, or CIDR) ───────────────────────────────────────
35
+ whiteList: [],
36
+
37
+ // ─── Re-check interval for returning visitors ──────────────────────────────
38
+ checksTimeRateControl: {
39
+ checkEveryRequest: true,
40
+ checkEvery: 1000 * 60 * 5,
41
+ },
42
+
43
+ // ─── Async batch write queue ───────────────────────────────────────────────
44
+ batchQueue: {
45
+ flushIntervalMs: 5000,
46
+ maxBufferSize: 100,
47
+ maxRetries: 3,
48
+ },
49
+
50
+ // ─── Logging ───────────────────────────────────────────────────────────────
51
+ logLevel: 'info',
52
+
53
+ // ─── Firewall ban (requires sudo ufw) ──────────────────────────────────────
54
+ punishmentType: {
55
+ enableFireWallBan: false,
56
+ },
57
+
58
+ // ─── Custom MMDB generation from visitor history ───────────────────────────
59
+ generator: {
60
+ scoreThreshold: 70,
61
+ generateTypes: false,
62
+ deleteAfterBuild: false,
63
+ mmdbctlPath: 'mmdbctl',
64
+ },
65
+
66
+ // ─── HTTP header anomaly penalties ─────────────────────────────────────────
67
+ headerOptions: {
68
+ weightPerMustHeader: 20,
69
+ missingBrowserEngine: 30,
70
+ postManOrInsomiaHeaders: 50,
71
+ AJAXHeaderExists: 30,
72
+ connectionHeaderIsClose: 20,
73
+ originHeaderIsNULL: 10,
74
+ originHeaderMismatch: 30,
75
+ omittedAcceptHeader: 30,
76
+ clientHintsMissingForBlink: 30,
77
+ teHeaderUnexpectedForBlink: 10,
78
+ clientHintsUnexpectedForGecko: 30,
79
+ teHeaderMissingForGecko: 20,
80
+ aggressiveCacheControlOnGet: 15,
81
+ crossSiteRequestMissingReferer: 10,
82
+ inconsistentSecFetchMode: 20,
83
+ hostMismatchWeight: 40,
84
+ },
85
+
86
+ // ─── Path traversal detection ──────────────────────────────────────────────
87
+ pathTraveler: {
88
+ maxIterations: 3,
89
+ maxPathLength: 1500,
90
+ pathLengthToLong: 100,
91
+ longDecoding: 100,
92
+ traversalDetected: 60,
93
+ },
94
+
95
+ // ─── Checkers (set enable: false to disable any individual checker) ─────────
96
+ checkers: {
97
+
98
+ localeMapsCheck: {
99
+ enable: true,
100
+ penalties: {
101
+ ipAndHeaderMismatch: 20,
102
+ missingHeader: 20,
103
+ missingGeoData: 20,
104
+ malformedHeader: 30,
105
+ },
106
+ },
107
+
108
+ knownBadUserAgents: {
109
+ enable: true,
110
+ penalties: {
111
+ criticalSeverity: 100,
112
+ highSeverity: 80,
113
+ mediumSeverity: 30,
114
+ lowSeverity: 10,
115
+ },
116
+ },
117
+
118
+ enableIpChecks: {
119
+ enable: true,
120
+ penalties: 10,
121
+ },
122
+
123
+ enableGoodBotsChecks: {
124
+ enable: true,
125
+ banUnlistedBots: true,
126
+ penalties: 100,
127
+ },
128
+
129
+ enableBehaviorRateCheck: {
130
+ enable: true,
131
+ behavioral_window: 60_000,
132
+ behavioral_threshold: 30,
133
+ penalties: 60,
134
+ },
135
+
136
+ enableProxyIspCookiesChecks: {
137
+ enable: true,
138
+ penalties: {
139
+ cookieMissing: 80,
140
+ proxyDetected: 40,
141
+ multiSourceBonus2to3: 10,
142
+ multiSourceBonus4plus: 20,
143
+ hostingDetected: 50,
144
+ ispUnknown: 10,
145
+ orgUnknown: 10,
146
+ },
147
+ },
148
+
149
+ enableUaAndHeaderChecks: {
150
+ enable: true,
151
+ penalties: {
152
+ headlessBrowser: 100,
153
+ shortUserAgent: 80,
154
+ tlsCheckFailed: 60,
155
+ badUaChecker: true,
156
+ },
157
+ },
158
+
159
+ enableBrowserAndDeviceChecks: {
160
+ enable: true,
161
+ penalties: {
162
+ cliOrLibrary: 100,
163
+ internetExplorer: 100,
164
+ linuxOs: 10,
165
+ impossibleBrowserCombinations: 30,
166
+ browserTypeUnknown: 10,
167
+ browserNameUnknown: 10,
168
+ desktopWithoutOS: 10,
169
+ deviceVendorUnknown: 10,
170
+ browserVersionUnknown: 10,
171
+ deviceModelUnknown: 5,
172
+ },
173
+ },
174
+
175
+ enableGeoChecks: {
176
+ enable: true,
177
+ bannedCountries: [],
178
+ penalties: {
179
+ countryUnknown: 10,
180
+ regionUnknown: 10,
181
+ latLonUnknown: 10,
182
+ districtUnknown: 10,
183
+ cityUnknown: 10,
184
+ timezoneUnknown: 10,
185
+ subregionUnknown: 10,
186
+ phoneUnknown: 10,
187
+ continentUnknown: 10,
188
+ },
189
+ },
190
+
191
+ enableKnownThreatsDetections: {
192
+ enable: true,
193
+ penalties: {
194
+ anonymiseNetwork: 20,
195
+ threatLevels: {
196
+ criticalLevel1: 40,
197
+ currentAttacksLevel2: 30,
198
+ threatLevel3: 20,
199
+ threatLevel4: 10,
200
+ },
201
+ },
202
+ },
203
+
204
+ enableAsnClassification: {
205
+ enable: true,
206
+ penalties: {
207
+ contentClassification: 20,
208
+ unknownClassification: 10,
209
+ lowVisibilityPenalty: 10,
210
+ lowVisibilityThreshold: 15,
211
+ comboHostingLowVisibility: 20,
212
+ },
213
+ },
214
+
215
+ enableTorAnalysis: {
216
+ enable: true,
217
+ penalties: {
218
+ runningNode: 15,
219
+ exitNode: 20,
220
+ webExitCapable: 15,
221
+ guardNode: 10,
222
+ badExit: 40,
223
+ obsoleteVersion: 10,
224
+ },
225
+ },
226
+
227
+ enableTimezoneConsistency: {
228
+ enable: true,
229
+ penalties: 20,
230
+ },
231
+
232
+ honeypot: {
233
+ enable: true,
234
+ paths: ['/.env', '/wp-admin', '/wp-login.php', '/.git/config', '/phpinfo.php'],
235
+ },
236
+
237
+ enableSessionCoherence: {
238
+ enable: true,
239
+ penalties: {
240
+ pathMismatch: 10,
241
+ missingReferer: 20,
242
+ domainMismatch: 30,
243
+ },
244
+ },
245
+
246
+ enableVelocityFingerprint: {
247
+ enable: true,
248
+ cvThreshold: 0.1,
249
+ penalties: 40,
250
+ },
251
+
252
+ enableKnownBadIpsCheck: {
253
+ enable: true,
254
+ highRiskPenalty: 30,
255
+ },
256
+ },
257
+ });
258
+ `;
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "baseUrl": ".",
6
+ "paths": {}
7
+ },
8
+ "include": ["src/**/*"],
9
+ "exclude": ["node_modules"]
10
+ }
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'tsdown';
2
+
3
+ export default defineConfig({
4
+ entry: ['./src/create.ts'],
5
+ format: ['esm'],
6
+ target: 'node18',
7
+ dts: false,
8
+ clean: true,
9
+ sourcemap: false,
10
+ });