buzzster 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.
- package/.env.example +10 -0
- package/README.md +325 -0
- package/SKILL.md +765 -0
- package/client.js +138 -0
- package/index.js +25 -0
- package/middleware.js +111 -0
- package/package.json +30 -0
- package/rules.js +100 -0
- package/setup.sh +50 -0
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
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BioFirewall Client Module
|
|
3
|
+
* Express middleware for agent authentication
|
|
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
|
+
}
|
|
17
|
+
|
|
18
|
+
return createMiddleware(options);
|
|
19
|
+
}
|
|
20
|
+
|
|
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
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "buzzster",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Buzzster Client - Express middleware to protect routes with agent authentication",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "jest"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"buzzster",
|
|
11
|
+
"client",
|
|
12
|
+
"middleware",
|
|
13
|
+
"express",
|
|
14
|
+
"authentication",
|
|
15
|
+
"agents"
|
|
16
|
+
],
|
|
17
|
+
"author": "Comma",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"dependencies": {
|
|
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"
|
|
29
|
+
}
|
|
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/setup.sh
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# BioFirewall Client Setup Script
|
|
4
|
+
|
|
5
|
+
echo "🔒 BioFirewall Client - Setup"
|
|
6
|
+
echo "======================================"
|
|
7
|
+
|
|
8
|
+
# Check Node.js
|
|
9
|
+
if ! command -v node &> /dev/null; then
|
|
10
|
+
echo "❌ Node.js is not installed"
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
echo "✅ Node.js $(node --version)"
|
|
15
|
+
|
|
16
|
+
# Check if already in a project with express
|
|
17
|
+
if ! grep -q "\"express\"" package.json 2>/dev/null; then
|
|
18
|
+
echo "⚠️ This is a module. Use in your Express project:"
|
|
19
|
+
echo " npm install biofirewall-client"
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Install biofirewall-client
|
|
24
|
+
echo ""
|
|
25
|
+
echo "📦 Installing biofirewall-client..."
|
|
26
|
+
npm install biofirewall-client
|
|
27
|
+
|
|
28
|
+
# Create .env if doesn't exist
|
|
29
|
+
if [ ! -f .env ]; then
|
|
30
|
+
echo ""
|
|
31
|
+
echo "📝 Creating .env file..."
|
|
32
|
+
cp .env.example .env
|
|
33
|
+
echo " Please update .env with your configuration"
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Display next steps
|
|
37
|
+
echo ""
|
|
38
|
+
echo "======================================"
|
|
39
|
+
echo "✅ Setup complete!"
|
|
40
|
+
echo ""
|
|
41
|
+
echo "Next steps:"
|
|
42
|
+
echo "1. Update .env with your configuration"
|
|
43
|
+
echo "2. Add middleware to your Express app:"
|
|
44
|
+
echo ""
|
|
45
|
+
echo " const BioFirewall = require('biofirewall-client');"
|
|
46
|
+
echo " const bio = new BioFirewall('http://localhost:3333');"
|
|
47
|
+
echo " app.use(bio);"
|
|
48
|
+
echo ""
|
|
49
|
+
echo "3. Start your API and test!"
|
|
50
|
+
echo ""
|