web-agent-bridge 3.4.0 → 3.9.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 +84 -84
- package/README.ar.md +1565 -1304
- package/README.md +171 -298
- package/bin/agent-runner.js +474 -474
- package/bin/cli.js +237 -237
- package/bin/wab-init.js +244 -223
- package/bin/wab.js +80 -80
- package/examples/azure-dns-wab.js +83 -83
- package/examples/bidi-agent.js +119 -119
- package/examples/cloudflare-wab-dns.js +121 -121
- package/examples/cpanel-wab-dns.js +114 -114
- package/examples/cross-site-agent.js +91 -91
- package/examples/dns-discovery-agent.js +166 -166
- package/examples/gcp-dns-wab.js +76 -76
- package/examples/governance-agent.js +169 -169
- package/examples/mcp-agent.js +94 -94
- package/examples/next-app-router/README.md +44 -44
- package/examples/plesk-wab-dns.js +103 -103
- package/examples/puppeteer-agent.js +108 -108
- package/examples/route53-wab-dns.js +144 -144
- package/examples/saas-dashboard/README.md +55 -55
- package/examples/safe-mode-agent.js +96 -96
- package/examples/self-discovery.js +106 -0
- package/examples/shopify-hydrogen/README.md +74 -74
- package/examples/vision-agent.js +171 -171
- package/examples/wab-sign.js +74 -74
- package/examples/wab-verify.js +60 -60
- package/examples/wordpress-elementor/README.md +77 -77
- package/package.json +93 -93
- package/public/.well-known/agent-tools.json +180 -180
- package/public/.well-known/ai-assets.json +59 -59
- package/public/.well-known/security.txt +8 -8
- package/public/.well-known/wab.json +28 -28
- package/public/activate.html +448 -368
- package/public/adopt.html +236 -0
- package/public/adoption-metrics.html +188 -188
- package/public/agent-workspace.html +359 -349
- package/public/ai.html +198 -198
- package/public/api.html +397 -413
- package/public/atp.html +171 -0
- package/public/azure-dns-integration.html +289 -289
- package/public/browser.html +486 -486
- package/public/cloudflare-integration.html +380 -380
- package/public/commander-dashboard.html +243 -243
- package/public/cookies.html +210 -210
- package/public/cpanel-integration.html +398 -398
- package/public/css/agent-workspace.css +1713 -1713
- package/public/css/premium.css +317 -317
- package/public/css/styles.css +1401 -1263
- package/public/dashboard-shieldlink.html +295 -0
- package/public/dashboard.html +711 -707
- package/public/dns.html +436 -436
- package/public/docs.html +588 -588
- package/public/enterprise-mesh.ar.html +80 -0
- package/public/enterprise-mesh.html +81 -0
- package/public/feed.xml +89 -89
- package/public/gcp-dns-integration.html +318 -318
- package/public/governance.ar.html +70 -0
- package/public/governance.html +69 -0
- package/public/growth.html +465 -465
- package/public/index.html +1372 -1266
- package/public/integrations.html +556 -556
- package/public/js/activate.js +449 -145
- package/public/js/agent-workspace.js +1740 -1740
- package/public/js/auth-nav.js +117 -65
- package/public/js/auth-redirect.js +12 -12
- package/public/js/cookie-consent.js +56 -56
- package/public/js/dns.js +438 -438
- package/public/js/wab-demo-page.js +721 -721
- package/public/js/ws-client.js +74 -74
- package/public/l-preview.html +242 -0
- package/public/llms-full.txt +360 -360
- package/public/llms.txt +125 -125
- package/public/login.html +85 -85
- package/public/mesh-dashboard.html +328 -328
- package/public/milestones.html +346 -0
- package/public/one-click.html +779 -0
- package/public/openapi.json +669 -669
- package/public/partners.ar.html +145 -0
- package/public/partners.html +143 -0
- package/public/phone-shield.html +281 -281
- package/public/plesk-integration.html +375 -375
- package/public/premium-dashboard.html +2489 -2489
- package/public/premium.html +793 -793
- package/public/privacy.html +297 -297
- package/public/provider-onboarding.html +172 -172
- package/public/provider-sandbox.html +134 -134
- package/public/providers.html +359 -359
- package/public/refusals.html +172 -0
- package/public/register.html +105 -105
- package/public/registrar-integrations.html +141 -141
- package/public/ring4.html +292 -0
- package/public/robots.txt +99 -99
- package/public/route53-integration.html +531 -531
- package/public/score.html +263 -0
- 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 -8
- package/public/shieldlink.html +244 -0
- package/public/shieldqr.html +231 -231
- package/public/sitemap.xml +13 -1
- package/public/terms.html +256 -256
- package/public/trust-graph-api.ar.html +92 -0
- package/public/trust-graph-api.html +91 -0
- package/public/wab-features.html +560 -0
- package/public/wab-trust.html +200 -200
- package/public/wab-truth.html +375 -0
- package/public/wab-vs-protocols.html +210 -210
- package/public/whitepaper.html +449 -449
- package/script/ai-agent-bridge.js +1754 -1754
- package/sdk/README.md +99 -99
- package/sdk/agent-mesh.js +449 -449
- package/sdk/atp.js +103 -0
- package/sdk/auto-discovery.js +301 -288
- package/sdk/commander.js +262 -262
- package/sdk/governance.js +262 -262
- package/sdk/index.d.ts +464 -464
- package/sdk/index.js +653 -649
- package/sdk/multi-agent.js +318 -318
- package/sdk/safe-mode.js +221 -221
- package/sdk/safety-shield.js +219 -219
- package/sdk/schema-discovery.js +83 -83
- package/server/adapters/index.js +520 -520
- package/server/config/plans.js +412 -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 +793 -670
- package/server/llm/index.js +404 -404
- package/server/middleware/adminAuth.js +35 -35
- package/server/middleware/api-tier.js +170 -0
- 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 -157
- package/server/middleware/wab-trust.js +141 -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/migrations/006_growth_suite.sql +138 -0
- package/server/migrations/007_governance.sql +106 -106
- package/server/migrations/008_plans.sql +144 -144
- package/server/migrations/009_shieldqr.sql +30 -30
- package/server/migrations/010_extended_trust.sql +33 -33
- package/server/migrations/011_outreach.sql +47 -0
- package/server/migrations/012_shieldlink.sql +116 -0
- package/server/migrations/013_ct_monitor.sql +13 -0
- package/server/migrations/014_wab_advanced_features.sql +128 -0
- package/server/migrations/015_wab_truth_layer.sql +101 -0
- package/server/migrations/016_ring4_external_trust.sql +84 -0
- package/server/migrations/017_ring4_extensions.sql +69 -0
- package/server/migrations/018_commercial_foundations.sql +167 -0
- package/server/migrations/019_unify_tier_constraints.sql +133 -0
- package/server/migrations/020_agent_transaction_primitive.sql +119 -0
- 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 +740 -740
- 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/activate.js +478 -0
- package/server/routes/admin-outreach.js +239 -0
- package/server/routes/admin-plans.js +76 -76
- package/server/routes/admin-premium.js +674 -673
- package/server/routes/admin-shieldlink.js +137 -0
- package/server/routes/admin-shieldqr.js +90 -90
- package/server/routes/admin-trust-monitor.js +139 -83
- package/server/routes/admin.js +550 -549
- package/server/routes/adopt.js +61 -0
- package/server/routes/ads.js +130 -130
- package/server/routes/agent-workspace.js +540 -540
- package/server/routes/api-keys.js +127 -0
- package/server/routes/api.js +150 -150
- package/server/routes/auth.js +71 -71
- package/server/routes/billing.js +57 -57
- package/server/routes/commander.js +316 -316
- package/server/routes/customer-shieldlink.js +133 -0
- package/server/routes/demo-showcase.js +332 -332
- package/server/routes/demo-store.js +154 -154
- package/server/routes/diagnose.js +373 -0
- package/server/routes/discovery.js +2348 -2348
- package/server/routes/enterprise-mesh.js +170 -0
- package/server/routes/gateway.js +173 -173
- package/server/routes/governance-saas.js +203 -0
- package/server/routes/governance.js +208 -208
- package/server/routes/growth.js +1048 -0
- package/server/routes/intent.js +328 -0
- package/server/routes/license.js +251 -251
- package/server/routes/mesh.js +469 -469
- package/server/routes/noscript.js +543 -543
- package/server/routes/partners.js +201 -0
- package/server/routes/plans.js +33 -33
- package/server/routes/premium-v2.js +686 -686
- package/server/routes/premium.js +724 -724
- package/server/routes/providers.js +650 -650
- package/server/routes/reputation.js +411 -0
- package/server/routes/ring4.js +885 -0
- package/server/routes/runtime.js +2148 -2148
- package/server/routes/shieldlink.js +70 -0
- package/server/routes/shieldqr.js +88 -88
- package/server/routes/sovereign.js +465 -465
- package/server/routes/transactions.js +233 -0
- package/server/routes/truth-layer.js +670 -0
- package/server/routes/universal.js +200 -200
- package/server/routes/unsubscribe.js +51 -0
- package/server/routes/wab-api.js +850 -850
- package/server/routes/wab-cache.js +282 -0
- 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/secrets/wab-signing-key.pem +3 -0
- package/server/secrets/wab-signing-pub.pem +3 -0
- package/server/security/cross-site-redactor.js +196 -196
- package/server/security/dry-run.js +180 -180
- package/server/security/human-gate-rate-limit.js +147 -147
- package/server/security/human-gate-transports.js +178 -178
- package/server/security/human-gate.js +281 -281
- package/server/security/index.js +368 -368
- package/server/security/intent-engine.js +245 -245
- package/server/security/reward-guard.js +171 -171
- package/server/security/rollback-store.js +239 -239
- package/server/security/token-scope.js +404 -404
- package/server/security/url-policy.js +139 -139
- package/server/services/adoption-agent.js +182 -0
- package/server/services/agent-chat.js +506 -506
- package/server/services/agent-learning.js +601 -601
- package/server/services/agent-memory.js +625 -625
- package/server/services/agent-mesh.js +555 -555
- package/server/services/agent-symphony.js +717 -717
- package/server/services/agent-tasks.js +1807 -1807
- package/server/services/api-key-engine.js +292 -292
- 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 +233 -233
- package/server/services/fairness-engine.js +409 -0
- package/server/services/fairness.js +420 -0
- package/server/services/governance.js +466 -466
- package/server/services/hosted-runtime.js +205 -205
- package/server/services/lfd.js +635 -635
- 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 -93
- package/server/services/modules/agent-firewall.js +90 -90
- package/server/services/modules/bounty.js +89 -89
- package/server/services/modules/collective-bargaining.js +92 -92
- package/server/services/modules/dark-pattern.js +66 -66
- package/server/services/modules/gov-intelligence.js +45 -45
- package/server/services/modules/neural.js +55 -55
- package/server/services/modules/notary.js +49 -49
- package/server/services/modules/price-time-machine.js +86 -86
- package/server/services/modules/protocol.js +104 -104
- package/server/services/negotiation.js +439 -439
- package/server/services/outreach-agent.js +312 -0
- package/server/services/plans.js +214 -214
- package/server/services/plugins.js +771 -771
- package/server/services/price-intelligence.js +566 -566
- package/server/services/price-shield.js +1137 -1137
- package/server/services/provider-clients.js +740 -740
- 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/shieldlink.js +492 -0
- package/server/services/shieldqr.js +322 -322
- package/server/services/sovereign-shield.js +542 -542
- package/server/services/ssl-ct-monitor.js +224 -0
- package/server/services/ssl-inspector.js +42 -42
- package/server/services/ssl-monitor.js +167 -167
- package/server/services/stripe.js +206 -205
- package/server/services/swarm.js +788 -788
- package/server/services/transactions.js +525 -0
- package/server/services/universal-scraper.js +662 -662
- package/server/services/verification.js +481 -481
- package/server/services/vision.js +1163 -1163
- package/server/services/wab-crypto.js +178 -178
- package/server/utils/cache.js +125 -125
- package/server/utils/migrate.js +81 -81
- package/server/utils/safe-fetch.js +228 -228
- 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/templates/ring4/banking-sovereign.yaml +55 -0
- package/templates/ring4/ecommerce-sovereign.yaml +58 -0
- package/templates/ring4/healthcare-sovereign.yaml +60 -0
package/server/index.js
CHANGED
|
@@ -1,670 +1,793 @@
|
|
|
1
|
-
require('dotenv').config();
|
|
2
|
-
|
|
3
|
-
const { assertSecretsAtStartup } = require('./config/secrets');
|
|
4
|
-
assertSecretsAtStartup();
|
|
5
|
-
|
|
6
|
-
const express = require('express');
|
|
7
|
-
const http = require('http');
|
|
8
|
-
const cors = require('cors');
|
|
9
|
-
const helmet = require('helmet');
|
|
10
|
-
const rateLimit = require('express-rate-limit');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const { setupWebSocket } = require('./ws');
|
|
13
|
-
const { runMigrations } = require('./utils/migrate');
|
|
14
|
-
const { maybeBootstrapAdmin, db } = require('./models/db');
|
|
15
|
-
const { initSearchEngine, search, getSuggestions, getTrendingSearches, getSearchStats, purgeOldCache } = require('./services/search-engine');
|
|
16
|
-
const { processMessage: agentChat } = require('./services/agent-chat');
|
|
17
|
-
const agentTasks = require('./services/agent-tasks');
|
|
18
|
-
const { cluster } = require('./services/cluster');
|
|
19
|
-
|
|
20
|
-
const authRoutes = require('./routes/auth');
|
|
21
|
-
const apiRoutes = require('./routes/api');
|
|
22
|
-
const licenseRoutes = require('./routes/license');
|
|
23
|
-
const adminRoutes = require('./routes/admin');
|
|
24
|
-
const billingRoutes = require('./routes/billing');
|
|
25
|
-
const sovereignRoutes = require('./routes/sovereign');
|
|
26
|
-
const meshRoutes = require('./routes/mesh');
|
|
27
|
-
const commanderRoutes = require('./routes/commander');
|
|
28
|
-
const adsRoutes = require('./routes/ads');
|
|
29
|
-
const wabApiRoutes = require('./routes/wab-api');
|
|
30
|
-
const noscriptRoutes = require('./routes/noscript');
|
|
31
|
-
const discoveryRoutes = require('./routes/discovery');
|
|
32
|
-
const providerRoutes = require('./routes/providers');
|
|
33
|
-
const governanceRoutes = require('./routes/governance');
|
|
34
|
-
const premiumRoutes = require('./routes/premium');
|
|
35
|
-
const adminPremiumRoutes = require('./routes/admin-premium');
|
|
36
|
-
const workspaceRoutes = require('./routes/agent-workspace');
|
|
37
|
-
const universalRoutes = require('./routes/universal');
|
|
38
|
-
const runtimeRoutes = require('./routes/runtime');
|
|
39
|
-
const demoShowcaseRoutes = require('./routes/demo-showcase');
|
|
40
|
-
const demoStoreRoutes = require('./routes/demo-store');
|
|
41
|
-
const gatewayRoutes = require('./routes/gateway');
|
|
42
|
-
let growthRoutes;
|
|
43
|
-
try { growthRoutes = require('./routes/growth'); } catch { growthRoutes = require('express').Router(); }
|
|
44
|
-
const { handleWebhookRequest } = require('./services/stripe');
|
|
45
|
-
const { runtime } = require('./runtime');
|
|
46
|
-
|
|
47
|
-
const app = express();
|
|
48
|
-
const PORT = process.env.PORT || 3000;
|
|
49
|
-
|
|
50
|
-
app.set('trust proxy', 1);
|
|
51
|
-
|
|
52
|
-
const corsOrigins = (process.env.ALLOWED_ORIGINS
|
|
53
|
-
|| 'http://localhost:3000,http://127.0.0.1:3000,http://localhost:5173')
|
|
54
|
-
.split(',')
|
|
55
|
-
.map((s) => s.trim())
|
|
56
|
-
.filter(Boolean);
|
|
57
|
-
|
|
58
|
-
app.use(
|
|
59
|
-
cors({
|
|
60
|
-
origin(origin, callback) {
|
|
61
|
-
if (!origin) return callback(null, true);
|
|
62
|
-
if (corsOrigins.includes(origin)) return callback(null, true);
|
|
63
|
-
if (process.env.NODE_ENV !== 'production' && /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/i.test(origin)) {
|
|
64
|
-
return callback(null, true);
|
|
65
|
-
}
|
|
66
|
-
return callback(null, false);
|
|
67
|
-
},
|
|
68
|
-
credentials: true
|
|
69
|
-
})
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
const scriptSrc = process.env.CSP_ALLOW_UNSAFE_INLINE === 'false'
|
|
73
|
-
? ["'self'", 'https://unpkg.com', 'https://cdn.jsdelivr.net']
|
|
74
|
-
: ["'self'", "'unsafe-inline'", 'https://unpkg.com', 'https://cdn.jsdelivr.net'];
|
|
75
|
-
const styleSrc = process.env.CSP_ALLOW_UNSAFE_INLINE === 'false'
|
|
76
|
-
? ["'self'"]
|
|
77
|
-
: ["'self'", "'unsafe-inline'"];
|
|
78
|
-
|
|
79
|
-
// Per-request CSP nonce — exposed as res.locals.cspNonce for new pages opting into strict CSP.
|
|
80
|
-
app.use((req, res, next) => {
|
|
81
|
-
res.locals.cspNonce = require('crypto').randomBytes(16).toString('base64');
|
|
82
|
-
next();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// CSP — tightened: HTTPS-only iframes, upgrade-insecure-requests, report endpoint.
|
|
86
|
-
const cspReportUri = '/api/security/csp-report';
|
|
87
|
-
app.use(
|
|
88
|
-
helmet({
|
|
89
|
-
contentSecurityPolicy: {
|
|
90
|
-
directives: {
|
|
91
|
-
defaultSrc: ["'self'"],
|
|
92
|
-
// NOTE: Adding a nonce alongside 'unsafe-inline' makes browsers ignore
|
|
93
|
-
// 'unsafe-inline' (CSP3 spec). All existing public/admin pages still
|
|
94
|
-
// rely on inline <script> blocks, so we keep 'unsafe-inline' enforced
|
|
95
|
-
// here and use the Report-Only policy below to track nonce migration.
|
|
96
|
-
scriptSrc: scriptSrc,
|
|
97
|
-
scriptSrcAttr: [...scriptSrc, "'unsafe-hashes'"],
|
|
98
|
-
styleSrc: [...styleSrc, 'https://fonts.googleapis.com'],
|
|
99
|
-
imgSrc: ["'self'", 'data:', 'https:'],
|
|
100
|
-
connectSrc: ["'self'", 'https:', 'ws:', 'wss:'],
|
|
101
|
-
fontSrc: ["'self'", 'https://fonts.gstatic.com', 'https:', 'data:'],
|
|
102
|
-
frameSrc: ["'self'", 'https:'],
|
|
103
|
-
frameAncestors: ["'none'"],
|
|
104
|
-
objectSrc: ["'none'"],
|
|
105
|
-
baseUri: ["'self'"],
|
|
106
|
-
formAction: ["'self'"],
|
|
107
|
-
upgradeInsecureRequests: process.env.NODE_ENV === 'production' ? [] : null,
|
|
108
|
-
reportUri: [cspReportUri]
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
crossOriginEmbedderPolicy: false,
|
|
112
|
-
referrerPolicy: { policy: 'strict-origin-when-cross-origin' }
|
|
113
|
-
})
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
// Companion strict Report-Only CSP — surfaces every inline-script violation
|
|
117
|
-
// without breaking existing pages, so we can migrate page-by-page to nonces.
|
|
118
|
-
app.use((req, res, next) => {
|
|
119
|
-
const nonce = res.locals.cspNonce;
|
|
120
|
-
const strict = [
|
|
121
|
-
"default-src 'self'",
|
|
122
|
-
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
|
|
123
|
-
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
|
|
124
|
-
"img-src 'self' data: https:",
|
|
125
|
-
"connect-src 'self' https: wss:",
|
|
126
|
-
"font-src 'self' https://fonts.gstatic.com data:",
|
|
127
|
-
"frame-src 'self' https:",
|
|
128
|
-
"frame-ancestors 'none'",
|
|
129
|
-
"object-src 'none'",
|
|
130
|
-
"base-uri 'self'",
|
|
131
|
-
"form-action 'self'",
|
|
132
|
-
"upgrade-insecure-requests",
|
|
133
|
-
`report-uri ${cspReportUri}`
|
|
134
|
-
].join('; ');
|
|
135
|
-
res.setHeader('Content-Security-Policy-Report-Only', strict);
|
|
136
|
-
next();
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// CSP violation report sink (capped, in-memory ring buffer + console).
|
|
140
|
-
const _cspReports = [];
|
|
141
|
-
app.post('/api/security/csp-report', express.json({ type: ['application/csp-report', 'application/json'], limit: '32kb' }), (req, res) => {
|
|
142
|
-
const report = req.body && (req.body['csp-report'] || req.body);
|
|
143
|
-
if (report) {
|
|
144
|
-
_cspReports.push({ at: new Date().toISOString(), ip: req.ip, report });
|
|
145
|
-
if (_cspReports.length > 500) _cspReports.shift();
|
|
146
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
147
|
-
console.warn('[CSP]', report['violated-directive'] || report.violatedDirective, '→', report['blocked-uri'] || report.blockedURI);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
res.status(204).end();
|
|
151
|
-
});
|
|
152
|
-
app.get('/api/security/csp-report/recent', (req, res) => {
|
|
153
|
-
res.json({ count: _cspReports.length, reports: _cspReports.slice(-50) });
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
// ── Reward-guard + cross-site redactor admin views (token-gated) ──
|
|
157
|
-
function _adminAuth(req, res, next) {
|
|
158
|
-
const want = process.env.WAB_ADMIN_TOKEN;
|
|
159
|
-
if (!want) return res.status(503).json({ error: 'WAB_ADMIN_TOKEN not configured' });
|
|
160
|
-
const got = req.headers['x-wab-admin-token'] || req.query.token;
|
|
161
|
-
if (got !== want) return res.status(401).json({ error: 'admin token required' });
|
|
162
|
-
next();
|
|
163
|
-
}
|
|
164
|
-
app.get('/api/security/reward-audit/recent', _adminAuth, (req, res) => {
|
|
165
|
-
try {
|
|
166
|
-
const guard = require('./security/reward-guard');
|
|
167
|
-
res.json({ stats: guard.getStats(), recent: guard.getRecentAudits(50, req.query.decision || null) });
|
|
168
|
-
} catch (err) { res.status(500).json({ error: err.message }); }
|
|
169
|
-
});
|
|
170
|
-
app.get('/api/security/cross-site-transfers/recent', _adminAuth, (req, res) => {
|
|
171
|
-
try {
|
|
172
|
-
const r = require('./security/cross-site-redactor');
|
|
173
|
-
res.json({ recent: r.getRecentTransfers(50, req.query.from || null) });
|
|
174
|
-
} catch (err) { res.status(500).json({ error: err.message }); }
|
|
175
|
-
});
|
|
176
|
-
app.get('/api/security/url-policy/recent', _adminAuth, (req, res) => {
|
|
177
|
-
try {
|
|
178
|
-
const p = require('./security/url-policy');
|
|
179
|
-
res.json({ recent: p.getRecentAudits(50, req.query.decision || null) });
|
|
180
|
-
} catch (err) { res.status(500).json({ error: err.message }); }
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
app.post('/api/billing/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
|
|
184
|
-
try {
|
|
185
|
-
await handleWebhookRequest(req);
|
|
186
|
-
res.json({ received: true });
|
|
187
|
-
} catch (err) {
|
|
188
|
-
console.error('Webhook error:', err.message);
|
|
189
|
-
res.status(400).json({ error: err.message });
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
app.use(express.json());
|
|
194
|
-
|
|
195
|
-
// Global JSON parse error handler (catches malformed JSON from bots/scanners)
|
|
196
|
-
app.use((err, req, res, next) => {
|
|
197
|
-
if (err.type === 'entity.parse.failed' || err instanceof SyntaxError) {
|
|
198
|
-
return res.status(400).json({ error: 'Invalid JSON', details: err.message });
|
|
199
|
-
}
|
|
200
|
-
next(err);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// Global error handler — catches all unhandled route errors
|
|
204
|
-
// global-error-handler
|
|
205
|
-
app.use((err, req, res, next) => {
|
|
206
|
-
const status = err.status || err.statusCode || 500;
|
|
207
|
-
const message = err.message || 'Internal Server Error';
|
|
208
|
-
if (status >= 500) {
|
|
209
|
-
console.error('[server] Unhandled error:', err.message, err.stack?.split('\n')[1] || '');
|
|
210
|
-
}
|
|
211
|
-
if (!res.headersSent) {
|
|
212
|
-
res.status(status).json({ error: message });
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
const apiLimiter = rateLimit({
|
|
217
|
-
windowMs: 15 * 60 * 1000,
|
|
218
|
-
max: 200,
|
|
219
|
-
standardHeaders: true,
|
|
220
|
-
legacyHeaders: false,
|
|
221
|
-
message: { error: 'Too many requests, please try again later' }
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
const licenseLimiter = rateLimit({
|
|
225
|
-
windowMs: 60 * 1000,
|
|
226
|
-
max: 120,
|
|
227
|
-
standardHeaders: true,
|
|
228
|
-
legacyHeaders: false,
|
|
229
|
-
keyGenerator: (req) => {
|
|
230
|
-
const key = req.body?.licenseKey || req.body?.siteId || req.ip;
|
|
231
|
-
return `${req.ip}:${key}`;
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
// Whitepaper guard — must run BEFORE express.static so we can apply strict headers
|
|
236
|
-
// and intercept both /whitepaper and /whitepaper.html with the same protections.
|
|
237
|
-
const whitepaperHandler = (req, res) => {
|
|
238
|
-
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
|
|
239
|
-
res.set('Pragma', 'no-cache');
|
|
240
|
-
res.set('Expires', '0');
|
|
241
|
-
res.set('X-Frame-Options', 'DENY');
|
|
242
|
-
res.set('X-Content-Type-Options', 'nosniff');
|
|
243
|
-
res.set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
244
|
-
res.set('X-Robots-Tag', 'index, follow, noarchive, nosnippet, noimageindex');
|
|
245
|
-
res.set('Content-Security-Policy', "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'");
|
|
246
|
-
res.set('X-Copyright', 'All Rights Reserved (c) 2026 Web Agent Bridge - Reproduction Prohibited');
|
|
247
|
-
res.sendFile(path.join(__dirname, '..', 'public', 'whitepaper.html'));
|
|
248
|
-
};
|
|
249
|
-
app.get(['/whitepaper', '/whitepaper.html'], whitepaperHandler);
|
|
250
|
-
|
|
251
|
-
// WAB Trust artifact (signed Ed25519 wab.json) — served explicitly because
|
|
252
|
-
// express.static skips dotfile directories like /.well-known by default.
|
|
253
|
-
app.get('/.well-known/wab.json', (req, res) => {
|
|
254
|
-
res.set('Cache-Control', 'public, max-age=300');
|
|
255
|
-
res.set('Access-Control-Allow-Origin', '*');
|
|
256
|
-
res.type('application/json');
|
|
257
|
-
res.sendFile(path.join(__dirname, '..', 'public', '.well-known', 'wab.json'));
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
app.use(express.static(path.join(__dirname, '..', 'public'), {
|
|
261
|
-
setHeaders(res, filePath) {
|
|
262
|
-
if (filePath.endsWith('.html')) {
|
|
263
|
-
res.setHeader('Cache-Control', 'no-cache, must-revalidate');
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}));
|
|
267
|
-
app.use('/script', express.static(path.join(__dirname, '..', 'script')));
|
|
268
|
-
|
|
269
|
-
app.use('/api/auth', apiLimiter, authRoutes);
|
|
270
|
-
app.use('/api', apiLimiter, apiRoutes);
|
|
271
|
-
app.use('/api/license', licenseLimiter, licenseRoutes);
|
|
272
|
-
app.use('/api/admin', apiLimiter, adminRoutes);
|
|
273
|
-
app.use('/api/billing', apiLimiter, billingRoutes);
|
|
274
|
-
app.use('/api/sovereign', apiLimiter, sovereignRoutes);
|
|
275
|
-
app.use('/api/mesh', apiLimiter, meshRoutes);
|
|
276
|
-
app.use('/api/commander', apiLimiter, commanderRoutes);
|
|
277
|
-
app.use('/api/ads', apiLimiter, adsRoutes);
|
|
278
|
-
app.use('/api/wab', wabApiRoutes);
|
|
279
|
-
app.use('/api/noscript', apiLimiter, noscriptRoutes);
|
|
280
|
-
app.use('/api/discovery', apiLimiter, discoveryRoutes);
|
|
281
|
-
app.use('/api/
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
app.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
app.
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
app.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
app.
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
app.
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
app.
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
app.use('/api/
|
|
340
|
-
app.use('/api/
|
|
341
|
-
app.use('/api/
|
|
342
|
-
app.use('/
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
app.get('/
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
})
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
//
|
|
405
|
-
|
|
406
|
-
res.
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
app.
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
app.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
app.
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
app.get('/
|
|
469
|
-
res.
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
app.
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
app.
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
1
|
+
require('dotenv').config();
|
|
2
|
+
|
|
3
|
+
const { assertSecretsAtStartup } = require('./config/secrets');
|
|
4
|
+
assertSecretsAtStartup();
|
|
5
|
+
|
|
6
|
+
const express = require('express');
|
|
7
|
+
const http = require('http');
|
|
8
|
+
const cors = require('cors');
|
|
9
|
+
const helmet = require('helmet');
|
|
10
|
+
const rateLimit = require('express-rate-limit');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { setupWebSocket } = require('./ws');
|
|
13
|
+
const { runMigrations } = require('./utils/migrate');
|
|
14
|
+
const { maybeBootstrapAdmin, db } = require('./models/db');
|
|
15
|
+
const { initSearchEngine, search, getSuggestions, getTrendingSearches, getSearchStats, purgeOldCache } = require('./services/search-engine');
|
|
16
|
+
const { processMessage: agentChat } = require('./services/agent-chat');
|
|
17
|
+
const agentTasks = require('./services/agent-tasks');
|
|
18
|
+
const { cluster } = require('./services/cluster');
|
|
19
|
+
|
|
20
|
+
const authRoutes = require('./routes/auth');
|
|
21
|
+
const apiRoutes = require('./routes/api');
|
|
22
|
+
const licenseRoutes = require('./routes/license');
|
|
23
|
+
const adminRoutes = require('./routes/admin');
|
|
24
|
+
const billingRoutes = require('./routes/billing');
|
|
25
|
+
const sovereignRoutes = require('./routes/sovereign');
|
|
26
|
+
const meshRoutes = require('./routes/mesh');
|
|
27
|
+
const commanderRoutes = require('./routes/commander');
|
|
28
|
+
const adsRoutes = require('./routes/ads');
|
|
29
|
+
const wabApiRoutes = require('./routes/wab-api');
|
|
30
|
+
const noscriptRoutes = require('./routes/noscript');
|
|
31
|
+
const discoveryRoutes = require('./routes/discovery');
|
|
32
|
+
const providerRoutes = require('./routes/providers');
|
|
33
|
+
const governanceRoutes = require('./routes/governance');
|
|
34
|
+
const premiumRoutes = require('./routes/premium');
|
|
35
|
+
const adminPremiumRoutes = require('./routes/admin-premium');
|
|
36
|
+
const workspaceRoutes = require('./routes/agent-workspace');
|
|
37
|
+
const universalRoutes = require('./routes/universal');
|
|
38
|
+
const runtimeRoutes = require('./routes/runtime');
|
|
39
|
+
const demoShowcaseRoutes = require('./routes/demo-showcase');
|
|
40
|
+
const demoStoreRoutes = require('./routes/demo-store');
|
|
41
|
+
const gatewayRoutes = require('./routes/gateway');
|
|
42
|
+
let growthRoutes;
|
|
43
|
+
try { growthRoutes = require('./routes/growth'); } catch { growthRoutes = require('express').Router(); }
|
|
44
|
+
const { handleWebhookRequest } = require('./services/stripe');
|
|
45
|
+
const { runtime } = require('./runtime');
|
|
46
|
+
|
|
47
|
+
const app = express();
|
|
48
|
+
const PORT = process.env.PORT || 3000;
|
|
49
|
+
|
|
50
|
+
app.set('trust proxy', 1);
|
|
51
|
+
|
|
52
|
+
const corsOrigins = (process.env.ALLOWED_ORIGINS
|
|
53
|
+
|| 'http://localhost:3000,http://127.0.0.1:3000,http://localhost:5173')
|
|
54
|
+
.split(',')
|
|
55
|
+
.map((s) => s.trim())
|
|
56
|
+
.filter(Boolean);
|
|
57
|
+
|
|
58
|
+
app.use(
|
|
59
|
+
cors({
|
|
60
|
+
origin(origin, callback) {
|
|
61
|
+
if (!origin) return callback(null, true);
|
|
62
|
+
if (corsOrigins.includes(origin)) return callback(null, true);
|
|
63
|
+
if (process.env.NODE_ENV !== 'production' && /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/i.test(origin)) {
|
|
64
|
+
return callback(null, true);
|
|
65
|
+
}
|
|
66
|
+
return callback(null, false);
|
|
67
|
+
},
|
|
68
|
+
credentials: true
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const scriptSrc = process.env.CSP_ALLOW_UNSAFE_INLINE === 'false'
|
|
73
|
+
? ["'self'", 'https://unpkg.com', 'https://cdn.jsdelivr.net']
|
|
74
|
+
: ["'self'", "'unsafe-inline'", 'https://unpkg.com', 'https://cdn.jsdelivr.net'];
|
|
75
|
+
const styleSrc = process.env.CSP_ALLOW_UNSAFE_INLINE === 'false'
|
|
76
|
+
? ["'self'"]
|
|
77
|
+
: ["'self'", "'unsafe-inline'"];
|
|
78
|
+
|
|
79
|
+
// Per-request CSP nonce — exposed as res.locals.cspNonce for new pages opting into strict CSP.
|
|
80
|
+
app.use((req, res, next) => {
|
|
81
|
+
res.locals.cspNonce = require('crypto').randomBytes(16).toString('base64');
|
|
82
|
+
next();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// CSP — tightened: HTTPS-only iframes, upgrade-insecure-requests, report endpoint.
|
|
86
|
+
const cspReportUri = '/api/security/csp-report';
|
|
87
|
+
app.use(
|
|
88
|
+
helmet({
|
|
89
|
+
contentSecurityPolicy: {
|
|
90
|
+
directives: {
|
|
91
|
+
defaultSrc: ["'self'"],
|
|
92
|
+
// NOTE: Adding a nonce alongside 'unsafe-inline' makes browsers ignore
|
|
93
|
+
// 'unsafe-inline' (CSP3 spec). All existing public/admin pages still
|
|
94
|
+
// rely on inline <script> blocks, so we keep 'unsafe-inline' enforced
|
|
95
|
+
// here and use the Report-Only policy below to track nonce migration.
|
|
96
|
+
scriptSrc: scriptSrc,
|
|
97
|
+
scriptSrcAttr: [...scriptSrc, "'unsafe-hashes'"],
|
|
98
|
+
styleSrc: [...styleSrc, 'https://fonts.googleapis.com'],
|
|
99
|
+
imgSrc: ["'self'", 'data:', 'https:'],
|
|
100
|
+
connectSrc: ["'self'", 'https:', 'ws:', 'wss:'],
|
|
101
|
+
fontSrc: ["'self'", 'https://fonts.gstatic.com', 'https:', 'data:'],
|
|
102
|
+
frameSrc: ["'self'", 'https:'],
|
|
103
|
+
frameAncestors: ["'none'"],
|
|
104
|
+
objectSrc: ["'none'"],
|
|
105
|
+
baseUri: ["'self'"],
|
|
106
|
+
formAction: ["'self'"],
|
|
107
|
+
upgradeInsecureRequests: process.env.NODE_ENV === 'production' ? [] : null,
|
|
108
|
+
reportUri: [cspReportUri]
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
crossOriginEmbedderPolicy: false,
|
|
112
|
+
referrerPolicy: { policy: 'strict-origin-when-cross-origin' }
|
|
113
|
+
})
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Companion strict Report-Only CSP — surfaces every inline-script violation
|
|
117
|
+
// without breaking existing pages, so we can migrate page-by-page to nonces.
|
|
118
|
+
app.use((req, res, next) => {
|
|
119
|
+
const nonce = res.locals.cspNonce;
|
|
120
|
+
const strict = [
|
|
121
|
+
"default-src 'self'",
|
|
122
|
+
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
|
|
123
|
+
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
|
|
124
|
+
"img-src 'self' data: https:",
|
|
125
|
+
"connect-src 'self' https: wss:",
|
|
126
|
+
"font-src 'self' https://fonts.gstatic.com data:",
|
|
127
|
+
"frame-src 'self' https:",
|
|
128
|
+
"frame-ancestors 'none'",
|
|
129
|
+
"object-src 'none'",
|
|
130
|
+
"base-uri 'self'",
|
|
131
|
+
"form-action 'self'",
|
|
132
|
+
"upgrade-insecure-requests",
|
|
133
|
+
`report-uri ${cspReportUri}`
|
|
134
|
+
].join('; ');
|
|
135
|
+
res.setHeader('Content-Security-Policy-Report-Only', strict);
|
|
136
|
+
next();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// CSP violation report sink (capped, in-memory ring buffer + console).
|
|
140
|
+
const _cspReports = [];
|
|
141
|
+
app.post('/api/security/csp-report', express.json({ type: ['application/csp-report', 'application/json'], limit: '32kb' }), (req, res) => {
|
|
142
|
+
const report = req.body && (req.body['csp-report'] || req.body);
|
|
143
|
+
if (report) {
|
|
144
|
+
_cspReports.push({ at: new Date().toISOString(), ip: req.ip, report });
|
|
145
|
+
if (_cspReports.length > 500) _cspReports.shift();
|
|
146
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
147
|
+
console.warn('[CSP]', report['violated-directive'] || report.violatedDirective, '→', report['blocked-uri'] || report.blockedURI);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
res.status(204).end();
|
|
151
|
+
});
|
|
152
|
+
app.get('/api/security/csp-report/recent', (req, res) => {
|
|
153
|
+
res.json({ count: _cspReports.length, reports: _cspReports.slice(-50) });
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// ── Reward-guard + cross-site redactor admin views (token-gated) ──
|
|
157
|
+
function _adminAuth(req, res, next) {
|
|
158
|
+
const want = process.env.WAB_ADMIN_TOKEN;
|
|
159
|
+
if (!want) return res.status(503).json({ error: 'WAB_ADMIN_TOKEN not configured' });
|
|
160
|
+
const got = req.headers['x-wab-admin-token'] || req.query.token;
|
|
161
|
+
if (got !== want) return res.status(401).json({ error: 'admin token required' });
|
|
162
|
+
next();
|
|
163
|
+
}
|
|
164
|
+
app.get('/api/security/reward-audit/recent', _adminAuth, (req, res) => {
|
|
165
|
+
try {
|
|
166
|
+
const guard = require('./security/reward-guard');
|
|
167
|
+
res.json({ stats: guard.getStats(), recent: guard.getRecentAudits(50, req.query.decision || null) });
|
|
168
|
+
} catch (err) { res.status(500).json({ error: err.message }); }
|
|
169
|
+
});
|
|
170
|
+
app.get('/api/security/cross-site-transfers/recent', _adminAuth, (req, res) => {
|
|
171
|
+
try {
|
|
172
|
+
const r = require('./security/cross-site-redactor');
|
|
173
|
+
res.json({ recent: r.getRecentTransfers(50, req.query.from || null) });
|
|
174
|
+
} catch (err) { res.status(500).json({ error: err.message }); }
|
|
175
|
+
});
|
|
176
|
+
app.get('/api/security/url-policy/recent', _adminAuth, (req, res) => {
|
|
177
|
+
try {
|
|
178
|
+
const p = require('./security/url-policy');
|
|
179
|
+
res.json({ recent: p.getRecentAudits(50, req.query.decision || null) });
|
|
180
|
+
} catch (err) { res.status(500).json({ error: err.message }); }
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
app.post('/api/billing/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
|
|
184
|
+
try {
|
|
185
|
+
await handleWebhookRequest(req);
|
|
186
|
+
res.json({ received: true });
|
|
187
|
+
} catch (err) {
|
|
188
|
+
console.error('Webhook error:', err.message);
|
|
189
|
+
res.status(400).json({ error: err.message });
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
app.use(express.json());
|
|
194
|
+
|
|
195
|
+
// Global JSON parse error handler (catches malformed JSON from bots/scanners)
|
|
196
|
+
app.use((err, req, res, next) => {
|
|
197
|
+
if (err.type === 'entity.parse.failed' || err instanceof SyntaxError) {
|
|
198
|
+
return res.status(400).json({ error: 'Invalid JSON', details: err.message });
|
|
199
|
+
}
|
|
200
|
+
next(err);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Global error handler — catches all unhandled route errors
|
|
204
|
+
// global-error-handler
|
|
205
|
+
app.use((err, req, res, next) => {
|
|
206
|
+
const status = err.status || err.statusCode || 500;
|
|
207
|
+
const message = err.message || 'Internal Server Error';
|
|
208
|
+
if (status >= 500) {
|
|
209
|
+
console.error('[server] Unhandled error:', err.message, err.stack?.split('\n')[1] || '');
|
|
210
|
+
}
|
|
211
|
+
if (!res.headersSent) {
|
|
212
|
+
res.status(status).json({ error: message });
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const apiLimiter = rateLimit({
|
|
217
|
+
windowMs: 15 * 60 * 1000,
|
|
218
|
+
max: 200,
|
|
219
|
+
standardHeaders: true,
|
|
220
|
+
legacyHeaders: false,
|
|
221
|
+
message: { error: 'Too many requests, please try again later' }
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const licenseLimiter = rateLimit({
|
|
225
|
+
windowMs: 60 * 1000,
|
|
226
|
+
max: 120,
|
|
227
|
+
standardHeaders: true,
|
|
228
|
+
legacyHeaders: false,
|
|
229
|
+
keyGenerator: (req) => {
|
|
230
|
+
const key = req.body?.licenseKey || req.body?.siteId || req.ip;
|
|
231
|
+
return `${req.ip}:${key}`;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Whitepaper guard — must run BEFORE express.static so we can apply strict headers
|
|
236
|
+
// and intercept both /whitepaper and /whitepaper.html with the same protections.
|
|
237
|
+
const whitepaperHandler = (req, res) => {
|
|
238
|
+
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
|
|
239
|
+
res.set('Pragma', 'no-cache');
|
|
240
|
+
res.set('Expires', '0');
|
|
241
|
+
res.set('X-Frame-Options', 'DENY');
|
|
242
|
+
res.set('X-Content-Type-Options', 'nosniff');
|
|
243
|
+
res.set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
244
|
+
res.set('X-Robots-Tag', 'index, follow, noarchive, nosnippet, noimageindex');
|
|
245
|
+
res.set('Content-Security-Policy', "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'");
|
|
246
|
+
res.set('X-Copyright', 'All Rights Reserved (c) 2026 Web Agent Bridge - Reproduction Prohibited');
|
|
247
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'whitepaper.html'));
|
|
248
|
+
};
|
|
249
|
+
app.get(['/whitepaper', '/whitepaper.html'], whitepaperHandler);
|
|
250
|
+
|
|
251
|
+
// WAB Trust artifact (signed Ed25519 wab.json) — served explicitly because
|
|
252
|
+
// express.static skips dotfile directories like /.well-known by default.
|
|
253
|
+
app.get('/.well-known/wab.json', (req, res) => {
|
|
254
|
+
res.set('Cache-Control', 'public, max-age=300');
|
|
255
|
+
res.set('Access-Control-Allow-Origin', '*');
|
|
256
|
+
res.type('application/json');
|
|
257
|
+
res.sendFile(path.join(__dirname, '..', 'public', '.well-known', 'wab.json'));
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
app.use(express.static(path.join(__dirname, '..', 'public'), {
|
|
261
|
+
setHeaders(res, filePath) {
|
|
262
|
+
if (filePath.endsWith('.html')) {
|
|
263
|
+
res.setHeader('Cache-Control', 'no-cache, must-revalidate');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}));
|
|
267
|
+
app.use('/script', express.static(path.join(__dirname, '..', 'script')));
|
|
268
|
+
|
|
269
|
+
app.use('/api/auth', apiLimiter, authRoutes);
|
|
270
|
+
app.use('/api', apiLimiter, apiRoutes);
|
|
271
|
+
app.use('/api/license', licenseLimiter, licenseRoutes);
|
|
272
|
+
app.use('/api/admin', apiLimiter, adminRoutes);
|
|
273
|
+
app.use('/api/billing', apiLimiter, billingRoutes);
|
|
274
|
+
app.use('/api/sovereign', apiLimiter, sovereignRoutes);
|
|
275
|
+
app.use('/api/mesh', apiLimiter, meshRoutes);
|
|
276
|
+
app.use('/api/commander', apiLimiter, commanderRoutes);
|
|
277
|
+
app.use('/api/ads', apiLimiter, adsRoutes);
|
|
278
|
+
app.use('/api/wab', wabApiRoutes);
|
|
279
|
+
app.use('/api/noscript', apiLimiter, noscriptRoutes);
|
|
280
|
+
app.use('/api/discovery', apiLimiter, discoveryRoutes);
|
|
281
|
+
app.use('/api/activate', apiLimiter, require('./routes/activate'));
|
|
282
|
+
|
|
283
|
+
// ── WAB Advanced Features v1.0 ──────────────────────────────────────────────
|
|
284
|
+
const { reputationRouter, collectiveRouter } = require('./routes/reputation');
|
|
285
|
+
const { intentRouter, privacyRouter } = require('./routes/intent');
|
|
286
|
+
const { cacheRouter, offlineRouter } = require('./routes/wab-cache');
|
|
287
|
+
// Trust Graph tier gate — tags & meters anonymous + keyed traffic.
|
|
288
|
+
// Mounted BEFORE the routers so it sees their requests.
|
|
289
|
+
const { apiTierMiddleware } = require('./middleware/api-tier');
|
|
290
|
+
app.use(['/api/reputation', '/api/truth', '/api/ring4/status'], apiTierMiddleware);
|
|
291
|
+
app.use('/api/reputation', apiLimiter, reputationRouter);
|
|
292
|
+
app.use('/api/collective', apiLimiter, collectiveRouter);
|
|
293
|
+
app.use('/api/intent', apiLimiter, intentRouter);
|
|
294
|
+
app.use('/api/privacy', apiLimiter, privacyRouter);
|
|
295
|
+
app.use('/api/cache', apiLimiter, cacheRouter);
|
|
296
|
+
app.use('/api/offline', apiLimiter, offlineRouter);
|
|
297
|
+
|
|
298
|
+
// ── WAB Truth Layer v1.0 (Semantic Memory + Temporal Trust + Action Graphs + Reality Anchor) ──
|
|
299
|
+
const { truthRouter } = require('./routes/truth-layer');
|
|
300
|
+
app.use('/api/truth', apiLimiter, truthRouter);
|
|
301
|
+
|
|
302
|
+
// ── WAB Ring 4 External Trust Verification (sovereign-agent trust API) ──
|
|
303
|
+
const { ring4Router } = require('./routes/ring4');
|
|
304
|
+
const { wabTrustMiddleware } = require('./middleware/wab-trust');
|
|
305
|
+
app.use(wabTrustMiddleware);
|
|
306
|
+
app.use('/api/ring4', apiLimiter, ring4Router);
|
|
307
|
+
|
|
308
|
+
// ── Agent Transaction Primitive (ATP) v3.9.0 — intents · transactions · signed receipts ──
|
|
309
|
+
app.use('/api/atp', apiLimiter, require('./routes/transactions'));
|
|
310
|
+
|
|
311
|
+
// ── WAB Commercial Foundations v3.8.0 (Partners · Trust Graph API · Governance SaaS · Enterprise Mesh) ──
|
|
312
|
+
app.use('/api/partners', apiLimiter, require('./routes/partners'));
|
|
313
|
+
app.use('/api/keys', apiLimiter, require('./routes/api-keys'));
|
|
314
|
+
app.use('/api/governance-saas', apiLimiter, require('./routes/governance-saas'));
|
|
315
|
+
app.use('/api/enterprise-mesh', apiLimiter, require('./routes/enterprise-mesh'));
|
|
316
|
+
// Trust Graph tier gate is mounted earlier (before /api/reputation et al.)
|
|
317
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
app.use('/api/providers', apiLimiter, providerRoutes);
|
|
320
|
+
app.use('/api/governance', apiLimiter, governanceRoutes);
|
|
321
|
+
app.use('/api/plans', apiLimiter, require('./routes/plans'));
|
|
322
|
+
app.use('/api/admin/plans', apiLimiter, require('./routes/admin-plans'));
|
|
323
|
+
app.use('/api/admin/shieldqr', apiLimiter, require('./routes/admin-shieldqr'));
|
|
324
|
+
app.use('/api/admin/trust-monitor', apiLimiter, require('./routes/admin-trust-monitor'));
|
|
325
|
+
// Optional premium modules — mounted only when present (open-source repo
|
|
326
|
+
// excludes the ShieldLink stack which is a paid feature).
|
|
327
|
+
function mountOptional(prefix, modPath) {
|
|
328
|
+
try { app.use(prefix, apiLimiter, require(modPath)); }
|
|
329
|
+
catch (e) {
|
|
330
|
+
if (e.code === 'MODULE_NOT_FOUND' && e.message.includes(modPath)) {
|
|
331
|
+
console.log(`[optional] ${prefix} not mounted (${modPath} not present)`);
|
|
332
|
+
} else { throw e; }
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
mountOptional('/api/admin/shieldlink', './routes/admin-shieldlink');
|
|
336
|
+
app.use('/api/shieldqr', apiLimiter, require('./routes/shieldqr'));
|
|
337
|
+
mountOptional('/api/shieldlink', './routes/shieldlink');
|
|
338
|
+
mountOptional('/api/customer/shieldlink','./routes/customer-shieldlink');
|
|
339
|
+
app.use('/api/adopt', apiLimiter, require('./routes/adopt'));
|
|
340
|
+
app.use('/api/diagnose', apiLimiter, require('./routes/diagnose'));
|
|
341
|
+
app.use('/api/admin/outreach', apiLimiter, require('./routes/admin-outreach'));
|
|
342
|
+
app.use('/', apiLimiter, require('./routes/unsubscribe'));
|
|
343
|
+
// Also expose well-known discovery endpoints at the canonical root paths so
|
|
344
|
+
// agents can find them without the /api/discovery prefix (RFC 8615).
|
|
345
|
+
|
|
346
|
+
// /activate — WAB DNS Discovery activation guide (bilingual)
|
|
347
|
+
app.get('/activate', noCache, (req, res) => {
|
|
348
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'activate.html'));
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// /one-click — interactive self-serve activation wizard (key-gen, sign, deploy via API)
|
|
352
|
+
app.get(['/one-click', '/one-click.html', '/activate/one-click'], noCache, (req, res) => {
|
|
353
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'one-click.html'));
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// /wab-features — WAB Advanced Features showcase (Reputation, Cache, Intent, Privacy, Collective, Offline)
|
|
357
|
+
app.get(['/wab-features', '/features'], noCache, (req, res) => {
|
|
358
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'wab-features.html'));
|
|
359
|
+
});
|
|
360
|
+
// /wab-truth — WAB Truth Layer showcase (Semantic Memory + Temporal Trust + Action Graphs + Reality Anchor)
|
|
361
|
+
app.get(['/wab-truth', '/truth'], noCache, (req, res) => {
|
|
362
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'wab-truth.html'));
|
|
363
|
+
});
|
|
364
|
+
// /milestones — Partners & Milestones (VEXR Ultra × WAB Ring 4 integration)
|
|
365
|
+
app.get(['/milestones'], noCache, (req, res) => {
|
|
366
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'milestones.html'));
|
|
367
|
+
});
|
|
368
|
+
// /partners — Certified Partner Program (3 tiers · self-serve)
|
|
369
|
+
app.get(['/partners', '/partners.html'], noCache, (req, res) => {
|
|
370
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'partners.html'));
|
|
371
|
+
});
|
|
372
|
+
// /trust-graph-api — Trust Graph API docs & self-serve key issuance
|
|
373
|
+
app.get(['/trust-graph-api', '/trust-graph-api.html'], noCache, (req, res) => {
|
|
374
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'trust-graph-api.html'));
|
|
375
|
+
});
|
|
376
|
+
// /governance — Governance SaaS landing (EU AI Act audit trail)
|
|
377
|
+
app.get(['/governance', '/governance.html'], noCache, (req, res) => {
|
|
378
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'governance.html'));
|
|
379
|
+
});
|
|
380
|
+
// /enterprise-mesh — Self-hosted Enterprise Mesh contact
|
|
381
|
+
app.get(['/enterprise-mesh', '/enterprise-mesh.html', '/enterprise'], noCache, (req, res) => {
|
|
382
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'enterprise-mesh.html'));
|
|
383
|
+
});
|
|
384
|
+
// /ring4 — Ring 4 Trust Handshake protocol docs
|
|
385
|
+
app.get(['/ring4', '/trust-handshake'], noCache, (req, res) => {
|
|
386
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'ring4.html'));
|
|
387
|
+
});
|
|
388
|
+
// /refusals — Public refusal log (anonymized constitutional refusal stats)
|
|
389
|
+
app.get('/refusals', noCache, (req, res) => {
|
|
390
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'refusals.html'));
|
|
391
|
+
});
|
|
392
|
+
// /.well-known/jwks.json — standard JWKS discovery for OIDC/JWT ecosystem
|
|
393
|
+
app.get('/.well-known/jwks.json', (req, res) => {
|
|
394
|
+
try {
|
|
395
|
+
const { _internals } = require('./routes/ring4');
|
|
396
|
+
return res.json(_internals.buildJwks());
|
|
397
|
+
} catch (e) {
|
|
398
|
+
return res.status(503).json({ error: 'jwks_unavailable', detail: e.message });
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
app.get('/shieldqr', noCache, (req, res) => {
|
|
402
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'shieldqr.html'));
|
|
403
|
+
});
|
|
404
|
+
// ── ShieldLink landing + Trust Preview redirect ──
|
|
405
|
+
app.get('/shieldlink', noCache, (req, res) => {
|
|
406
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'shieldlink.html'));
|
|
407
|
+
});
|
|
408
|
+
app.get('/l/:token', noCache, (req, res) => {
|
|
409
|
+
// Serve the Trust Preview page; the page calls /api/shieldlink/verify?token=
|
|
410
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'l-preview.html'));
|
|
411
|
+
});
|
|
412
|
+
app.get('/dashboard/shieldlink', noCache, (req, res) => {
|
|
413
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'dashboard-shieldlink.html'));
|
|
414
|
+
});
|
|
415
|
+
app.get('/activate-dns', noCache, (req, res) => {
|
|
416
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'activate.html'));
|
|
417
|
+
});
|
|
418
|
+
app.get('/provider-onboarding', noCache, (req, res) => {
|
|
419
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'provider-onboarding.html'));
|
|
420
|
+
});
|
|
421
|
+
app.get('/provider-sandbox', noCache, (req, res) => {
|
|
422
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'provider-sandbox.html'));
|
|
423
|
+
});
|
|
424
|
+
app.get('/cloudflare-integration', noCache, (req, res) => {
|
|
425
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'cloudflare-integration.html'));
|
|
426
|
+
});
|
|
427
|
+
app.get('/cpanel-integration', noCache, (req, res) => {
|
|
428
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'cpanel-integration.html'));
|
|
429
|
+
});
|
|
430
|
+
app.get('/route53-integration', noCache, (req, res) => {
|
|
431
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'route53-integration.html'));
|
|
432
|
+
});
|
|
433
|
+
app.get('/plesk-integration', noCache, (req, res) => {
|
|
434
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'plesk-integration.html'));
|
|
435
|
+
});
|
|
436
|
+
app.get('/gcp-dns-integration', noCache, (req, res) => {
|
|
437
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'gcp-dns-integration.html'));
|
|
438
|
+
});
|
|
439
|
+
app.get('/azure-dns-integration', noCache, (req, res) => {
|
|
440
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'azure-dns-integration.html'));
|
|
441
|
+
});
|
|
442
|
+
app.get('/registrar-integrations', noCache, (req, res) => {
|
|
443
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'registrar-integrations.html'));
|
|
444
|
+
});
|
|
445
|
+
app.get('/adoption-metrics', noCache, (req, res) => {
|
|
446
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'adoption-metrics.html'));
|
|
447
|
+
});
|
|
448
|
+
app.get('/adopt', noCache, (req, res) => {
|
|
449
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'adopt.html'));
|
|
450
|
+
});
|
|
451
|
+
app.get('/wab-trust', noCache, (req, res) => {
|
|
452
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'wab-trust.html'));
|
|
453
|
+
});
|
|
454
|
+
app.get('/wab-vs-protocols', noCache, (req, res) => {
|
|
455
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'wab-vs-protocols.html'));
|
|
456
|
+
});
|
|
457
|
+
app.use('/', apiLimiter, discoveryRoutes);
|
|
458
|
+
app.use('/api/premium', apiLimiter, premiumRoutes);
|
|
459
|
+
app.use('/api/admin/premium', apiLimiter, adminPremiumRoutes);
|
|
460
|
+
app.use('/api/workspace', apiLimiter, workspaceRoutes);
|
|
461
|
+
app.use('/api/universal', apiLimiter, universalRoutes);
|
|
462
|
+
app.use('/api/os', apiLimiter, runtimeRoutes);
|
|
463
|
+
app.use('/api/demo', apiLimiter, demoShowcaseRoutes);
|
|
464
|
+
app.use('/api/growth', apiLimiter, growthRoutes);
|
|
465
|
+
app.use('/api/v1', gatewayRoutes);
|
|
466
|
+
|
|
467
|
+
// Convenience alias: /api/negotiate/* → /api/sovereign/negotiation/*
|
|
468
|
+
app.get('/api/negotiate', apiLimiter, (req, res) => {
|
|
469
|
+
res.json({
|
|
470
|
+
engine: 'WAB Negotiation Engine',
|
|
471
|
+
endpoints: {
|
|
472
|
+
'POST /api/negotiate/rules': 'Create negotiation rules (auth required)',
|
|
473
|
+
'GET /api/negotiate/rules/:siteId': 'Get rules for a site',
|
|
474
|
+
'PUT /api/negotiate/rules/:ruleId': 'Update a rule (auth required)',
|
|
475
|
+
'POST /api/negotiate/sessions': 'Open negotiation session',
|
|
476
|
+
'POST /api/negotiate/sessions/:id/propose': 'Agent counter-offer',
|
|
477
|
+
'POST /api/negotiate/sessions/:id/confirm': 'Confirm deal',
|
|
478
|
+
'GET /api/negotiate/stats/:siteId': 'Negotiation stats',
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
app.use('/api/negotiate', apiLimiter, (req, res, next) => {
|
|
483
|
+
req.url = '/negotiation' + req.url;
|
|
484
|
+
sovereignRoutes(req, res, next);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// ─── WAB Search Engine ────────────────────────────────────────────────
|
|
488
|
+
|
|
489
|
+
const searchLimiter = rateLimit({
|
|
490
|
+
windowMs: 60 * 1000,
|
|
491
|
+
max: 30,
|
|
492
|
+
standardHeaders: true,
|
|
493
|
+
legacyHeaders: false,
|
|
494
|
+
message: { error: 'Too many search requests, please slow down' }
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
app.get('/api/search', searchLimiter, async (req, res) => {
|
|
498
|
+
const q = (req.query.q || '').trim();
|
|
499
|
+
if (!q) return res.json({ results: [], cached: false });
|
|
500
|
+
if (q.length > 200) return res.status(400).json({ error: 'Query too long' });
|
|
501
|
+
const crypto = require('crypto');
|
|
502
|
+
const ipHash = crypto.createHash('sha256').update(req.ip || '').digest('hex').slice(0, 16);
|
|
503
|
+
const result = await search(q, ipHash);
|
|
504
|
+
res.json(result);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
app.get('/api/search/suggest', searchLimiter, (req, res) => {
|
|
508
|
+
const q = (req.query.q || '').trim();
|
|
509
|
+
if (!q) return res.json({ suggestions: [] });
|
|
510
|
+
const suggestions = getSuggestions(q, 8);
|
|
511
|
+
res.json({ suggestions });
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
app.get('/api/search/trending', apiLimiter, (req, res) => {
|
|
515
|
+
const trending = getTrendingSearches(10);
|
|
516
|
+
res.json({ trending });
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
app.get('/api/search/stats', apiLimiter, (req, res) => {
|
|
520
|
+
const stats = getSearchStats();
|
|
521
|
+
res.json(stats);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
// Prevent browsers from caching HTML page routes
|
|
525
|
+
function noCache(req, res, next) {
|
|
526
|
+
res.set('Cache-Control', 'no-cache, must-revalidate');
|
|
527
|
+
next();
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
app.get('/dashboard', noCache, (req, res) => {
|
|
531
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'dashboard.html'));
|
|
532
|
+
});
|
|
533
|
+
app.get('/providers', noCache, (req, res) => {
|
|
534
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'providers.html'));
|
|
535
|
+
});
|
|
536
|
+
app.get('/mesh-dashboard', noCache, (req, res) => {
|
|
537
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'mesh-dashboard.html'));
|
|
538
|
+
});
|
|
539
|
+
app.get('/commander-dashboard', noCache, (req, res) => {
|
|
540
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'commander-dashboard.html'));
|
|
541
|
+
});
|
|
542
|
+
app.get('/docs', noCache, (req, res) => {
|
|
543
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'docs.html'));
|
|
544
|
+
});
|
|
545
|
+
app.get('/login', noCache, (req, res) => {
|
|
546
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'login.html'));
|
|
547
|
+
});
|
|
548
|
+
app.get('/register', noCache, (req, res) => {
|
|
549
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'register.html'));
|
|
550
|
+
});
|
|
551
|
+
app.get('/admin/login', noCache, (req, res) => {
|
|
552
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'admin', 'login.html'));
|
|
553
|
+
});
|
|
554
|
+
app.get('/admin', noCache, (req, res) => {
|
|
555
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'admin', 'dashboard.html'));
|
|
556
|
+
});
|
|
557
|
+
app.get('/admin/snapshots', noCache, (req, res) => {
|
|
558
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'admin', 'snapshots.html'));
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// ─── Admin sub-pages (each backed by real API endpoints in /api/admin/*) ──
|
|
562
|
+
['users','sites','analytics','grants','payments','stripe','smtp','notifications','governance','discovery','trust','providers','plans','shieldqr','shieldlink','trust-monitor','outreach'].forEach((page) => {
|
|
563
|
+
app.get('/admin/' + page, noCache, (req, res) => {
|
|
564
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'admin', page + '.html'));
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
app.get('/privacy', noCache, (req, res) => {
|
|
568
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'privacy.html'));
|
|
569
|
+
});
|
|
570
|
+
app.get('/terms', noCache, (req, res) => {
|
|
571
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'terms.html'));
|
|
572
|
+
});
|
|
573
|
+
app.get('/cookies', noCache, (req, res) => {
|
|
574
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'cookies.html'));
|
|
575
|
+
});
|
|
576
|
+
app.get('/browser', noCache, (req, res) => {
|
|
577
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'browser.html'));
|
|
578
|
+
});
|
|
579
|
+
app.get('/workspace', noCache, (req, res) => {
|
|
580
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'agent-workspace.html'));
|
|
581
|
+
});
|
|
582
|
+
app.get('/growth', noCache, (req, res) => {
|
|
583
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'growth.html'));
|
|
584
|
+
});
|
|
585
|
+
app.get('/score', noCache, (req, res) => {
|
|
586
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'score.html'));
|
|
587
|
+
});
|
|
588
|
+
app.get('/sovereign', noCache, (req, res) => {
|
|
589
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'sovereign.html'));
|
|
590
|
+
});
|
|
591
|
+
app.get('/api', noCache, (req, res) => {
|
|
592
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'api.html'));
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
app.get('/phone-shield', noCache, (req, res) => {
|
|
596
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'phone-shield.html'));
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
app.get('/dns', noCache, (req, res) => {
|
|
600
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'dns.html'));
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
// /integrations — bilingual deploy landing page
|
|
604
|
+
app.get('/integrations', noCache, (req, res) => {
|
|
605
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'integrations.html'));
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
// /demo — interactive WAB Demo Store (new)
|
|
609
|
+
app.use('/demo', demoStoreRoutes);
|
|
610
|
+
|
|
611
|
+
// Browser downloads
|
|
612
|
+
app.use('/downloads', express.static(path.join(__dirname, '..', 'downloads'), {
|
|
613
|
+
maxAge: '1d',
|
|
614
|
+
setHeaders: (res, filePath) => {
|
|
615
|
+
// Shell scripts served as plain text for curl | bash usage
|
|
616
|
+
if (filePath.endsWith('.sh')) {
|
|
617
|
+
res.set('Content-Type', 'text/plain; charset=utf-8');
|
|
618
|
+
} else {
|
|
619
|
+
res.set('Content-Disposition', 'attachment');
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}));
|
|
623
|
+
|
|
624
|
+
// WAB Discovery install shortcut: curl -fsSL https://webagentbridge.com/install | bash
|
|
625
|
+
app.get('/install', (req, res) => {
|
|
626
|
+
res.set('Content-Type', 'text/plain; charset=utf-8');
|
|
627
|
+
res.sendFile(path.join(__dirname, '..', 'downloads', 'quick-wab.sh'));
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
// Agent chat endpoint for WAB Browser — Real AI Agent
|
|
631
|
+
const chatLimiter = rateLimit({
|
|
632
|
+
windowMs: 60 * 1000,
|
|
633
|
+
max: 20,
|
|
634
|
+
standardHeaders: true,
|
|
635
|
+
legacyHeaders: false,
|
|
636
|
+
message: { error: 'Too many messages, please slow down' }
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
app.post('/api/wab/agent-chat', chatLimiter, async (req, res) => {
|
|
640
|
+
const { message, context, sessionId, taskId, taskAction } = req.body || {};
|
|
641
|
+
if (!message || typeof message !== 'string') {
|
|
642
|
+
return res.status(400).json({ error: 'Message required' });
|
|
643
|
+
}
|
|
644
|
+
if (message.length > 3000) {
|
|
645
|
+
return res.status(400).json({ error: 'Message too long' });
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const sid = sessionId || req.ip || 'anonymous';
|
|
649
|
+
|
|
650
|
+
try {
|
|
651
|
+
// ── Task actions (user responding to an active task) ──
|
|
652
|
+
if (taskId && taskAction) {
|
|
653
|
+
if (taskAction === 'answer') {
|
|
654
|
+
const result = agentTasks.answerClarification(taskId, message);
|
|
655
|
+
if (result.status === 'planning') {
|
|
656
|
+
// Auto-execute after planning
|
|
657
|
+
const execResult = await agentTasks.executeTask(taskId);
|
|
658
|
+
return res.json({ ...execResult, type: 'task' });
|
|
659
|
+
}
|
|
660
|
+
return res.json({ ...result, type: 'task' });
|
|
661
|
+
}
|
|
662
|
+
if (taskAction === 'select') {
|
|
663
|
+
const idx = parseInt(message.replace(/\D/g, '')) - 1;
|
|
664
|
+
const result = agentTasks.selectOffer(taskId, idx);
|
|
665
|
+
return res.json({ ...result, type: 'task' });
|
|
666
|
+
}
|
|
667
|
+
if (taskAction === 'cancel') {
|
|
668
|
+
const result = agentTasks.cancelTask(taskId);
|
|
669
|
+
return res.json({ ...result, type: 'task' });
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// ── Check if user wants to select from existing offers ──
|
|
674
|
+
if (!taskId) {
|
|
675
|
+
const selectMatch = message.match(/(?:اختر|اخت(?:ا|ي)ر|select|choose|pick)\s*(\d+)/i);
|
|
676
|
+
if (selectMatch) {
|
|
677
|
+
const tasks = agentTasks.getSessionTasks(sid, 1);
|
|
678
|
+
if (tasks.length > 0 && tasks[0].status === 'presenting') {
|
|
679
|
+
const idx = parseInt(selectMatch[1]) - 1;
|
|
680
|
+
const result = agentTasks.selectOffer(tasks[0].id, idx);
|
|
681
|
+
return res.json({ ...result, type: 'task' });
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// ── Detect URL paste — create URL negotiation task ──
|
|
687
|
+
const urlData = agentTasks.parseBookingUrl(message);
|
|
688
|
+
if (urlData) {
|
|
689
|
+
const task = agentTasks.createUrlTask(sid, message, urlData);
|
|
690
|
+
const execResult = await agentTasks.executeUrlTask(task.taskId);
|
|
691
|
+
return res.json({ ...execResult, type: 'task', urlData });
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// ── Detect if this is a task-type request (booking, shopping, etc.) ──
|
|
695
|
+
const intent = agentTasks.detectIntent(message);
|
|
696
|
+
if (intent.confidence >= 0.7 && intent.intent !== 'general') {
|
|
697
|
+
const task = agentTasks.createTask(sid, message);
|
|
698
|
+
|
|
699
|
+
if (task.status === 'clarifying') {
|
|
700
|
+
return res.json({ ...task, type: 'task' });
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// If requirements are complete, auto-execute
|
|
704
|
+
const execResult = await agentTasks.executeTask(task.taskId);
|
|
705
|
+
return res.json({ ...execResult, type: 'task' });
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// ── Regular chat (not a task) ──
|
|
709
|
+
const chatContext = {
|
|
710
|
+
url: context?.url || '',
|
|
711
|
+
platform: context?.platform || 'unknown',
|
|
712
|
+
sessionId: sid,
|
|
713
|
+
};
|
|
714
|
+
const result = await agentChat(message, chatContext);
|
|
715
|
+
res.json(result);
|
|
716
|
+
} catch (err) {
|
|
717
|
+
console.error('[agent-chat] Error:', err.message);
|
|
718
|
+
res.json({ reply: '🤖 عذراً، حدث خطأ. حاول مرة أخرى.', type: 'text' });
|
|
719
|
+
}
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
// Agent task status & history
|
|
723
|
+
app.get('/api/wab/agent-task/:id', chatLimiter, (req, res) => {
|
|
724
|
+
const state = agentTasks.getTaskState(req.params.id);
|
|
725
|
+
if (!state) return res.status(404).json({ error: 'Task not found' });
|
|
726
|
+
res.json(state);
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
app.get('/api/wab/agent-tasks', chatLimiter, (req, res) => {
|
|
730
|
+
const sid = req.query.sessionId || req.ip || 'anonymous';
|
|
731
|
+
const tasks = agentTasks.getSessionTasks(sid, 20);
|
|
732
|
+
res.json({ tasks });
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
const pkg = require('../package.json');
|
|
736
|
+
app.use(`/v${pkg.version.split('.')[0]}`, express.static(path.join(__dirname, '..', 'script')));
|
|
737
|
+
app.use('/latest', express.static(path.join(__dirname, '..', 'script')));
|
|
738
|
+
|
|
739
|
+
app.get('*', (req, res) => {
|
|
740
|
+
// API routes always return JSON 404
|
|
741
|
+
if (req.path.startsWith('/api/')) {
|
|
742
|
+
return res.status(404).json({ error: 'Not found', path: req.path });
|
|
743
|
+
}
|
|
744
|
+
if (req.accepts('html')) {
|
|
745
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));
|
|
746
|
+
} else {
|
|
747
|
+
res.status(404).json({ error: 'Not found' });
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
// Prevent PM2 restarts from uncaught errors — log and continue
|
|
753
|
+
process.on('uncaughtException', (err) => {
|
|
754
|
+
console.error('[process] uncaughtException:', err.message);
|
|
755
|
+
});
|
|
756
|
+
process.on('unhandledRejection', (reason) => {
|
|
757
|
+
console.error('[process] unhandledRejection:', reason?.message || reason);
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
761
|
+
console.log('Running database migrations...');
|
|
762
|
+
runMigrations();
|
|
763
|
+
maybeBootstrapAdmin();
|
|
764
|
+
initSearchEngine(db);
|
|
765
|
+
|
|
766
|
+
// Purge old search cache every hour
|
|
767
|
+
setInterval(purgeOldCache, 60 * 60 * 1000);
|
|
768
|
+
|
|
769
|
+
const server = http.createServer(app);
|
|
770
|
+
setupWebSocket(server);
|
|
771
|
+
|
|
772
|
+
// Start Agent OS runtime
|
|
773
|
+
runtime.start();
|
|
774
|
+
|
|
775
|
+
// Start Cluster Orchestrator
|
|
776
|
+
cluster.start();
|
|
777
|
+
|
|
778
|
+
// Start the SSL Health Monitor cron (Extended Trust Layer).
|
|
779
|
+
try { require('./services/ssl-monitor').start(); } catch (e) { console.warn('[ssl-monitor] start failed:', e.message); }
|
|
780
|
+
|
|
781
|
+
// Start the Certificate Transparency Monitor (opt-in via WAB_CT_MONITOR=true).
|
|
782
|
+
try { require('./services/ssl-ct-monitor').start(); } catch (e) { console.warn('[ct-monitor] start failed:', e.message); }
|
|
783
|
+
|
|
784
|
+
server.listen(PORT, () => {
|
|
785
|
+
console.log(`\n ╔══════════════════════════════════════════╗`);
|
|
786
|
+
console.log(` ║ Web Agent Bridge v${pkg.version} ║`);
|
|
787
|
+
console.log(` ║ Server running on http://localhost:${PORT} ║`);
|
|
788
|
+
console.log(` ║ WebSocket: ws://localhost:${PORT}/ws/analytics ║`);
|
|
789
|
+
console.log(` ╚══════════════════════════════════════════╝\n`);
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
module.exports = app;
|