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/SKILL.md
ADDED
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: biofirewall-client
|
|
3
|
+
version: 3.0.0
|
|
4
|
+
description: Express middleware to protect your APIs with agent authentication.
|
|
5
|
+
homepage: https://github.com/openclaw/biofirewall
|
|
6
|
+
metadata: {"biofirewall-client":{"emoji":"🔒","category":"security"}}
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# BioFirewall Client 🔒 SKILL.md
|
|
10
|
+
|
|
11
|
+
**For Developers:** How to protect your APIs with agent-only authentication.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## What Is biofirewall-client?
|
|
16
|
+
|
|
17
|
+
An Express middleware that:
|
|
18
|
+
1. **Blocks browsers** (User-Agent detection)
|
|
19
|
+
2. **Verifies tokens** with central BioFirewall API
|
|
20
|
+
3. **Caches verification** locally (30s, reduces API load)
|
|
21
|
+
4. **Attaches agent info** to `req.agent`
|
|
22
|
+
5. **Returns helpful errors**
|
|
23
|
+
|
|
24
|
+
**Think:** A bouncer that only lets verified robots in.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install biofirewall-client
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Quick Start (2 Minutes)
|
|
37
|
+
|
|
38
|
+
### 1. Basic Setup
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
const express = require('express');
|
|
42
|
+
const BioFirewall = require('biofirewall-client');
|
|
43
|
+
|
|
44
|
+
const app = express();
|
|
45
|
+
|
|
46
|
+
// Create middleware
|
|
47
|
+
const bioFirewall = new BioFirewall('http://localhost:3333');
|
|
48
|
+
|
|
49
|
+
// Protect all routes
|
|
50
|
+
app.use(bioFirewall);
|
|
51
|
+
|
|
52
|
+
// Now only agents can access
|
|
53
|
+
app.get('/api/secret', (req, res) => {
|
|
54
|
+
res.json({
|
|
55
|
+
message: 'Hello, ' + req.agent.name,
|
|
56
|
+
agentId: req.agent.id
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
app.listen(8080);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. Test It
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# As a browser (blocked)
|
|
67
|
+
curl http://localhost:8080/api/secret \
|
|
68
|
+
-H "User-Agent: Mozilla/5.0 Chrome/..."
|
|
69
|
+
# Response: 406 Not Acceptable
|
|
70
|
+
|
|
71
|
+
# As an agent (allowed)
|
|
72
|
+
curl http://localhost:8080/api/secret \
|
|
73
|
+
-H "X-Bio-Agent-Id: agent_abc123" \
|
|
74
|
+
-H "X-Bio-Token: eyJhbGc..." \
|
|
75
|
+
-H "User-Agent: MyAgent/1.0" \
|
|
76
|
+
-H "Accept: application/json"
|
|
77
|
+
# Response: 200 OK
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Configuration
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
const bioFirewall = new BioFirewall({
|
|
86
|
+
// Required
|
|
87
|
+
apiUrl: 'http://localhost:3333',
|
|
88
|
+
|
|
89
|
+
// Optional
|
|
90
|
+
blockBrowsers: true, // Block User-Agents that look like browsers
|
|
91
|
+
enforceAuthentication: true, // Require X-Bio-Token header
|
|
92
|
+
cacheTokens: true, // Cache verification results locally
|
|
93
|
+
cacheTTL: 30000 // Cache time-to-live (milliseconds)
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
app.use(bioFirewall);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Configuration Options
|
|
100
|
+
|
|
101
|
+
| Option | Default | Purpose |
|
|
102
|
+
|--------|---------|---------|
|
|
103
|
+
| `apiUrl` | required | Central BioFirewall API URL |
|
|
104
|
+
| `blockBrowsers` | true | Block requests that look like browsers |
|
|
105
|
+
| `enforceAuthentication` | true | Require valid authentication |
|
|
106
|
+
| `cacheTokens` | true | Cache token verification locally |
|
|
107
|
+
| `cacheTTL` | 30000 | Cache time-to-live in milliseconds |
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## How It Works
|
|
112
|
+
|
|
113
|
+
### Three Layers of Security
|
|
114
|
+
|
|
115
|
+
#### Layer 1️⃣: Passive Filtering (Local, <1ms)
|
|
116
|
+
|
|
117
|
+
Blocks obvious browsers without API call:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
User-Agent: Mozilla/5.0 Chrome/... → 406 (instant)
|
|
121
|
+
Accept: text/html → 406 (instant)
|
|
122
|
+
Accept-Language: en-US → 406 (instant)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Why:** Fast rejection of obvious non-agents.
|
|
126
|
+
|
|
127
|
+
#### Layer 2️⃣: Token Verification (Central API, ~20ms)
|
|
128
|
+
|
|
129
|
+
For requests that pass Layer 1:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
POST /verify on central API
|
|
133
|
+
{
|
|
134
|
+
"agentId": "agent_xyz",
|
|
135
|
+
"token": "eyJhbGc..."
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
Response:
|
|
139
|
+
{
|
|
140
|
+
"valid": true,
|
|
141
|
+
"agent": { "id": "agent_xyz", "name": "MyAgent" }
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Why:** Cryptographic verification with central registry.
|
|
146
|
+
|
|
147
|
+
#### Layer 3️⃣: Caching (Local, <1ms)
|
|
148
|
+
|
|
149
|
+
Result cached for 30 seconds:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
Request 1 (07:00:00) → API call ~20ms
|
|
153
|
+
Request 2 (07:00:10) → Cache hit ~0ms
|
|
154
|
+
Request 3 (07:00:20) → Cache hit ~0ms
|
|
155
|
+
Request 4 (07:00:35) → API call ~20ms (cache expired)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Why:** Reduce load on central API during request bursts.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Usage Examples
|
|
163
|
+
|
|
164
|
+
### Protect Specific Routes
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
const express = require('express');
|
|
168
|
+
const BioFirewall = require('biofirewall-client');
|
|
169
|
+
|
|
170
|
+
const app = express();
|
|
171
|
+
const bioFirewall = new BioFirewall('http://localhost:3333');
|
|
172
|
+
|
|
173
|
+
// Public routes (no auth)
|
|
174
|
+
app.get('/', (req, res) => {
|
|
175
|
+
res.json({ service: 'My API', status: 'online' });
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Protected routes (auth required)
|
|
179
|
+
app.get('/api/secret', bioFirewall, (req, res) => {
|
|
180
|
+
res.json({
|
|
181
|
+
message: 'Secret data',
|
|
182
|
+
agent: req.agent
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
app.post('/api/data', bioFirewall, (req, res) => {
|
|
187
|
+
res.json({
|
|
188
|
+
success: true,
|
|
189
|
+
agent: req.agent.id
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
app.listen(8080);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Protect All Routes Except /health
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
const app = express();
|
|
200
|
+
const bioFirewall = new BioFirewall('http://localhost:3333');
|
|
201
|
+
|
|
202
|
+
// Health check (no auth)
|
|
203
|
+
app.get('/health', (req, res) => {
|
|
204
|
+
res.json({ status: 'ok' });
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Protect everything else
|
|
208
|
+
app.use(bioFirewall);
|
|
209
|
+
|
|
210
|
+
app.get('/api/data', (req, res) => {
|
|
211
|
+
res.json({ agent: req.agent.name });
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Access Agent Information
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
app.get('/api/profile', bioFirewall, (req, res) => {
|
|
219
|
+
// req.agent is populated by middleware
|
|
220
|
+
res.json({
|
|
221
|
+
you: req.agent.name,
|
|
222
|
+
yourId: req.agent.id,
|
|
223
|
+
version: req.agent.version,
|
|
224
|
+
permissions: req.agent.permissions
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Custom Error Handling
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
const bioFirewall = new BioFirewall('http://localhost:3333');
|
|
233
|
+
|
|
234
|
+
app.use((req, res, next) => {
|
|
235
|
+
bioFirewall(req, res, (err) => {
|
|
236
|
+
if (err) {
|
|
237
|
+
// Custom error handling
|
|
238
|
+
console.error('Auth error:', err);
|
|
239
|
+
return res.status(401).json({
|
|
240
|
+
success: false,
|
|
241
|
+
message: 'Custom error message'
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
next();
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### With Route Guards
|
|
250
|
+
|
|
251
|
+
```javascript
|
|
252
|
+
const bioFirewall = new BioFirewall('http://localhost:3333');
|
|
253
|
+
|
|
254
|
+
// Middleware
|
|
255
|
+
const requireAgent = bioFirewall;
|
|
256
|
+
|
|
257
|
+
const requireAdmin = (req, res, next) => {
|
|
258
|
+
if (req.agent.permissions.includes('admin')) {
|
|
259
|
+
next();
|
|
260
|
+
} else {
|
|
261
|
+
res.status(403).json({ error: 'Requires admin permission' });
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// Routes
|
|
266
|
+
app.get('/api/user-data', requireAgent, (req, res) => {
|
|
267
|
+
res.json({ data: 'user data' });
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
app.post('/api/system-control', requireAgent, requireAdmin, (req, res) => {
|
|
271
|
+
res.json({ success: true });
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Request Headers
|
|
278
|
+
|
|
279
|
+
### Required Headers (When Accessing Protected Route)
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
X-Bio-Agent-Id: agent_a1b2c3d4e5f6g7h8 (your agent ID)
|
|
283
|
+
X-Bio-Token: eyJhbGciOiJSUzI1NiIsInR5cCI... (JWT you signed)
|
|
284
|
+
User-Agent: MyAgent/1.0 (your agent name/version)
|
|
285
|
+
Accept: application/json (ask for JSON, not HTML)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Headers NOT to Send
|
|
289
|
+
|
|
290
|
+
❌ `Accept: text/html` (looks human)
|
|
291
|
+
❌ `Accept-Language: en-US` (human trait)
|
|
292
|
+
❌ `Accept-Encoding: gzip, deflate` (browser thing)
|
|
293
|
+
❌ Browser User-Agent like `Mozilla/5.0 Chrome/...`
|
|
294
|
+
|
|
295
|
+
### Example Correct Request
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
curl http://localhost:8080/api/secret \
|
|
299
|
+
-H "X-Bio-Agent-Id: agent_abc123" \
|
|
300
|
+
-H "X-Bio-Token: eyJhbGc..." \
|
|
301
|
+
-H "User-Agent: MyAgent/1.0" \
|
|
302
|
+
-H "Accept: application/json"
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Response Headers
|
|
308
|
+
|
|
309
|
+
Middleware adds these headers when authentication succeeds:
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
X-Bio-Verified: true
|
|
313
|
+
X-Bio-Agent-Name: MyAgent
|
|
314
|
+
X-Bio-Version: 3.0
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Use these in your code:
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
app.get('/api/secret', bioFirewall, (req, res) => {
|
|
321
|
+
const headers = res.getHeaders();
|
|
322
|
+
console.log(headers['x-bio-verified']); // 'true'
|
|
323
|
+
console.log(headers['x-bio-agent-name']); // 'MyAgent'
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Error Responses
|
|
330
|
+
|
|
331
|
+
### 406 Not Acceptable (Looks Like a Browser)
|
|
332
|
+
|
|
333
|
+
```json
|
|
334
|
+
{
|
|
335
|
+
"success": false,
|
|
336
|
+
"error": "BIOLOGICAL_ENTITY_DETECTED",
|
|
337
|
+
"message": "This resource is reserved for automated agents",
|
|
338
|
+
"tip": "Use an API client. Ensure User-Agent does not look like a browser"
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**Causes:**
|
|
343
|
+
- User-Agent contains "Mozilla", "Chrome", "Safari", etc.
|
|
344
|
+
- Accept header contains `text/html`
|
|
345
|
+
- Accept-Language header present
|
|
346
|
+
|
|
347
|
+
**Fix:**
|
|
348
|
+
```bash
|
|
349
|
+
# Don't do this
|
|
350
|
+
curl -H "User-Agent: Mozilla/5.0 Chrome/..." http://localhost:8080/api/secret
|
|
351
|
+
|
|
352
|
+
# Do this instead
|
|
353
|
+
curl \
|
|
354
|
+
-H "User-Agent: MyAgent/1.0" \
|
|
355
|
+
-H "Accept: application/json" \
|
|
356
|
+
-H "X-Bio-Agent-Id: agent_abc123" \
|
|
357
|
+
-H "X-Bio-Token: eyJhbGc..." \
|
|
358
|
+
http://localhost:8080/api/secret
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### 428 Precondition Required (Missing Headers)
|
|
362
|
+
|
|
363
|
+
```json
|
|
364
|
+
{
|
|
365
|
+
"success": false,
|
|
366
|
+
"error": "AUTHENTICATION_REQUIRED",
|
|
367
|
+
"message": "Valid agent token required to access this resource",
|
|
368
|
+
"protocol": {
|
|
369
|
+
"version": "3.0",
|
|
370
|
+
"method": "JWT + RS256",
|
|
371
|
+
"headers": {
|
|
372
|
+
"X-Bio-Agent-Id": "Your agent ID from registration",
|
|
373
|
+
"X-Bio-Token": "JWT token signed with your private key"
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**Cause:** Missing `X-Bio-Agent-Id` or `X-Bio-Token` header
|
|
380
|
+
|
|
381
|
+
**Fix:** Include both headers with valid values
|
|
382
|
+
|
|
383
|
+
### 401 Unauthorized (Invalid Token)
|
|
384
|
+
|
|
385
|
+
```json
|
|
386
|
+
{
|
|
387
|
+
"success": false,
|
|
388
|
+
"error": "INVALID_TOKEN",
|
|
389
|
+
"message": "Token signature is invalid"
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Causes:**
|
|
394
|
+
- Token signed with wrong private key
|
|
395
|
+
- Token expired (>1 hour old)
|
|
396
|
+
- Token corrupted/tampered
|
|
397
|
+
|
|
398
|
+
**Fix:**
|
|
399
|
+
```javascript
|
|
400
|
+
// Regenerate token with correct private key
|
|
401
|
+
const newToken = crypto.createToken(privateKey, agentId, metadata, 3600);
|
|
402
|
+
|
|
403
|
+
// Then retry request with new token
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### 403 Forbidden (Agent Revoked)
|
|
407
|
+
|
|
408
|
+
```json
|
|
409
|
+
{
|
|
410
|
+
"success": false,
|
|
411
|
+
"error": "AGENT_NOT_ACTIVE",
|
|
412
|
+
"message": "Agent status is revoked"
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**Cause:** Agent was revoked by administrator
|
|
417
|
+
|
|
418
|
+
**Fix:** Contact administrator to reactivate
|
|
419
|
+
|
|
420
|
+
### 404 Agent Not Found
|
|
421
|
+
|
|
422
|
+
```json
|
|
423
|
+
{
|
|
424
|
+
"success": false,
|
|
425
|
+
"error": "AGENT_NOT_FOUND",
|
|
426
|
+
"message": "Agent not registered"
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
**Cause:** Agent ID doesn't exist in central registry
|
|
431
|
+
|
|
432
|
+
**Fix:** Register agent first:
|
|
433
|
+
```bash
|
|
434
|
+
curl -X POST http://localhost:3333/register \
|
|
435
|
+
-d '{"publicKey": "...", "metadata": {...}}'
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## Token Caching
|
|
441
|
+
|
|
442
|
+
By default, token verification results are cached for 30 seconds locally:
|
|
443
|
+
|
|
444
|
+
```javascript
|
|
445
|
+
// Prevents hammering central API
|
|
446
|
+
// Typical cache hit rate: 95%+ for normal usage
|
|
447
|
+
|
|
448
|
+
// Clear all cache (if needed)
|
|
449
|
+
bioFirewall.clearCache?.();
|
|
450
|
+
|
|
451
|
+
// Or disable caching
|
|
452
|
+
const bioFirewall = new BioFirewall({
|
|
453
|
+
apiUrl: 'http://localhost:3333',
|
|
454
|
+
cacheTokens: false // No local caching
|
|
455
|
+
});
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
## Performance
|
|
461
|
+
|
|
462
|
+
### Typical Response Times
|
|
463
|
+
|
|
464
|
+
```
|
|
465
|
+
Browser request (Layer 1): ~0ms (instant rejection)
|
|
466
|
+
Agent request (Layer 2 cache): ~1ms (cached)
|
|
467
|
+
Agent request (Layer 2 API): ~20ms (central API call)
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Optimization Tips
|
|
471
|
+
|
|
472
|
+
1. **Reuse middleware instance:**
|
|
473
|
+
```javascript
|
|
474
|
+
const bioFirewall = new BioFirewall('http://localhost:3333');
|
|
475
|
+
app.use('/api', bioFirewall); // Single instance
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
2. **Use token caching (default):**
|
|
479
|
+
```javascript
|
|
480
|
+
const bioFirewall = new BioFirewall({
|
|
481
|
+
apiUrl: 'http://localhost:3333',
|
|
482
|
+
cacheTokens: true, // Default: ON
|
|
483
|
+
cacheTTL: 30000 // 30 seconds
|
|
484
|
+
});
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
3. **Keep central API close (low latency):**
|
|
488
|
+
- Same data center if possible
|
|
489
|
+
- CDN for geographically distributed apps
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## Testing
|
|
494
|
+
|
|
495
|
+
### Unit Tests
|
|
496
|
+
|
|
497
|
+
```javascript
|
|
498
|
+
const request = require('supertest');
|
|
499
|
+
const app = require('./app');
|
|
500
|
+
|
|
501
|
+
describe('Protected endpoint', () => {
|
|
502
|
+
it('should reject browsers', async () => {
|
|
503
|
+
const res = await request(app)
|
|
504
|
+
.get('/api/secret')
|
|
505
|
+
.set('User-Agent', 'Mozilla/5.0 Chrome/...');
|
|
506
|
+
|
|
507
|
+
expect(res.status).toBe(406);
|
|
508
|
+
expect(res.body.error).toBe('BIOLOGICAL_ENTITY_DETECTED');
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it('should reject missing auth', async () => {
|
|
512
|
+
const res = await request(app)
|
|
513
|
+
.get('/api/secret')
|
|
514
|
+
.set('User-Agent', 'MyBot/1.0');
|
|
515
|
+
|
|
516
|
+
expect(res.status).toBe(428);
|
|
517
|
+
expect(res.body.error).toBe('AUTHENTICATION_REQUIRED');
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('should allow valid agents', async () => {
|
|
521
|
+
const res = await request(app)
|
|
522
|
+
.get('/api/secret')
|
|
523
|
+
.set('User-Agent', 'MyBot/1.0')
|
|
524
|
+
.set('X-Bio-Agent-Id', 'agent_test123')
|
|
525
|
+
.set('X-Bio-Token', 'valid_jwt_token');
|
|
526
|
+
|
|
527
|
+
expect(res.status).toBe(200);
|
|
528
|
+
expect(res.body.agent.id).toBe('agent_test123');
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('should include response headers', async () => {
|
|
532
|
+
const res = await request(app)
|
|
533
|
+
.get('/api/secret')
|
|
534
|
+
.set('User-Agent', 'MyBot/1.0')
|
|
535
|
+
.set('X-Bio-Agent-Id', 'agent_test123')
|
|
536
|
+
.set('X-Bio-Token', 'valid_jwt_token');
|
|
537
|
+
|
|
538
|
+
expect(res.headers['x-bio-verified']).toBe('true');
|
|
539
|
+
expect(res.headers['x-bio-agent-name']).toBe('MyAgent');
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Integration Test
|
|
545
|
+
|
|
546
|
+
```bash
|
|
547
|
+
#!/bin/bash
|
|
548
|
+
|
|
549
|
+
# Test 1: Browser blocked
|
|
550
|
+
echo "Test 1: Block browser"
|
|
551
|
+
curl -v \
|
|
552
|
+
-H "User-Agent: Mozilla/5.0 Chrome/..." \
|
|
553
|
+
http://localhost:8080/api/secret
|
|
554
|
+
# Expected: 406
|
|
555
|
+
|
|
556
|
+
# Test 2: Missing auth
|
|
557
|
+
echo "Test 2: Missing auth"
|
|
558
|
+
curl -v \
|
|
559
|
+
-H "User-Agent: MyBot/1.0" \
|
|
560
|
+
http://localhost:8080/api/secret
|
|
561
|
+
# Expected: 428
|
|
562
|
+
|
|
563
|
+
# Test 3: Valid agent
|
|
564
|
+
echo "Test 3: Valid agent"
|
|
565
|
+
curl -v \
|
|
566
|
+
-H "User-Agent: MyBot/1.0" \
|
|
567
|
+
-H "X-Bio-Agent-Id: agent_test123" \
|
|
568
|
+
-H "X-Bio-Token: eyJhbGc..." \
|
|
569
|
+
http://localhost:8080/api/secret
|
|
570
|
+
# Expected: 200
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
---
|
|
574
|
+
|
|
575
|
+
## Troubleshooting
|
|
576
|
+
|
|
577
|
+
### "Cannot connect to central API"
|
|
578
|
+
|
|
579
|
+
```javascript
|
|
580
|
+
// Check API URL
|
|
581
|
+
const bioFirewall = new BioFirewall('http://localhost:3333');
|
|
582
|
+
|
|
583
|
+
// Verify API is running
|
|
584
|
+
curl http://localhost:3333/health
|
|
585
|
+
|
|
586
|
+
// Check firewall rules
|
|
587
|
+
# Allow traffic on 3333
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### "Token always invalid"
|
|
591
|
+
|
|
592
|
+
```javascript
|
|
593
|
+
// Ensure token signed with correct private key
|
|
594
|
+
const token = jwt.sign(payload, privateKey, { algorithm: 'RS256' });
|
|
595
|
+
|
|
596
|
+
// Verify agent is registered
|
|
597
|
+
curl http://localhost:3333/agents/agent_abc123
|
|
598
|
+
|
|
599
|
+
// Check token not expired (max 1 hour old)
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
### "Agents keep getting 406"
|
|
603
|
+
|
|
604
|
+
```bash
|
|
605
|
+
# Check headers
|
|
606
|
+
curl -v \
|
|
607
|
+
-H "User-Agent: MyBot/1.0" \
|
|
608
|
+
-H "Accept: application/json" \
|
|
609
|
+
-H "X-Bio-Agent-Id: agent_xyz" \
|
|
610
|
+
-H "X-Bio-Token: ..." \
|
|
611
|
+
http://localhost:8080/api/secret
|
|
612
|
+
|
|
613
|
+
# Don't include:
|
|
614
|
+
# - "Mozilla" in User-Agent
|
|
615
|
+
# - "text/html" in Accept header
|
|
616
|
+
# - "Accept-Language" header
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
---
|
|
620
|
+
|
|
621
|
+
## Complete Example App
|
|
622
|
+
|
|
623
|
+
```javascript
|
|
624
|
+
const express = require('express');
|
|
625
|
+
const BioFirewall = require('biofirewall-client');
|
|
626
|
+
|
|
627
|
+
const app = express();
|
|
628
|
+
|
|
629
|
+
// Setup
|
|
630
|
+
const bioFirewall = new BioFirewall('http://localhost:3333');
|
|
631
|
+
|
|
632
|
+
// Middleware
|
|
633
|
+
app.use(express.json());
|
|
634
|
+
|
|
635
|
+
// Public route (no auth)
|
|
636
|
+
app.get('/', (req, res) => {
|
|
637
|
+
res.json({
|
|
638
|
+
service: 'Protected API',
|
|
639
|
+
status: 'online',
|
|
640
|
+
endpoints: {
|
|
641
|
+
public: ['GET /'],
|
|
642
|
+
protected: ['GET /api/secret', 'POST /api/data']
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
// Protected routes
|
|
648
|
+
app.get('/api/secret', bioFirewall, (req, res) => {
|
|
649
|
+
res.json({
|
|
650
|
+
message: 'Secret data',
|
|
651
|
+
agent: {
|
|
652
|
+
id: req.agent.id,
|
|
653
|
+
name: req.agent.name,
|
|
654
|
+
version: req.agent.version
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
app.post('/api/data', bioFirewall, (req, res) => {
|
|
660
|
+
res.json({
|
|
661
|
+
success: true,
|
|
662
|
+
message: 'Data received',
|
|
663
|
+
agent: req.agent.id,
|
|
664
|
+
data: req.body
|
|
665
|
+
});
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
// Error handler
|
|
669
|
+
app.use((err, req, res, next) => {
|
|
670
|
+
console.error(err);
|
|
671
|
+
res.status(500).json({
|
|
672
|
+
error: 'Internal server error'
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
// Start
|
|
677
|
+
const PORT = process.env.PORT || 8080;
|
|
678
|
+
app.listen(PORT, () => {
|
|
679
|
+
console.log(`🔒 Protected API running on port ${PORT}`);
|
|
680
|
+
console.log(` Central API: http://localhost:3333`);
|
|
681
|
+
});
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
---
|
|
685
|
+
|
|
686
|
+
## Deployment
|
|
687
|
+
|
|
688
|
+
### Docker
|
|
689
|
+
|
|
690
|
+
```dockerfile
|
|
691
|
+
FROM node:18-alpine
|
|
692
|
+
|
|
693
|
+
WORKDIR /app
|
|
694
|
+
|
|
695
|
+
COPY package*.json ./
|
|
696
|
+
RUN npm install
|
|
697
|
+
|
|
698
|
+
COPY . .
|
|
699
|
+
|
|
700
|
+
ENV BIOFIREWALL_API=http://biofirewall-api:3333
|
|
701
|
+
ENV PORT=8080
|
|
702
|
+
|
|
703
|
+
EXPOSE 8080
|
|
704
|
+
|
|
705
|
+
CMD ["npm", "start"]
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### Environment Variables
|
|
709
|
+
|
|
710
|
+
```bash
|
|
711
|
+
BIOFIREWALL_API=http://localhost:3333
|
|
712
|
+
NODE_ENV=production
|
|
713
|
+
PORT=8080
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
## Security Best Practices
|
|
719
|
+
|
|
720
|
+
✅ **DO:**
|
|
721
|
+
- Use HTTPS in production
|
|
722
|
+
- Keep central API URL secret
|
|
723
|
+
- Monitor for unusual patterns
|
|
724
|
+
- Log authentication attempts
|
|
725
|
+
- Update middleware regularly
|
|
726
|
+
- Use caching (reduces API calls)
|
|
727
|
+
|
|
728
|
+
❌ **DON'T:**
|
|
729
|
+
- Expose central API URL in client code
|
|
730
|
+
- Log tokens or agent secrets
|
|
731
|
+
- Disable browser detection
|
|
732
|
+
- Disable token verification
|
|
733
|
+
- Use old versions
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
|
|
737
|
+
## FAQ
|
|
738
|
+
|
|
739
|
+
**Q: Can I use biofirewall-client without central API?**
|
|
740
|
+
A: No. It requires a running central API for token verification.
|
|
741
|
+
|
|
742
|
+
**Q: How do I set up central API?**
|
|
743
|
+
A: See `/biofirewall-api/README.md` or `npm install biofirewall-api`.
|
|
744
|
+
|
|
745
|
+
**Q: Can I customize error messages?**
|
|
746
|
+
A: Yes, implement custom middleware before biofirewall.
|
|
747
|
+
|
|
748
|
+
**Q: How do I handle 404 from central API?**
|
|
749
|
+
A: Middleware will return 500. Ensure central API is reachable.
|
|
750
|
+
|
|
751
|
+
**Q: Can multiple services use same central API?**
|
|
752
|
+
A: Yes! That's the point. Central API is shared.
|
|
753
|
+
|
|
754
|
+
---
|
|
755
|
+
|
|
756
|
+
## Support
|
|
757
|
+
|
|
758
|
+
- 📖 [biofirewall-api/SKILL.md](../biofirewall-api/SKILL.md) — Agent registration guide
|
|
759
|
+
- 💓 [biofirewall-api/HEARTBEAT.md](../biofirewall-api/HEARTBEAT.md) — Agent activity routine
|
|
760
|
+
- 🏗️ [ARCHITECTURE.md](../ARCHITECTURE.md) — System design
|
|
761
|
+
- 🔗 GitHub: https://github.com/openclaw/biofirewall
|
|
762
|
+
|
|
763
|
+
---
|
|
764
|
+
|
|
765
|
+
*BioFirewall Client v3.0 | Protect your APIs. Verify silicon.* 🔒
|