biofirewall 1.0.0 → 2.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.
- package/README.md +325 -0
- package/SKILL.md +713 -86
- package/client.js +138 -0
- package/index.js +22 -8
- package/middleware.js +111 -0
- package/package.json +17 -13
- package/rules.js +100 -0
- package/lib/biofirewall.js +0 -75
- package/lib/challenge.js +0 -28
- package/lib/rules.js +0 -31
package/client.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BioFirewall Client - HTTP client for central API
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const axios = require('axios');
|
|
6
|
+
|
|
7
|
+
class BioFirewallClient {
|
|
8
|
+
constructor(apiUrl, options = {}) {
|
|
9
|
+
this.apiUrl = apiUrl.replace(/\/$/, ''); // Remove trailing slash
|
|
10
|
+
this.timeout = options.timeout || 5000;
|
|
11
|
+
this.cacheTokens = options.cacheTokens !== false;
|
|
12
|
+
this.cacheTTL = options.cacheTTL || 30000; // 30 seconds
|
|
13
|
+
|
|
14
|
+
// Token cache
|
|
15
|
+
this.tokenCache = new Map();
|
|
16
|
+
|
|
17
|
+
// HTTP client
|
|
18
|
+
this.http = axios.create({
|
|
19
|
+
baseURL: this.apiUrl,
|
|
20
|
+
timeout: this.timeout,
|
|
21
|
+
headers: {
|
|
22
|
+
'Content-Type': 'application/json',
|
|
23
|
+
'User-Agent': 'BioFirewallClient/3.0'
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Verify token with central API
|
|
30
|
+
* Caches result to reduce API calls
|
|
31
|
+
*/
|
|
32
|
+
async verifyToken(agentId, token) {
|
|
33
|
+
const cacheKey = `${agentId}:${token}`;
|
|
34
|
+
|
|
35
|
+
// Check cache
|
|
36
|
+
if (this.cacheTokens && this.tokenCache.has(cacheKey)) {
|
|
37
|
+
const cached = this.tokenCache.get(cacheKey);
|
|
38
|
+
if (Date.now() - cached.timestamp < this.cacheTTL) {
|
|
39
|
+
return cached.data;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
// Call central API
|
|
45
|
+
const response = await this.http.post('/verify', {
|
|
46
|
+
agentId,
|
|
47
|
+
token
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const result = response.data;
|
|
51
|
+
|
|
52
|
+
// Cache the result
|
|
53
|
+
if (this.cacheTokens) {
|
|
54
|
+
this.tokenCache.set(cacheKey, {
|
|
55
|
+
data: result,
|
|
56
|
+
timestamp: Date.now()
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return result;
|
|
61
|
+
|
|
62
|
+
} catch (error) {
|
|
63
|
+
// Return error response
|
|
64
|
+
const errorResponse = error.response?.data || {
|
|
65
|
+
success: false,
|
|
66
|
+
valid: false,
|
|
67
|
+
error: 'API_ERROR',
|
|
68
|
+
message: error.message
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return errorResponse;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get agent info from central API
|
|
77
|
+
*/
|
|
78
|
+
async getAgent(agentId) {
|
|
79
|
+
try {
|
|
80
|
+
const response = await this.http.get(`/agents/${agentId}`);
|
|
81
|
+
return response.data;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
error: error.response?.data?.error || 'API_ERROR',
|
|
86
|
+
message: error.message
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get global statistics
|
|
93
|
+
*/
|
|
94
|
+
async getStats() {
|
|
95
|
+
try {
|
|
96
|
+
const response = await this.http.get('/agents/stats/global');
|
|
97
|
+
return response.data;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
error: 'API_ERROR',
|
|
102
|
+
message: error.message
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Health check
|
|
109
|
+
*/
|
|
110
|
+
async health() {
|
|
111
|
+
try {
|
|
112
|
+
const response = await this.http.get('/health');
|
|
113
|
+
return response.data;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return {
|
|
116
|
+
status: 'unhealthy',
|
|
117
|
+
error: error.message
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Clear token cache
|
|
124
|
+
*/
|
|
125
|
+
clearCache() {
|
|
126
|
+
this.tokenCache.clear();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Clear specific token from cache
|
|
131
|
+
*/
|
|
132
|
+
clearCacheEntry(agentId, token) {
|
|
133
|
+
const cacheKey = `${agentId}:${token}`;
|
|
134
|
+
this.tokenCache.delete(cacheKey);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = BioFirewallClient;
|
package/index.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* BioFirewall Client Module
|
|
3
|
+
* Express middleware for agent authentication
|
|
4
|
+
*/
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
const createMiddleware = require('./middleware');
|
|
7
|
+
const BioFirewallClient = require('./client');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Main export: Express middleware factory
|
|
11
|
+
*/
|
|
12
|
+
function BioFirewall(options) {
|
|
13
|
+
// If options is a string, treat it as apiUrl
|
|
14
|
+
if (typeof options === 'string') {
|
|
15
|
+
options = { apiUrl: options };
|
|
16
|
+
}
|
|
6
17
|
|
|
7
|
-
|
|
8
|
-
|
|
18
|
+
return createMiddleware(options);
|
|
19
|
+
}
|
|
9
20
|
|
|
10
|
-
// Export
|
|
11
|
-
|
|
21
|
+
// Export components
|
|
22
|
+
BioFirewall.createMiddleware = createMiddleware;
|
|
23
|
+
BioFirewall.Client = BioFirewallClient;
|
|
24
|
+
|
|
25
|
+
module.exports = BioFirewall;
|
package/middleware.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BioFirewall Client Middleware
|
|
3
|
+
* Express middleware to protect routes with agent authentication
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const BioFirewallClient = require('./client');
|
|
7
|
+
const rules = require('./rules');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create BioFirewall middleware
|
|
11
|
+
*/
|
|
12
|
+
function createMiddleware(options = {}) {
|
|
13
|
+
const {
|
|
14
|
+
apiUrl,
|
|
15
|
+
blockBrowsers = true,
|
|
16
|
+
enforceAuthentication = true,
|
|
17
|
+
cacheTokens = true,
|
|
18
|
+
cacheTTL = 30000
|
|
19
|
+
} = options;
|
|
20
|
+
|
|
21
|
+
if (!apiUrl) {
|
|
22
|
+
throw new Error('apiUrl is required');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Create client
|
|
26
|
+
const client = new BioFirewallClient(apiUrl, {
|
|
27
|
+
cacheTokens,
|
|
28
|
+
cacheTTL
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Return middleware function
|
|
32
|
+
return async (req, res, next) => {
|
|
33
|
+
try {
|
|
34
|
+
// Layer 1: Passive filtering (User-Agent detection)
|
|
35
|
+
if (blockBrowsers && rules.isHuman(req)) {
|
|
36
|
+
return res.status(406).json({
|
|
37
|
+
success: false,
|
|
38
|
+
error: 'BIOLOGICAL_ENTITY_DETECTED',
|
|
39
|
+
message: 'This resource is reserved for automated agents',
|
|
40
|
+
tip: 'Use an API client. Ensure User-Agent does not look like a browser'
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Layer 2: Authentication
|
|
45
|
+
if (enforceAuthentication) {
|
|
46
|
+
const agentId = req.headers['x-bio-agent-id'];
|
|
47
|
+
const token = req.headers['x-bio-token'];
|
|
48
|
+
|
|
49
|
+
// Check headers present
|
|
50
|
+
if (!rules.hasValidAgentHeaders(req)) {
|
|
51
|
+
return res.status(428).json({
|
|
52
|
+
success: false,
|
|
53
|
+
error: 'AUTHENTICATION_REQUIRED',
|
|
54
|
+
message: 'Valid agent token required to access this resource',
|
|
55
|
+
protocol: {
|
|
56
|
+
version: '3.0',
|
|
57
|
+
method: 'JWT + RS256',
|
|
58
|
+
headers: {
|
|
59
|
+
'X-Bio-Agent-Id': 'Your agent ID from registration',
|
|
60
|
+
'X-Bio-Token': 'JWT token signed with your private key'
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
documentation: 'https://github.com/openclaw/biofirewall',
|
|
64
|
+
hint: 'Register your agent first at POST /register'
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Verify token with central API
|
|
69
|
+
const verification = await client.verifyToken(agentId, token);
|
|
70
|
+
|
|
71
|
+
if (!verification.valid) {
|
|
72
|
+
const statusCode = verification.error === 'AGENT_NOT_FOUND' ? 428 :
|
|
73
|
+
verification.error === 'AGENT_NOT_ACTIVE' ? 403 :
|
|
74
|
+
401;
|
|
75
|
+
|
|
76
|
+
return res.status(statusCode).json({
|
|
77
|
+
success: false,
|
|
78
|
+
error: verification.error,
|
|
79
|
+
message: verification.message,
|
|
80
|
+
hint: verification.error === 'AGENT_NOT_FOUND'
|
|
81
|
+
? 'Register at POST /register'
|
|
82
|
+
: 'Check your token and try again'
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Attach agent info to request
|
|
87
|
+
req.agent = verification.agent;
|
|
88
|
+
|
|
89
|
+
// Add response headers
|
|
90
|
+
res.set({
|
|
91
|
+
'X-Bio-Verified': 'true',
|
|
92
|
+
'X-Bio-Agent-Name': verification.agent.name,
|
|
93
|
+
'X-Bio-Version': '3.0'
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
next();
|
|
98
|
+
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error('BioFirewall middleware error:', error);
|
|
101
|
+
|
|
102
|
+
res.status(500).json({
|
|
103
|
+
success: false,
|
|
104
|
+
error: 'INTERNAL_ERROR',
|
|
105
|
+
message: 'Authentication system error'
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = createMiddleware;
|
package/package.json
CHANGED
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "biofirewall",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "BioFirewall Client - Express middleware to protect routes with agent authentication",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "
|
|
8
|
-
"start": "node examples/server.js",
|
|
9
|
-
"demo:bot": "node examples/bot.js"
|
|
7
|
+
"test": "jest"
|
|
10
8
|
},
|
|
11
9
|
"keywords": [
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"reverse-captcha",
|
|
15
|
-
"pow",
|
|
16
|
-
"express",
|
|
10
|
+
"biofirewall",
|
|
11
|
+
"client",
|
|
17
12
|
"middleware",
|
|
18
|
-
"
|
|
13
|
+
"express",
|
|
14
|
+
"authentication",
|
|
15
|
+
"agents"
|
|
19
16
|
],
|
|
20
17
|
"author": "OpenClaw Community",
|
|
21
18
|
"license": "MIT",
|
|
22
19
|
"dependencies": {
|
|
23
|
-
"axios": "^1.
|
|
24
|
-
"express": "^4.
|
|
20
|
+
"axios": "^1.6.0",
|
|
21
|
+
"express": "^4.18.2"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"jest": "^29.7.0",
|
|
25
|
+
"supertest": "^6.3.3"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"express": "^4.0.0"
|
|
25
29
|
}
|
|
26
30
|
}
|
package/rules.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BioFirewall Client - Security Rules
|
|
3
|
+
* User-Agent validation (local, no API calls needed)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Patterns that indicate a human browser
|
|
7
|
+
const BROWSER_PATTERNS = [
|
|
8
|
+
/mozilla.*firefox/i,
|
|
9
|
+
/mozilla.*safari.*chrome/i, // Chrome
|
|
10
|
+
/mozilla.*safari(?!.*linux)/i, // Safari (not Linux)
|
|
11
|
+
/mozilla.*edge/i, // Edge
|
|
12
|
+
/mozilla.*trident.*rv:11/i, // IE 11
|
|
13
|
+
/mozilla.*applewebkit/i, // WebKit-based
|
|
14
|
+
/opr\//i, // Opera
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
// Patterns that indicate a legitimate bot/agent
|
|
18
|
+
const BOT_PATTERNS = [
|
|
19
|
+
/bot/i,
|
|
20
|
+
/agent/i,
|
|
21
|
+
/crawler/i,
|
|
22
|
+
/curl/i,
|
|
23
|
+
/wget/i,
|
|
24
|
+
/python/i,
|
|
25
|
+
/java(?!script)/i,
|
|
26
|
+
/axios/i,
|
|
27
|
+
/node/i,
|
|
28
|
+
/go-http-client/i,
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
/**
|
|
33
|
+
* Check if request appears to be from a human (browser)
|
|
34
|
+
*/
|
|
35
|
+
isHuman: (req) => {
|
|
36
|
+
const userAgent = (req.get('User-Agent') || '').trim();
|
|
37
|
+
const accept = (req.get('Accept') || '').trim();
|
|
38
|
+
const acceptLanguage = req.get('Accept-Language');
|
|
39
|
+
|
|
40
|
+
// Rule 1: Accept header containing text/html = human
|
|
41
|
+
if (accept.includes('text/html')) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Rule 2: Accept-Language header = human trait
|
|
46
|
+
if (acceptLanguage) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Rule 3: No User-Agent = suspicious but check other traits
|
|
51
|
+
if (!userAgent) {
|
|
52
|
+
return !this._isBotUser(req);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Rule 4: Check against browser patterns
|
|
56
|
+
const isBrowserLike = BROWSER_PATTERNS.some(pattern => pattern.test(userAgent));
|
|
57
|
+
|
|
58
|
+
if (isBrowserLike) {
|
|
59
|
+
// Could still be a bot pretending to be a browser
|
|
60
|
+
if (this._isBotUser(req)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Rule 5: Check if it matches legitimate bot patterns
|
|
67
|
+
const isBot = BOT_PATTERNS.some(pattern => pattern.test(userAgent));
|
|
68
|
+
|
|
69
|
+
if (isBot) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Default: DENY (prefer blocking a bot over allowing a human)
|
|
74
|
+
return true;
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Helper: Check if request has bot-like characteristics
|
|
79
|
+
*/
|
|
80
|
+
_isBotUser: (req) => {
|
|
81
|
+
const userAgent = (req.get('User-Agent') || '').toLowerCase();
|
|
82
|
+
const accept = (req.get('Accept') || '').toLowerCase();
|
|
83
|
+
|
|
84
|
+
const asksForJson = accept.includes('application/json');
|
|
85
|
+
const asksForHtml = accept.includes('text/html');
|
|
86
|
+
const hasProperUA = BOT_PATTERNS.some(pattern => pattern.test(userAgent));
|
|
87
|
+
|
|
88
|
+
return (asksForJson || hasProperUA) && !asksForHtml;
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check if request has valid agent authentication headers
|
|
93
|
+
*/
|
|
94
|
+
hasValidAgentHeaders: (req) => {
|
|
95
|
+
const token = req.headers['x-bio-token'];
|
|
96
|
+
const agentId = req.headers['x-bio-agent-id'];
|
|
97
|
+
|
|
98
|
+
return !!(token && agentId);
|
|
99
|
+
}
|
|
100
|
+
};
|
package/lib/biofirewall.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
const rules = require('./rules');
|
|
2
|
-
const challenge = require('./challenge');
|
|
3
|
-
|
|
4
|
-
class BioFirewall {
|
|
5
|
-
constructor(options = {}) {
|
|
6
|
-
this.options = {
|
|
7
|
-
blockBrowsers: true,
|
|
8
|
-
enforceChallenge: true,
|
|
9
|
-
challengeDifficulty: 3, // Number of leading zeros required
|
|
10
|
-
...options
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
middleware() {
|
|
15
|
-
return (req, res, next) => {
|
|
16
|
-
// 1. Passive Filtering (The Silicon Curtain)
|
|
17
|
-
if (this.options.blockBrowsers) {
|
|
18
|
-
if (rules.isHuman(req)) {
|
|
19
|
-
return res.status(406).type('application/json').json({
|
|
20
|
-
error: "BIOLOGICAL_ENTITY_DETECTED",
|
|
21
|
-
message: "This resource is reserved for automated agents.",
|
|
22
|
-
tip: "Use an API client or disable 'human' headers."
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// 2. Active Filtering (The Computational Toll)
|
|
28
|
-
if (this.options.enforceChallenge) {
|
|
29
|
-
const solution = req.headers['x-bio-solution'];
|
|
30
|
-
|
|
31
|
-
if (!solution) {
|
|
32
|
-
return this.sendChallenge(res);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Verify specific challenge solution
|
|
36
|
-
// In a real/stateless app, we might sign the seed.
|
|
37
|
-
// For this demo, we verify the solution satisfies the generic work requirement.
|
|
38
|
-
// To be fully secure, the 'seed' should be verified as one we issued recently.
|
|
39
|
-
const originalSeed = req.headers['x-bio-challenge-seed']; // Client passes back the seed they solved for
|
|
40
|
-
|
|
41
|
-
if (!originalSeed || !challenge.verify(originalSeed, solution, this.options.challengeDifficulty)) {
|
|
42
|
-
return res.status(403).json({
|
|
43
|
-
error: "INVALID_COMPUTATION",
|
|
44
|
-
message: "Proof of work failed or insufficient difficulty."
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
next();
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
sendChallenge(res) {
|
|
54
|
-
const seed = challenge.generateSeed();
|
|
55
|
-
const difficulty = this.options.challengeDifficulty;
|
|
56
|
-
|
|
57
|
-
// 428 Precondition Required
|
|
58
|
-
res.status(428).set({
|
|
59
|
-
'X-Bio-Challenge-Algo': 'sha256',
|
|
60
|
-
'X-Bio-Challenge-Difficulty': difficulty,
|
|
61
|
-
'X-Bio-Challenge-Seed': seed
|
|
62
|
-
}).json({
|
|
63
|
-
error: "COMPUTATION_REQUIRED",
|
|
64
|
-
message: "Solve the puzzle to prove silicon heritage.",
|
|
65
|
-
challenge: {
|
|
66
|
-
algo: "sha256",
|
|
67
|
-
seed: seed,
|
|
68
|
-
difficulty: difficulty,
|
|
69
|
-
instruction: `Find a nonce where sha256(seed + nonce) starts with '${'0'.repeat(difficulty)}'`
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
module.exports = BioFirewall;
|
package/lib/challenge.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
const crypto = require('crypto');
|
|
2
|
-
|
|
3
|
-
module.exports = {
|
|
4
|
-
generateSeed: () => {
|
|
5
|
-
return crypto.randomBytes(16).toString('hex');
|
|
6
|
-
},
|
|
7
|
-
|
|
8
|
-
verify: (seed, nonce, difficulty) => {
|
|
9
|
-
const hash = crypto.createHash('sha256').update(seed + nonce).digest('hex');
|
|
10
|
-
const prefix = '0'.repeat(difficulty);
|
|
11
|
-
return hash.startsWith(prefix);
|
|
12
|
-
},
|
|
13
|
-
|
|
14
|
-
// Helper for bots (exported so bots can use this alg)
|
|
15
|
-
solve: (seed, difficulty) => {
|
|
16
|
-
const prefix = '0'.repeat(difficulty);
|
|
17
|
-
let nonce = 0;
|
|
18
|
-
while (true) {
|
|
19
|
-
const hash = crypto.createHash('sha256').update(seed + String(nonce)).digest('hex');
|
|
20
|
-
if (hash.startsWith(prefix)) {
|
|
21
|
-
return String(nonce);
|
|
22
|
-
}
|
|
23
|
-
nonce++;
|
|
24
|
-
// Safety break for testing to avoid infinite loop if difficulty is crazy high
|
|
25
|
-
if (nonce > 10000000) return null;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
};
|
package/lib/rules.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
isHuman: (req) => {
|
|
3
|
-
const userAgent = req.get('User-Agent') || '';
|
|
4
|
-
const accept = req.get('Accept') || '';
|
|
5
|
-
|
|
6
|
-
// Rule 1: Reject common Browser User-Agents
|
|
7
|
-
const browserKeywords = ['Mozilla', 'Chrome', 'Safari', 'Edge', 'Firefox', 'AppleWebKit'];
|
|
8
|
-
const isBrowserAgent = browserKeywords.some(keyword => userAgent.includes(keyword));
|
|
9
|
-
|
|
10
|
-
// Rule 2: Humans usually ask for HTML
|
|
11
|
-
const asksForHtml = accept.includes('text/html');
|
|
12
|
-
|
|
13
|
-
// Rule 3: Missing User-Agent is suspicious for humans?
|
|
14
|
-
// Actually bots often have no UA or proper UA.
|
|
15
|
-
// Humans almost ALWAYS have a sophisticated UA string.
|
|
16
|
-
|
|
17
|
-
if (isBrowserAgent) {
|
|
18
|
-
// Allow override if they explicitly identify as a bot in UA?
|
|
19
|
-
// For this specific 'anti-human' firewall, if it looks like a browser, kill it.
|
|
20
|
-
if (!userAgent.toLowerCase().includes('bot') && !userAgent.toLowerCase().includes('openclaw')) {
|
|
21
|
-
return true;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (asksForHtml) {
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
};
|