web-agent-bridge 2.3.1 → 2.5.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.ar.md +524 -31
- package/README.md +592 -47
- package/bin/agent-runner.js +10 -1
- package/package.json +1 -1
- package/public/agent-workspace.html +347 -0
- package/public/browser.html +484 -0
- package/public/css/agent-workspace.css +1713 -0
- package/public/index.html +94 -0
- package/public/js/agent-workspace.js +1740 -0
- package/sdk/index.d.ts +253 -0
- package/sdk/index.js +360 -1
- package/sdk/package.json +1 -1
- package/server/config/secrets.js +13 -5
- package/server/control-plane/index.js +301 -0
- package/server/data-plane/index.js +354 -0
- package/server/index.js +185 -4
- package/server/llm/index.js +404 -0
- package/server/middleware/adminAuth.js +6 -1
- package/server/middleware/auth.js +11 -2
- package/server/middleware/rateLimits.js +78 -2
- package/server/migrations/003_ads_integer_cents.sql +33 -0
- package/server/models/db.js +126 -25
- package/server/observability/index.js +394 -0
- package/server/protocol/capabilities.js +223 -0
- package/server/protocol/index.js +243 -0
- package/server/protocol/schema.js +584 -0
- package/server/registry/index.js +326 -0
- package/server/routes/admin.js +16 -2
- package/server/routes/ads.js +130 -0
- package/server/routes/agent-workspace.js +378 -0
- package/server/routes/api.js +21 -2
- package/server/routes/auth.js +26 -6
- package/server/routes/runtime.js +725 -0
- package/server/routes/sovereign.js +78 -0
- package/server/routes/universal.js +177 -0
- package/server/routes/wab-api.js +20 -5
- package/server/runtime/event-bus.js +210 -0
- package/server/runtime/index.js +233 -0
- package/server/runtime/sandbox.js +266 -0
- package/server/runtime/scheduler.js +395 -0
- package/server/runtime/state-manager.js +188 -0
- package/server/security/index.js +355 -0
- package/server/services/agent-chat.js +506 -0
- package/server/services/agent-symphony.js +6 -0
- package/server/services/agent-tasks.js +1807 -0
- package/server/services/fairness-engine.js +409 -0
- package/server/services/plugins.js +27 -3
- package/server/services/price-intelligence.js +565 -0
- package/server/services/price-shield.js +1137 -0
- package/server/services/search-engine.js +357 -0
- package/server/services/security.js +513 -0
- package/server/services/universal-scraper.js +661 -0
- package/server/ws.js +61 -1
package/server/ws.js
CHANGED
|
@@ -1,20 +1,58 @@
|
|
|
1
1
|
const WebSocket = require('ws');
|
|
2
2
|
const { verifyUserToken, verifyAdminToken } = require('./config/secrets');
|
|
3
3
|
const { findSiteById } = require('./models/db');
|
|
4
|
+
const { isJWTRevoked, auditLog } = require('./services/security');
|
|
4
5
|
|
|
5
6
|
// Map of siteId → Set of WebSocket clients
|
|
6
7
|
const siteClients = new Map();
|
|
8
|
+
// Per-IP connection tracking
|
|
9
|
+
const ipConnections = new Map();
|
|
10
|
+
const MAX_CONNECTIONS_PER_IP = 10;
|
|
11
|
+
const AUTH_TIMEOUT_MS = 10_000;
|
|
12
|
+
const MAX_MESSAGE_SIZE = 4096;
|
|
13
|
+
const MSG_RATE_WINDOW = 60_000;
|
|
14
|
+
const MSG_RATE_MAX = 30;
|
|
7
15
|
|
|
8
16
|
function setupWebSocket(server) {
|
|
9
|
-
const wss = new WebSocket.Server({ server, path: '/ws/analytics' });
|
|
17
|
+
const wss = new WebSocket.Server({ server, path: '/ws/analytics', maxPayload: MAX_MESSAGE_SIZE });
|
|
10
18
|
|
|
11
19
|
wss.on('connection', (ws, req) => {
|
|
12
20
|
let authenticatedSiteId = null;
|
|
21
|
+
const clientIP = req.headers['x-forwarded-for']?.split(',')[0]?.trim() || req.socket.remoteAddress || 'unknown';
|
|
22
|
+
|
|
23
|
+
// ── Per-IP connection limit ──
|
|
24
|
+
const currentCount = ipConnections.get(clientIP) || 0;
|
|
25
|
+
if (currentCount >= MAX_CONNECTIONS_PER_IP) {
|
|
26
|
+
ws.close(1013, 'Too many connections');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
ipConnections.set(clientIP, currentCount + 1);
|
|
30
|
+
|
|
31
|
+
// ── Auth timeout — close if not authenticated within 10s ──
|
|
32
|
+
const authTimer = setTimeout(() => {
|
|
33
|
+
if (!authenticatedSiteId) {
|
|
34
|
+
ws.close(4001, 'Authentication timeout');
|
|
35
|
+
}
|
|
36
|
+
}, AUTH_TIMEOUT_MS);
|
|
37
|
+
|
|
38
|
+
// ── Message rate limiter ──
|
|
39
|
+
const msgTimestamps = [];
|
|
13
40
|
|
|
14
41
|
ws.isAlive = true;
|
|
15
42
|
ws.on('pong', () => { ws.isAlive = true; });
|
|
16
43
|
|
|
17
44
|
ws.on('message', (data) => {
|
|
45
|
+
// Rate limit messages
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
msgTimestamps.push(now);
|
|
48
|
+
while (msgTimestamps.length > 0 && msgTimestamps[0] < now - MSG_RATE_WINDOW) {
|
|
49
|
+
msgTimestamps.shift();
|
|
50
|
+
}
|
|
51
|
+
if (msgTimestamps.length > MSG_RATE_MAX) {
|
|
52
|
+
ws.close(4008, 'Message rate limit exceeded');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
18
56
|
try {
|
|
19
57
|
const msg = JSON.parse(data);
|
|
20
58
|
|
|
@@ -24,6 +62,13 @@ function setupWebSocket(server) {
|
|
|
24
62
|
return;
|
|
25
63
|
}
|
|
26
64
|
|
|
65
|
+
// Check JWT revocation before verifying
|
|
66
|
+
if (isJWTRevoked(msg.token)) {
|
|
67
|
+
ws.send(JSON.stringify({ type: 'error', message: 'Token has been revoked' }));
|
|
68
|
+
ws.close(4003, 'Token revoked');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
27
72
|
let decoded;
|
|
28
73
|
let isAdmin = false;
|
|
29
74
|
try {
|
|
@@ -34,6 +79,7 @@ function setupWebSocket(server) {
|
|
|
34
79
|
isAdmin = decoded.isAdmin === true;
|
|
35
80
|
} catch {
|
|
36
81
|
ws.send(JSON.stringify({ type: 'error', message: 'Invalid message or auth failed' }));
|
|
82
|
+
auditLog({ actorType: 'system', action: 'ws_auth_failed', ip: clientIP, outcome: 'denied', severity: 'warning' });
|
|
37
83
|
return;
|
|
38
84
|
}
|
|
39
85
|
}
|
|
@@ -46,12 +92,16 @@ function setupWebSocket(server) {
|
|
|
46
92
|
}
|
|
47
93
|
}
|
|
48
94
|
|
|
95
|
+
clearTimeout(authTimer);
|
|
49
96
|
authenticatedSiteId = msg.siteId;
|
|
50
97
|
if (!siteClients.has(msg.siteId)) {
|
|
51
98
|
siteClients.set(msg.siteId, new Set());
|
|
52
99
|
}
|
|
53
100
|
siteClients.get(msg.siteId).add(ws);
|
|
54
101
|
ws.send(JSON.stringify({ type: 'auth:success', siteId: msg.siteId }));
|
|
102
|
+
} else if (!authenticatedSiteId) {
|
|
103
|
+
// Reject all non-auth messages before authentication
|
|
104
|
+
ws.send(JSON.stringify({ type: 'error', message: 'Authentication required' }));
|
|
55
105
|
}
|
|
56
106
|
} catch (e) {
|
|
57
107
|
ws.send(JSON.stringify({ type: 'error', message: 'Invalid message or auth failed' }));
|
|
@@ -59,6 +109,12 @@ function setupWebSocket(server) {
|
|
|
59
109
|
});
|
|
60
110
|
|
|
61
111
|
ws.on('close', () => {
|
|
112
|
+
clearTimeout(authTimer);
|
|
113
|
+
// Decrement IP connection count
|
|
114
|
+
const count = ipConnections.get(clientIP) || 1;
|
|
115
|
+
if (count <= 1) ipConnections.delete(clientIP);
|
|
116
|
+
else ipConnections.set(clientIP, count - 1);
|
|
117
|
+
|
|
62
118
|
if (authenticatedSiteId && siteClients.has(authenticatedSiteId)) {
|
|
63
119
|
siteClients.get(authenticatedSiteId).delete(ws);
|
|
64
120
|
if (siteClients.get(authenticatedSiteId).size === 0) {
|
|
@@ -66,6 +122,10 @@ function setupWebSocket(server) {
|
|
|
66
122
|
}
|
|
67
123
|
}
|
|
68
124
|
});
|
|
125
|
+
|
|
126
|
+
ws.on('error', () => {
|
|
127
|
+
clearTimeout(authTimer);
|
|
128
|
+
});
|
|
69
129
|
});
|
|
70
130
|
|
|
71
131
|
const interval = setInterval(() => {
|