biofirewall 1.0.0 → 1.0.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/index.js +1 -1
- package/lib/biofirewall.js +58 -22
- package/lib/challenge.js +25 -22
- package/package.json +3 -4
package/index.js
CHANGED
|
@@ -8,4 +8,4 @@ module.exports = BioFirewall;
|
|
|
8
8
|
module.exports.solve = challenge.solve;
|
|
9
9
|
|
|
10
10
|
// Export middleware directly if preferred
|
|
11
|
-
module.exports.middleware = (options) => new BioFirewall(options).middleware();
|
|
11
|
+
module.exports.middleware = (options) => new BioFirewall(options).middleware();
|
package/lib/biofirewall.js
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
1
2
|
const rules = require('./rules');
|
|
2
|
-
const challenge = require('./challenge');
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* BioFirewall - Proof of Work Challenge for Silicon Verification
|
|
6
|
+
* Ensures only agents (silicon) can access, not browsers (carbon)
|
|
7
|
+
*/
|
|
4
8
|
class BioFirewall {
|
|
5
9
|
constructor(options = {}) {
|
|
6
10
|
this.options = {
|
|
7
11
|
blockBrowsers: true,
|
|
8
12
|
enforceChallenge: true,
|
|
9
|
-
challengeDifficulty:
|
|
13
|
+
challengeDifficulty: options.difficulty || options.challengeDifficulty || 4,
|
|
14
|
+
sessionTimeout: 60000,
|
|
10
15
|
...options
|
|
11
16
|
};
|
|
12
17
|
}
|
|
13
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Express middleware
|
|
21
|
+
*/
|
|
14
22
|
middleware() {
|
|
15
23
|
return (req, res, next) => {
|
|
16
|
-
// 1. Passive Filtering (
|
|
24
|
+
// 1. Passive Filtering (Check User-Agent)
|
|
17
25
|
if (this.options.blockBrowsers) {
|
|
18
26
|
if (rules.isHuman(req)) {
|
|
19
27
|
return res.status(406).type('application/json').json({
|
|
@@ -24,37 +32,43 @@ class BioFirewall {
|
|
|
24
32
|
}
|
|
25
33
|
}
|
|
26
34
|
|
|
27
|
-
// 2. Active Filtering (
|
|
35
|
+
// 2. Active Filtering (PoW Challenge)
|
|
28
36
|
if (this.options.enforceChallenge) {
|
|
29
|
-
|
|
37
|
+
// Get proof headers (both formats supported for compatibility)
|
|
38
|
+
const nonce = req.headers['x-bio-solution'] || req.headers['x-proof-nonce'];
|
|
39
|
+
const seed = req.headers['x-bio-challenge-seed'] || req.headers['x-proof-seed'];
|
|
30
40
|
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
// If both are present, validate
|
|
42
|
+
if (nonce && seed) {
|
|
43
|
+
const isValid = this._validateProof(seed, nonce);
|
|
44
|
+
|
|
45
|
+
if (isValid) {
|
|
46
|
+
res.set('X-Silicon-Verified', 'true');
|
|
47
|
+
return next();
|
|
48
|
+
} else {
|
|
49
|
+
return res.status(403).json({
|
|
50
|
+
error: 'INVALID_PROOF',
|
|
51
|
+
message: 'Proof of Work validation failed',
|
|
52
|
+
challenge: this._generateChallenge()
|
|
53
|
+
});
|
|
54
|
+
}
|
|
33
55
|
}
|
|
34
56
|
|
|
35
|
-
//
|
|
36
|
-
|
|
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
|
-
}
|
|
57
|
+
// No proof, send challenge
|
|
58
|
+
return this.sendChallenge(res);
|
|
47
59
|
}
|
|
48
60
|
|
|
49
61
|
next();
|
|
50
62
|
};
|
|
51
63
|
}
|
|
52
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Send a PoW challenge to the client
|
|
67
|
+
*/
|
|
53
68
|
sendChallenge(res) {
|
|
54
|
-
const seed =
|
|
69
|
+
const seed = crypto.randomBytes(16).toString('hex');
|
|
55
70
|
const difficulty = this.options.challengeDifficulty;
|
|
56
71
|
|
|
57
|
-
// 428 Precondition Required
|
|
58
72
|
res.status(428).set({
|
|
59
73
|
'X-Bio-Challenge-Algo': 'sha256',
|
|
60
74
|
'X-Bio-Challenge-Difficulty': difficulty,
|
|
@@ -70,6 +84,28 @@ class BioFirewall {
|
|
|
70
84
|
}
|
|
71
85
|
});
|
|
72
86
|
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Generate a new challenge
|
|
90
|
+
*/
|
|
91
|
+
_generateChallenge() {
|
|
92
|
+
return {
|
|
93
|
+
algo: 'sha256',
|
|
94
|
+
seed: crypto.randomBytes(16).toString('hex'),
|
|
95
|
+
difficulty: this.options.challengeDifficulty
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Validate a nonce against a seed
|
|
101
|
+
*/
|
|
102
|
+
_validateProof(seed, nonce) {
|
|
103
|
+
const candidate = seed + nonce;
|
|
104
|
+
const hash = crypto.createHash('sha256').update(candidate).digest('hex');
|
|
105
|
+
const target = '0'.repeat(this.options.challengeDifficulty);
|
|
106
|
+
|
|
107
|
+
return hash.startsWith(target);
|
|
108
|
+
}
|
|
73
109
|
}
|
|
74
110
|
|
|
75
|
-
module.exports = BioFirewall;
|
|
111
|
+
module.exports = BioFirewall;
|
package/lib/challenge.js
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
|
-
const crypto = require('crypto');
|
|
2
|
-
|
|
3
1
|
module.exports = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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');
|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return hash.startsWith(prefix);
|
|
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.
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const hash = crypto.createHash('sha256').update(seed + String(nonce)).digest('hex');
|
|
20
|
-
if (hash.startsWith(prefix)) {
|
|
21
|
-
return String(nonce);
|
|
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
22
|
}
|
|
23
|
-
nonce++;
|
|
24
|
-
// Safety break for testing to avoid infinite loop if difficulty is crazy high
|
|
25
|
-
if (nonce > 10000000) return null;
|
|
26
23
|
}
|
|
24
|
+
|
|
25
|
+
if (asksForHtml) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return false;
|
|
27
30
|
}
|
|
28
|
-
};
|
|
31
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "biofirewall",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Anti-Human Firewall: Block browsers, allow bots via Proof-of-Work to verify silicon heritage.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
"author": "OpenClaw Community",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"axios": "^1.13.4"
|
|
24
|
-
"express": "^4.21.2"
|
|
23
|
+
"axios": "^1.13.4"
|
|
25
24
|
}
|
|
26
|
-
}
|
|
25
|
+
}
|