web-agent-bridge 3.0.0 → 3.3.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/LICENSE +72 -21
- package/README.ar.md +1286 -1073
- package/README.md +1764 -1535
- package/bin/agent-runner.js +474 -474
- package/bin/cli.js +237 -138
- package/bin/wab.js +80 -80
- package/examples/bidi-agent.js +119 -119
- package/examples/cross-site-agent.js +91 -91
- package/examples/mcp-agent.js +94 -94
- package/examples/next-app-router/README.md +44 -44
- package/examples/puppeteer-agent.js +108 -108
- package/examples/saas-dashboard/README.md +55 -55
- package/examples/shopify-hydrogen/README.md +74 -74
- package/examples/vision-agent.js +171 -171
- package/examples/wordpress-elementor/README.md +77 -77
- package/package.json +17 -3
- package/public/.well-known/agent-tools.json +180 -180
- package/public/.well-known/ai-assets.json +59 -59
- package/public/.well-known/ai-plugin.json +28 -0
- package/public/.well-known/security.txt +8 -0
- package/public/agent-workspace.html +349 -347
- package/public/ai.html +198 -196
- package/public/api.html +413 -0
- package/public/browser.html +486 -484
- package/public/commander-dashboard.html +243 -243
- package/public/cookies.html +210 -208
- package/public/css/agent-workspace.css +1713 -1713
- package/public/css/premium.css +317 -317
- package/public/css/styles.css +1235 -1235
- package/public/dashboard.html +706 -704
- package/public/demo.html +1770 -1
- package/public/dns.html +507 -0
- package/public/docs.html +587 -585
- package/public/feed.xml +89 -89
- package/public/growth.html +463 -0
- package/public/index.html +341 -9
- package/public/integrations.html +556 -0
- package/public/js/agent-workspace.js +1740 -1740
- package/public/js/auth-nav.js +31 -31
- package/public/js/auth-redirect.js +12 -12
- package/public/js/cookie-consent.js +56 -56
- package/public/js/wab-demo-page.js +721 -721
- package/public/js/ws-client.js +74 -74
- package/public/llms-full.txt +360 -309
- package/public/llms.txt +125 -86
- package/public/login.html +85 -83
- package/public/mesh-dashboard.html +328 -328
- package/public/openapi.json +580 -580
- package/public/phone-shield.html +281 -0
- package/public/premium-dashboard.html +2489 -2487
- package/public/premium.html +793 -791
- package/public/privacy.html +297 -295
- package/public/register.html +105 -103
- package/public/robots.txt +87 -87
- package/public/script/wab-consent.d.ts +36 -36
- package/public/script/wab-consent.js +104 -104
- package/public/script/wab-schema.js +131 -131
- package/public/script/wab.d.ts +108 -108
- package/public/script/wab.min.js +580 -580
- package/public/security.txt +8 -0
- package/public/terms.html +256 -254
- package/script/ai-agent-bridge.js +1754 -1754
- package/sdk/README.md +99 -99
- package/sdk/agent-mesh.js +449 -449
- package/sdk/commander.js +262 -262
- package/sdk/index.d.ts +464 -464
- package/sdk/index.js +18 -1
- package/sdk/multi-agent.js +318 -318
- package/sdk/package.json +12 -1
- package/sdk/safety-shield.js +219 -0
- package/sdk/schema-discovery.js +83 -83
- package/server/adapters/index.js +520 -520
- package/server/config/plans.js +367 -367
- package/server/config/secrets.js +102 -102
- package/server/control-plane/index.js +301 -301
- package/server/data-plane/index.js +354 -354
- package/server/index.js +175 -19
- package/server/llm/index.js +404 -404
- package/server/middleware/adminAuth.js +35 -35
- package/server/middleware/auth.js +50 -50
- package/server/middleware/featureGate.js +88 -88
- package/server/middleware/rateLimits.js +100 -100
- package/server/middleware/sensitiveAction.js +157 -0
- package/server/migrations/001_add_analytics_indexes.sql +7 -7
- package/server/migrations/002_premium_features.sql +418 -418
- package/server/migrations/003_ads_integer_cents.sql +33 -33
- package/server/migrations/004_agent_os.sql +158 -158
- package/server/migrations/005_marketplace_metering.sql +126 -126
- package/server/models/adapters/index.js +33 -33
- package/server/models/adapters/mysql.js +183 -183
- package/server/models/adapters/postgresql.js +172 -172
- package/server/models/adapters/sqlite.js +7 -7
- package/server/models/db.js +681 -681
- package/server/observability/failure-analysis.js +337 -337
- package/server/observability/index.js +394 -394
- package/server/protocol/capabilities.js +223 -223
- package/server/protocol/index.js +243 -243
- package/server/protocol/schema.js +584 -584
- package/server/registry/certification.js +271 -271
- package/server/registry/index.js +326 -326
- package/server/routes/admin-premium.js +671 -671
- package/server/routes/admin.js +261 -261
- package/server/routes/ads.js +130 -130
- package/server/routes/agent-workspace.js +540 -378
- package/server/routes/api.js +150 -150
- package/server/routes/auth.js +71 -71
- package/server/routes/billing.js +45 -45
- package/server/routes/commander.js +316 -316
- package/server/routes/demo-showcase.js +332 -0
- package/server/routes/demo-store.js +154 -0
- package/server/routes/discovery.js +417 -406
- package/server/routes/gateway.js +173 -0
- package/server/routes/license.js +251 -240
- package/server/routes/mesh.js +469 -469
- package/server/routes/noscript.js +543 -543
- package/server/routes/premium-v2.js +686 -686
- package/server/routes/premium.js +724 -724
- package/server/routes/runtime.js +2148 -2147
- package/server/routes/sovereign.js +465 -385
- package/server/routes/universal.js +200 -177
- package/server/routes/wab-api.js +850 -491
- package/server/runtime/container-worker.js +111 -111
- package/server/runtime/container.js +448 -448
- package/server/runtime/distributed-worker.js +362 -362
- package/server/runtime/event-bus.js +210 -210
- package/server/runtime/index.js +253 -253
- package/server/runtime/queue.js +599 -599
- package/server/runtime/replay.js +666 -666
- package/server/runtime/sandbox.js +266 -266
- package/server/runtime/scheduler.js +534 -534
- package/server/runtime/session-engine.js +293 -293
- package/server/runtime/state-manager.js +188 -188
- package/server/security/cross-site-redactor.js +196 -0
- package/server/security/dry-run.js +180 -0
- package/server/security/human-gate-rate-limit.js +147 -0
- package/server/security/human-gate-transports.js +178 -0
- package/server/security/human-gate.js +281 -0
- package/server/security/index.js +368 -368
- package/server/security/intent-engine.js +245 -0
- package/server/security/reward-guard.js +171 -0
- package/server/security/rollback-store.js +239 -0
- package/server/security/token-scope.js +404 -0
- package/server/security/url-policy.js +139 -0
- package/server/services/agent-chat.js +506 -506
- package/server/services/agent-learning.js +601 -575
- package/server/services/agent-memory.js +625 -625
- package/server/services/agent-mesh.js +555 -539
- package/server/services/agent-symphony.js +717 -717
- package/server/services/agent-tasks.js +1807 -1807
- package/server/services/api-key-engine.js +292 -0
- package/server/services/cluster.js +894 -894
- package/server/services/commander.js +738 -738
- package/server/services/edge-compute.js +440 -440
- package/server/services/email.js +204 -204
- package/server/services/hosted-runtime.js +205 -205
- package/server/services/lfd.js +635 -616
- package/server/services/local-ai.js +389 -389
- package/server/services/marketplace.js +270 -270
- package/server/services/metering.js +182 -182
- package/server/services/modules/affiliate-intelligence.js +93 -0
- package/server/services/modules/agent-firewall.js +90 -0
- package/server/services/modules/bounty.js +89 -0
- package/server/services/modules/collective-bargaining.js +92 -0
- package/server/services/modules/dark-pattern.js +66 -0
- package/server/services/modules/gov-intelligence.js +45 -0
- package/server/services/modules/neural.js +55 -0
- package/server/services/modules/notary.js +49 -0
- package/server/services/modules/price-time-machine.js +86 -0
- package/server/services/modules/protocol.js +104 -0
- package/server/services/negotiation.js +439 -439
- package/server/services/plugins.js +771 -771
- package/server/services/premium.js +1 -1
- package/server/services/price-intelligence.js +566 -565
- package/server/services/price-shield.js +1137 -1137
- package/server/services/reputation.js +465 -465
- package/server/services/search-engine.js +357 -357
- package/server/services/security.js +513 -513
- package/server/services/self-healing.js +843 -843
- package/server/services/sovereign-shield.js +542 -0
- package/server/services/stripe.js +192 -192
- package/server/services/swarm.js +788 -788
- package/server/services/universal-scraper.js +662 -661
- package/server/services/verification.js +481 -481
- package/server/services/vision.js +1163 -1163
- package/server/utils/cache.js +125 -125
- package/server/utils/migrate.js +81 -81
- package/server/utils/safe-fetch.js +228 -0
- package/server/utils/secureFields.js +50 -50
- package/server/ws.js +161 -161
- package/templates/artisan-marketplace.yaml +104 -104
- package/templates/book-price-scout.yaml +98 -98
- package/templates/electronics-price-tracker.yaml +108 -108
- package/templates/flight-deal-hunter.yaml +113 -113
- package/templates/freelancer-direct.yaml +116 -116
- package/templates/grocery-price-compare.yaml +93 -93
- package/templates/hotel-direct-booking.yaml +113 -113
- package/templates/local-services.yaml +98 -98
- package/templates/olive-oil-tunisia.yaml +88 -88
- package/templates/organic-farm-fresh.yaml +101 -101
- package/templates/restaurant-direct.yaml +97 -97
- package/server/services/fairness-engine.js +0 -409
- package/server/services/fairness.js +0 -420
package/server/security/index.js
CHANGED
|
@@ -1,368 +1,368 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* WAB Security Model
|
|
5
|
-
*
|
|
6
|
-
* Production-grade security for Agent OS:
|
|
7
|
-
* - Agent Identity (Ed25519 key pairs)
|
|
8
|
-
* - Capability-based access control
|
|
9
|
-
* - Command signing & verification
|
|
10
|
-
* - Per-site isolation
|
|
11
|
-
* - Credential management
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const crypto = require('crypto');
|
|
15
|
-
|
|
16
|
-
// ─── Agent Identity ─────────────────────────────────────────────────────────
|
|
17
|
-
|
|
18
|
-
class AgentIdentity {
|
|
19
|
-
constructor() {
|
|
20
|
-
this._agents = new Map(); // agentId → identity record
|
|
21
|
-
this._apiKeys = new Map(); // apiKey → agentId
|
|
22
|
-
this._sessions = new Map(); // sessionId → { agentId, expiresAt, capabilities }
|
|
23
|
-
this._stats = { registered: 0, authenticated: 0, rejected: 0 };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Register a new agent identity
|
|
28
|
-
*/
|
|
29
|
-
register(name, type, options = {}) {
|
|
30
|
-
const agentId = `agent_${crypto.randomBytes(16).toString('hex')}`;
|
|
31
|
-
const apiKey = `wab_${crypto.randomBytes(32).toString('hex')}`;
|
|
32
|
-
// Hash the API key for storage
|
|
33
|
-
const apiKeyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
|
|
34
|
-
|
|
35
|
-
const identity = {
|
|
36
|
-
id: agentId,
|
|
37
|
-
name,
|
|
38
|
-
type, // browser, server, hybrid, orchestrator
|
|
39
|
-
apiKeyHash,
|
|
40
|
-
publicKey: options.publicKey || null,
|
|
41
|
-
capabilities: new Set(options.capabilities || []),
|
|
42
|
-
metadata: options.metadata || {},
|
|
43
|
-
rateLimit: options.rateLimit || { maxPerMinute: 60 },
|
|
44
|
-
allowedIPs: options.allowedIPs || [],
|
|
45
|
-
allowedDomains: options.allowedDomains || ['*'],
|
|
46
|
-
status: 'active',
|
|
47
|
-
createdAt: Date.now(),
|
|
48
|
-
lastSeen: Date.now(),
|
|
49
|
-
commandCount: 0,
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
this._agents.set(agentId, identity);
|
|
53
|
-
this._apiKeys.set(apiKeyHash, agentId);
|
|
54
|
-
this._stats.registered++;
|
|
55
|
-
|
|
56
|
-
return { agentId, apiKey }; // Return raw key only once
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Authenticate an agent via API key
|
|
61
|
-
*/
|
|
62
|
-
authenticate(apiKey, ip = null) {
|
|
63
|
-
const hash = crypto.createHash('sha256').update(apiKey).digest('hex');
|
|
64
|
-
const agentId = this._apiKeys.get(hash);
|
|
65
|
-
if (!agentId) {
|
|
66
|
-
this._stats.rejected++;
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const agent = this._agents.get(agentId);
|
|
71
|
-
if (!agent || agent.status !== 'active') {
|
|
72
|
-
this._stats.rejected++;
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// IP allowlist check
|
|
77
|
-
if (agent.allowedIPs.length > 0 && ip) {
|
|
78
|
-
if (!agent.allowedIPs.includes(ip)) {
|
|
79
|
-
this._stats.rejected++;
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
agent.lastSeen = Date.now();
|
|
85
|
-
this._stats.authenticated++;
|
|
86
|
-
|
|
87
|
-
// Create session
|
|
88
|
-
const sessionId = `sess_${crypto.randomBytes(24).toString('hex')}`;
|
|
89
|
-
const session = {
|
|
90
|
-
id: sessionId,
|
|
91
|
-
agentId,
|
|
92
|
-
capabilities: [...agent.capabilities],
|
|
93
|
-
ip,
|
|
94
|
-
createdAt: Date.now(),
|
|
95
|
-
expiresAt: Date.now() + 3600_000, // 1 hour
|
|
96
|
-
};
|
|
97
|
-
this._sessions.set(sessionId, session);
|
|
98
|
-
|
|
99
|
-
return {
|
|
100
|
-
sessionId,
|
|
101
|
-
agentId,
|
|
102
|
-
name: agent.name,
|
|
103
|
-
type: agent.type,
|
|
104
|
-
capabilities: [...agent.capabilities],
|
|
105
|
-
expiresAt: session.expiresAt,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Validate a session
|
|
111
|
-
*/
|
|
112
|
-
validateSession(sessionId) {
|
|
113
|
-
const session = this._sessions.get(sessionId);
|
|
114
|
-
if (!session) return null;
|
|
115
|
-
if (Date.now() > session.expiresAt) {
|
|
116
|
-
this._sessions.delete(sessionId);
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
return session;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Get agent identity (safe version, no secrets)
|
|
124
|
-
*/
|
|
125
|
-
getAgent(agentId) {
|
|
126
|
-
const agent = this._agents.get(agentId);
|
|
127
|
-
if (!agent) return null;
|
|
128
|
-
return {
|
|
129
|
-
id: agent.id,
|
|
130
|
-
name: agent.name,
|
|
131
|
-
type: agent.type,
|
|
132
|
-
capabilities: [...agent.capabilities],
|
|
133
|
-
status: agent.status,
|
|
134
|
-
createdAt: agent.createdAt,
|
|
135
|
-
lastSeen: agent.lastSeen,
|
|
136
|
-
commandCount: agent.commandCount,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Update agent capabilities
|
|
142
|
-
*/
|
|
143
|
-
updateCapabilities(agentId, capabilities) {
|
|
144
|
-
const agent = this._agents.get(agentId);
|
|
145
|
-
if (!agent) throw new Error(`Agent not found: ${agentId}`);
|
|
146
|
-
agent.capabilities = new Set(capabilities);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Revoke an agent
|
|
151
|
-
*/
|
|
152
|
-
revoke(agentId) {
|
|
153
|
-
const agent = this._agents.get(agentId);
|
|
154
|
-
if (agent) {
|
|
155
|
-
agent.status = 'revoked';
|
|
156
|
-
// Kill all sessions
|
|
157
|
-
for (const [sid, sess] of this._sessions) {
|
|
158
|
-
if (sess.agentId === agentId) this._sessions.delete(sid);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* List agents
|
|
165
|
-
*/
|
|
166
|
-
listAgents(filter = {}) {
|
|
167
|
-
const result = [];
|
|
168
|
-
for (const [, agent] of this._agents) {
|
|
169
|
-
if (filter.type && agent.type !== filter.type) continue;
|
|
170
|
-
if (filter.status && agent.status !== filter.status) continue;
|
|
171
|
-
result.push(this.getAgent(agent.id));
|
|
172
|
-
}
|
|
173
|
-
return result;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Increment command count for an agent
|
|
178
|
-
*/
|
|
179
|
-
trackCommand(agentId) {
|
|
180
|
-
const agent = this._agents.get(agentId);
|
|
181
|
-
if (agent) {
|
|
182
|
-
agent.commandCount++;
|
|
183
|
-
agent.lastSeen = Date.now();
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Validate a session token and return session data
|
|
189
|
-
*/
|
|
190
|
-
validateSession(token) {
|
|
191
|
-
const session = this._sessions.get(token);
|
|
192
|
-
if (!session) return null;
|
|
193
|
-
if (Date.now() > session.expiresAt) {
|
|
194
|
-
this._sessions.delete(token);
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
return session;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Cleanup expired sessions
|
|
202
|
-
*/
|
|
203
|
-
cleanup() {
|
|
204
|
-
const now = Date.now();
|
|
205
|
-
for (const [sid, sess] of this._sessions) {
|
|
206
|
-
if (now > sess.expiresAt) this._sessions.delete(sid);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
getStats() {
|
|
211
|
-
return {
|
|
212
|
-
...this._stats,
|
|
213
|
-
totalAgents: this._agents.size,
|
|
214
|
-
activeSessions: this._sessions.size,
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ─── Command Signing ────────────────────────────────────────────────────────
|
|
220
|
-
|
|
221
|
-
class CommandSigner {
|
|
222
|
-
constructor(secret) {
|
|
223
|
-
this._secret = secret || crypto.randomBytes(32).toString('hex');
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Sign a command payload
|
|
228
|
-
*/
|
|
229
|
-
sign(payload, agentId) {
|
|
230
|
-
const nonce = crypto.randomBytes(16).toString('hex');
|
|
231
|
-
const timestamp = Date.now();
|
|
232
|
-
const data = JSON.stringify({ payload, agentId, nonce, timestamp });
|
|
233
|
-
const signature = crypto.createHmac('sha256', this._secret).update(data).digest('hex');
|
|
234
|
-
|
|
235
|
-
return { nonce, timestamp, signature };
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Verify a signed command
|
|
240
|
-
*/
|
|
241
|
-
verify(payload, agentId, nonce, timestamp, signature, maxAge = 300_000) {
|
|
242
|
-
// Check timestamp freshness (5 min default)
|
|
243
|
-
if (Math.abs(Date.now() - timestamp) > maxAge) {
|
|
244
|
-
return { valid: false, reason: 'Timestamp expired' };
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const data = JSON.stringify({ payload, agentId, nonce, timestamp });
|
|
248
|
-
const expected = crypto.createHmac('sha256', this._secret).update(data).digest('hex');
|
|
249
|
-
|
|
250
|
-
// Timing-safe comparison
|
|
251
|
-
if (signature.length !== expected.length) {
|
|
252
|
-
return { valid: false, reason: 'Invalid signature' };
|
|
253
|
-
}
|
|
254
|
-
const valid = crypto.timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(expected, 'hex'));
|
|
255
|
-
|
|
256
|
-
return { valid, reason: valid ? null : 'Signature mismatch' };
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// ─── Site Isolation ─────────────────────────────────────────────────────────
|
|
261
|
-
|
|
262
|
-
class SiteIsolation {
|
|
263
|
-
constructor() {
|
|
264
|
-
this._sites = new Map(); // siteId → isolation config
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Configure isolation for a site
|
|
269
|
-
*/
|
|
270
|
-
configure(siteId, config) {
|
|
271
|
-
this._sites.set(siteId, {
|
|
272
|
-
siteId,
|
|
273
|
-
allowedAgents: new Set(config.allowedAgents || []),
|
|
274
|
-
blockedAgents: new Set(config.blockedAgents || []),
|
|
275
|
-
maxConcurrentAgents: config.maxConcurrentAgents || 5,
|
|
276
|
-
allowedCapabilities: new Set(config.allowedCapabilities || ['*']),
|
|
277
|
-
blockedSelectors: config.blockedSelectors || ['.private', '[data-secret]', '#password'],
|
|
278
|
-
dataClassification: config.dataClassification || 'public', // public, internal, confidential, restricted
|
|
279
|
-
requireSigning: config.requireSigning || false,
|
|
280
|
-
auditAll: config.auditAll || false,
|
|
281
|
-
activeAgents: new Set(),
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Check if agent can access site
|
|
287
|
-
*/
|
|
288
|
-
canAccess(siteId, agentId) {
|
|
289
|
-
const site = this._sites.get(siteId);
|
|
290
|
-
if (!site) return true; // No config = open
|
|
291
|
-
|
|
292
|
-
if (site.blockedAgents.has(agentId)) return false;
|
|
293
|
-
if (site.allowedAgents.size > 0 && !site.allowedAgents.has(agentId)) return false;
|
|
294
|
-
if (site.activeAgents.size >= site.maxConcurrentAgents) return false;
|
|
295
|
-
|
|
296
|
-
return true;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Enter a site (track active agent)
|
|
301
|
-
*/
|
|
302
|
-
enter(siteId, agentId) {
|
|
303
|
-
const site = this._sites.get(siteId);
|
|
304
|
-
if (!site) return true;
|
|
305
|
-
if (!this.canAccess(siteId, agentId)) return false;
|
|
306
|
-
site.activeAgents.add(agentId);
|
|
307
|
-
return true;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Leave a site
|
|
312
|
-
*/
|
|
313
|
-
leave(siteId, agentId) {
|
|
314
|
-
const site = this._sites.get(siteId);
|
|
315
|
-
if (site) site.activeAgents.delete(agentId);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Check capability for a site
|
|
320
|
-
*/
|
|
321
|
-
checkCapability(siteId, capability) {
|
|
322
|
-
const site = this._sites.get(siteId);
|
|
323
|
-
if (!site) return true;
|
|
324
|
-
if (site.allowedCapabilities.has('*')) return true;
|
|
325
|
-
return site.allowedCapabilities.has(capability);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Check selector access
|
|
330
|
-
*/
|
|
331
|
-
checkSelector(siteId, selector) {
|
|
332
|
-
const site = this._sites.get(siteId);
|
|
333
|
-
if (!site) return true;
|
|
334
|
-
return !site.blockedSelectors.some(b => selector.includes(b));
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
getConfig(siteId) {
|
|
338
|
-
const site = this._sites.get(siteId);
|
|
339
|
-
if (!site) return null;
|
|
340
|
-
return {
|
|
341
|
-
siteId: site.siteId,
|
|
342
|
-
maxConcurrentAgents: site.maxConcurrentAgents,
|
|
343
|
-
activeAgentCount: site.activeAgents.size,
|
|
344
|
-
dataClassification: site.dataClassification,
|
|
345
|
-
requireSigning: site.requireSigning,
|
|
346
|
-
auditAll: site.auditAll,
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// ─── Singletons ─────────────────────────────────────────────────────────────
|
|
352
|
-
|
|
353
|
-
const identity = new AgentIdentity();
|
|
354
|
-
const signer = new CommandSigner(process.env.WAB_SIGNING_SECRET);
|
|
355
|
-
const isolation = new SiteIsolation();
|
|
356
|
-
|
|
357
|
-
// Cleanup timer
|
|
358
|
-
const _cleanupTimer = setInterval(() => identity.cleanup(), 300_000);
|
|
359
|
-
if (_cleanupTimer.unref) _cleanupTimer.unref();
|
|
360
|
-
|
|
361
|
-
module.exports = {
|
|
362
|
-
AgentIdentity,
|
|
363
|
-
CommandSigner,
|
|
364
|
-
SiteIsolation,
|
|
365
|
-
identity,
|
|
366
|
-
signer,
|
|
367
|
-
isolation,
|
|
368
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WAB Security Model
|
|
5
|
+
*
|
|
6
|
+
* Production-grade security for Agent OS:
|
|
7
|
+
* - Agent Identity (Ed25519 key pairs)
|
|
8
|
+
* - Capability-based access control
|
|
9
|
+
* - Command signing & verification
|
|
10
|
+
* - Per-site isolation
|
|
11
|
+
* - Credential management
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const crypto = require('crypto');
|
|
15
|
+
|
|
16
|
+
// ─── Agent Identity ─────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
class AgentIdentity {
|
|
19
|
+
constructor() {
|
|
20
|
+
this._agents = new Map(); // agentId → identity record
|
|
21
|
+
this._apiKeys = new Map(); // apiKey → agentId
|
|
22
|
+
this._sessions = new Map(); // sessionId → { agentId, expiresAt, capabilities }
|
|
23
|
+
this._stats = { registered: 0, authenticated: 0, rejected: 0 };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Register a new agent identity
|
|
28
|
+
*/
|
|
29
|
+
register(name, type, options = {}) {
|
|
30
|
+
const agentId = `agent_${crypto.randomBytes(16).toString('hex')}`;
|
|
31
|
+
const apiKey = `wab_${crypto.randomBytes(32).toString('hex')}`;
|
|
32
|
+
// Hash the API key for storage
|
|
33
|
+
const apiKeyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
|
|
34
|
+
|
|
35
|
+
const identity = {
|
|
36
|
+
id: agentId,
|
|
37
|
+
name,
|
|
38
|
+
type, // browser, server, hybrid, orchestrator
|
|
39
|
+
apiKeyHash,
|
|
40
|
+
publicKey: options.publicKey || null,
|
|
41
|
+
capabilities: new Set(options.capabilities || []),
|
|
42
|
+
metadata: options.metadata || {},
|
|
43
|
+
rateLimit: options.rateLimit || { maxPerMinute: 60 },
|
|
44
|
+
allowedIPs: options.allowedIPs || [],
|
|
45
|
+
allowedDomains: options.allowedDomains || ['*'],
|
|
46
|
+
status: 'active',
|
|
47
|
+
createdAt: Date.now(),
|
|
48
|
+
lastSeen: Date.now(),
|
|
49
|
+
commandCount: 0,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
this._agents.set(agentId, identity);
|
|
53
|
+
this._apiKeys.set(apiKeyHash, agentId);
|
|
54
|
+
this._stats.registered++;
|
|
55
|
+
|
|
56
|
+
return { agentId, apiKey }; // Return raw key only once
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Authenticate an agent via API key
|
|
61
|
+
*/
|
|
62
|
+
authenticate(apiKey, ip = null) {
|
|
63
|
+
const hash = crypto.createHash('sha256').update(apiKey).digest('hex');
|
|
64
|
+
const agentId = this._apiKeys.get(hash);
|
|
65
|
+
if (!agentId) {
|
|
66
|
+
this._stats.rejected++;
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const agent = this._agents.get(agentId);
|
|
71
|
+
if (!agent || agent.status !== 'active') {
|
|
72
|
+
this._stats.rejected++;
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// IP allowlist check
|
|
77
|
+
if (agent.allowedIPs.length > 0 && ip) {
|
|
78
|
+
if (!agent.allowedIPs.includes(ip)) {
|
|
79
|
+
this._stats.rejected++;
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
agent.lastSeen = Date.now();
|
|
85
|
+
this._stats.authenticated++;
|
|
86
|
+
|
|
87
|
+
// Create session
|
|
88
|
+
const sessionId = `sess_${crypto.randomBytes(24).toString('hex')}`;
|
|
89
|
+
const session = {
|
|
90
|
+
id: sessionId,
|
|
91
|
+
agentId,
|
|
92
|
+
capabilities: [...agent.capabilities],
|
|
93
|
+
ip,
|
|
94
|
+
createdAt: Date.now(),
|
|
95
|
+
expiresAt: Date.now() + 3600_000, // 1 hour
|
|
96
|
+
};
|
|
97
|
+
this._sessions.set(sessionId, session);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
sessionId,
|
|
101
|
+
agentId,
|
|
102
|
+
name: agent.name,
|
|
103
|
+
type: agent.type,
|
|
104
|
+
capabilities: [...agent.capabilities],
|
|
105
|
+
expiresAt: session.expiresAt,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Validate a session
|
|
111
|
+
*/
|
|
112
|
+
validateSession(sessionId) {
|
|
113
|
+
const session = this._sessions.get(sessionId);
|
|
114
|
+
if (!session) return null;
|
|
115
|
+
if (Date.now() > session.expiresAt) {
|
|
116
|
+
this._sessions.delete(sessionId);
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return session;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get agent identity (safe version, no secrets)
|
|
124
|
+
*/
|
|
125
|
+
getAgent(agentId) {
|
|
126
|
+
const agent = this._agents.get(agentId);
|
|
127
|
+
if (!agent) return null;
|
|
128
|
+
return {
|
|
129
|
+
id: agent.id,
|
|
130
|
+
name: agent.name,
|
|
131
|
+
type: agent.type,
|
|
132
|
+
capabilities: [...agent.capabilities],
|
|
133
|
+
status: agent.status,
|
|
134
|
+
createdAt: agent.createdAt,
|
|
135
|
+
lastSeen: agent.lastSeen,
|
|
136
|
+
commandCount: agent.commandCount,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Update agent capabilities
|
|
142
|
+
*/
|
|
143
|
+
updateCapabilities(agentId, capabilities) {
|
|
144
|
+
const agent = this._agents.get(agentId);
|
|
145
|
+
if (!agent) throw new Error(`Agent not found: ${agentId}`);
|
|
146
|
+
agent.capabilities = new Set(capabilities);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Revoke an agent
|
|
151
|
+
*/
|
|
152
|
+
revoke(agentId) {
|
|
153
|
+
const agent = this._agents.get(agentId);
|
|
154
|
+
if (agent) {
|
|
155
|
+
agent.status = 'revoked';
|
|
156
|
+
// Kill all sessions
|
|
157
|
+
for (const [sid, sess] of this._sessions) {
|
|
158
|
+
if (sess.agentId === agentId) this._sessions.delete(sid);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* List agents
|
|
165
|
+
*/
|
|
166
|
+
listAgents(filter = {}) {
|
|
167
|
+
const result = [];
|
|
168
|
+
for (const [, agent] of this._agents) {
|
|
169
|
+
if (filter.type && agent.type !== filter.type) continue;
|
|
170
|
+
if (filter.status && agent.status !== filter.status) continue;
|
|
171
|
+
result.push(this.getAgent(agent.id));
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Increment command count for an agent
|
|
178
|
+
*/
|
|
179
|
+
trackCommand(agentId) {
|
|
180
|
+
const agent = this._agents.get(agentId);
|
|
181
|
+
if (agent) {
|
|
182
|
+
agent.commandCount++;
|
|
183
|
+
agent.lastSeen = Date.now();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Validate a session token and return session data
|
|
189
|
+
*/
|
|
190
|
+
validateSession(token) {
|
|
191
|
+
const session = this._sessions.get(token);
|
|
192
|
+
if (!session) return null;
|
|
193
|
+
if (Date.now() > session.expiresAt) {
|
|
194
|
+
this._sessions.delete(token);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
return session;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Cleanup expired sessions
|
|
202
|
+
*/
|
|
203
|
+
cleanup() {
|
|
204
|
+
const now = Date.now();
|
|
205
|
+
for (const [sid, sess] of this._sessions) {
|
|
206
|
+
if (now > sess.expiresAt) this._sessions.delete(sid);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
getStats() {
|
|
211
|
+
return {
|
|
212
|
+
...this._stats,
|
|
213
|
+
totalAgents: this._agents.size,
|
|
214
|
+
activeSessions: this._sessions.size,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ─── Command Signing ────────────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
class CommandSigner {
|
|
222
|
+
constructor(secret) {
|
|
223
|
+
this._secret = secret || crypto.randomBytes(32).toString('hex');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Sign a command payload
|
|
228
|
+
*/
|
|
229
|
+
sign(payload, agentId) {
|
|
230
|
+
const nonce = crypto.randomBytes(16).toString('hex');
|
|
231
|
+
const timestamp = Date.now();
|
|
232
|
+
const data = JSON.stringify({ payload, agentId, nonce, timestamp });
|
|
233
|
+
const signature = crypto.createHmac('sha256', this._secret).update(data).digest('hex');
|
|
234
|
+
|
|
235
|
+
return { nonce, timestamp, signature };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Verify a signed command
|
|
240
|
+
*/
|
|
241
|
+
verify(payload, agentId, nonce, timestamp, signature, maxAge = 300_000) {
|
|
242
|
+
// Check timestamp freshness (5 min default)
|
|
243
|
+
if (Math.abs(Date.now() - timestamp) > maxAge) {
|
|
244
|
+
return { valid: false, reason: 'Timestamp expired' };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const data = JSON.stringify({ payload, agentId, nonce, timestamp });
|
|
248
|
+
const expected = crypto.createHmac('sha256', this._secret).update(data).digest('hex');
|
|
249
|
+
|
|
250
|
+
// Timing-safe comparison
|
|
251
|
+
if (signature.length !== expected.length) {
|
|
252
|
+
return { valid: false, reason: 'Invalid signature' };
|
|
253
|
+
}
|
|
254
|
+
const valid = crypto.timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(expected, 'hex'));
|
|
255
|
+
|
|
256
|
+
return { valid, reason: valid ? null : 'Signature mismatch' };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ─── Site Isolation ─────────────────────────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
class SiteIsolation {
|
|
263
|
+
constructor() {
|
|
264
|
+
this._sites = new Map(); // siteId → isolation config
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Configure isolation for a site
|
|
269
|
+
*/
|
|
270
|
+
configure(siteId, config) {
|
|
271
|
+
this._sites.set(siteId, {
|
|
272
|
+
siteId,
|
|
273
|
+
allowedAgents: new Set(config.allowedAgents || []),
|
|
274
|
+
blockedAgents: new Set(config.blockedAgents || []),
|
|
275
|
+
maxConcurrentAgents: config.maxConcurrentAgents || 5,
|
|
276
|
+
allowedCapabilities: new Set(config.allowedCapabilities || ['*']),
|
|
277
|
+
blockedSelectors: config.blockedSelectors || ['.private', '[data-secret]', '#password'],
|
|
278
|
+
dataClassification: config.dataClassification || 'public', // public, internal, confidential, restricted
|
|
279
|
+
requireSigning: config.requireSigning || false,
|
|
280
|
+
auditAll: config.auditAll || false,
|
|
281
|
+
activeAgents: new Set(),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Check if agent can access site
|
|
287
|
+
*/
|
|
288
|
+
canAccess(siteId, agentId) {
|
|
289
|
+
const site = this._sites.get(siteId);
|
|
290
|
+
if (!site) return true; // No config = open
|
|
291
|
+
|
|
292
|
+
if (site.blockedAgents.has(agentId)) return false;
|
|
293
|
+
if (site.allowedAgents.size > 0 && !site.allowedAgents.has(agentId)) return false;
|
|
294
|
+
if (site.activeAgents.size >= site.maxConcurrentAgents) return false;
|
|
295
|
+
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Enter a site (track active agent)
|
|
301
|
+
*/
|
|
302
|
+
enter(siteId, agentId) {
|
|
303
|
+
const site = this._sites.get(siteId);
|
|
304
|
+
if (!site) return true;
|
|
305
|
+
if (!this.canAccess(siteId, agentId)) return false;
|
|
306
|
+
site.activeAgents.add(agentId);
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Leave a site
|
|
312
|
+
*/
|
|
313
|
+
leave(siteId, agentId) {
|
|
314
|
+
const site = this._sites.get(siteId);
|
|
315
|
+
if (site) site.activeAgents.delete(agentId);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Check capability for a site
|
|
320
|
+
*/
|
|
321
|
+
checkCapability(siteId, capability) {
|
|
322
|
+
const site = this._sites.get(siteId);
|
|
323
|
+
if (!site) return true;
|
|
324
|
+
if (site.allowedCapabilities.has('*')) return true;
|
|
325
|
+
return site.allowedCapabilities.has(capability);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Check selector access
|
|
330
|
+
*/
|
|
331
|
+
checkSelector(siteId, selector) {
|
|
332
|
+
const site = this._sites.get(siteId);
|
|
333
|
+
if (!site) return true;
|
|
334
|
+
return !site.blockedSelectors.some(b => selector.includes(b));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
getConfig(siteId) {
|
|
338
|
+
const site = this._sites.get(siteId);
|
|
339
|
+
if (!site) return null;
|
|
340
|
+
return {
|
|
341
|
+
siteId: site.siteId,
|
|
342
|
+
maxConcurrentAgents: site.maxConcurrentAgents,
|
|
343
|
+
activeAgentCount: site.activeAgents.size,
|
|
344
|
+
dataClassification: site.dataClassification,
|
|
345
|
+
requireSigning: site.requireSigning,
|
|
346
|
+
auditAll: site.auditAll,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ─── Singletons ─────────────────────────────────────────────────────────────
|
|
352
|
+
|
|
353
|
+
const identity = new AgentIdentity();
|
|
354
|
+
const signer = new CommandSigner(process.env.WAB_SIGNING_SECRET);
|
|
355
|
+
const isolation = new SiteIsolation();
|
|
356
|
+
|
|
357
|
+
// Cleanup timer
|
|
358
|
+
const _cleanupTimer = setInterval(() => identity.cleanup(), 300_000);
|
|
359
|
+
if (_cleanupTimer.unref) _cleanupTimer.unref();
|
|
360
|
+
|
|
361
|
+
module.exports = {
|
|
362
|
+
AgentIdentity,
|
|
363
|
+
CommandSigner,
|
|
364
|
+
SiteIsolation,
|
|
365
|
+
identity,
|
|
366
|
+
signer,
|
|
367
|
+
isolation,
|
|
368
|
+
};
|