@weavetab/mcp 2.5.0-beta.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/AGENTS.md +48 -0
- package/ARCHITECTURE.md +590 -0
- package/LICENSE +631 -0
- package/README.md +909 -0
- package/SECURITY.md +196 -0
- package/TROUBLESHOOTING.md +365 -0
- package/dist/anticaptcha/detector.d.ts +49 -0
- package/dist/anticaptcha/detector.js +465 -0
- package/dist/anticaptcha/integration.d.ts +36 -0
- package/dist/anticaptcha/integration.js +123 -0
- package/dist/anticaptcha/solver.d.ts +27 -0
- package/dist/anticaptcha/solver.js +552 -0
- package/dist/audit/logger.d.ts +13 -0
- package/dist/audit/logger.js +177 -0
- package/dist/audit/telemetry.d.ts +36 -0
- package/dist/audit/telemetry.js +162 -0
- package/dist/cdp/bridge.d.ts +16 -0
- package/dist/cdp/bridge.js +826 -0
- package/dist/cdp/confirm.d.ts +26 -0
- package/dist/cdp/confirm.js +79 -0
- package/dist/cdp/connector.d.ts +37 -0
- package/dist/cdp/connector.js +661 -0
- package/dist/cdp/extension.d.ts +36 -0
- package/dist/cdp/extension.js +118 -0
- package/dist/cdp/finder.d.ts +1 -0
- package/dist/cdp/finder.js +2 -0
- package/dist/cdp/helpers.d.ts +11 -0
- package/dist/cdp/helpers.js +195 -0
- package/dist/cdp/label_cache.d.ts +12 -0
- package/dist/cdp/label_cache.js +51 -0
- package/dist/cdp/lock.d.ts +33 -0
- package/dist/cdp/lock.js +206 -0
- package/dist/cdp/omni_broker.d.ts +17 -0
- package/dist/cdp/omni_broker.js +119 -0
- package/dist/cdp/profiles.d.ts +1 -0
- package/dist/cdp/profiles.js +87 -0
- package/dist/cdp/pruner.d.ts +41 -0
- package/dist/cdp/pruner.js +263 -0
- package/dist/cdp/ref_cache.d.ts +101 -0
- package/dist/cdp/ref_cache.js +234 -0
- package/dist/cdp/retry.d.ts +5 -0
- package/dist/cdp/retry.js +39 -0
- package/dist/cdp/retry_enhanced.d.ts +44 -0
- package/dist/cdp/retry_enhanced.js +102 -0
- package/dist/cdp/security.d.ts +50 -0
- package/dist/cdp/security.js +237 -0
- package/dist/cdp/verify.d.ts +39 -0
- package/dist/cdp/verify.js +143 -0
- package/dist/cdp/walker.d.ts +91 -0
- package/dist/cdp/walker.js +896 -0
- package/dist/cdp/walker.test.d.ts +2 -0
- package/dist/cdp/walker.test.js +172 -0
- package/dist/cdp/websocket_transport.d.ts +15 -0
- package/dist/cdp/websocket_transport.js +118 -0
- package/dist/cli/plugin.d.ts +9 -0
- package/dist/cli/plugin.js +1134 -0
- package/dist/cli/updater.d.ts +1 -0
- package/dist/cli/updater.js +72 -0
- package/dist/config/loader.d.ts +120 -0
- package/dist/config/loader.js +704 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1248 -0
- package/dist/intelligence/hints.d.ts +11 -0
- package/dist/intelligence/hints.js +39 -0
- package/dist/intelligence/intent.d.ts +33 -0
- package/dist/intelligence/intent.js +171 -0
- package/dist/intelligence/sessionMemory.d.ts +66 -0
- package/dist/intelligence/sessionMemory.js +284 -0
- package/dist/intelligence/sessionMemory.test.d.ts +2 -0
- package/dist/intelligence/sessionMemory.test.js +62 -0
- package/dist/intelligence/trail.d.ts +64 -0
- package/dist/intelligence/trail.js +233 -0
- package/dist/intelligence/trail.test.d.ts +2 -0
- package/dist/intelligence/trail.test.js +110 -0
- package/dist/intelligence/vision.d.ts +24 -0
- package/dist/intelligence/vision.js +151 -0
- package/dist/lifecycle/mission_manager.d.ts +16 -0
- package/dist/lifecycle/mission_manager.js +44 -0
- package/dist/overlay/bubble.d.ts +16 -0
- package/dist/overlay/bubble.js +37 -0
- package/dist/overlay/ghost.d.ts +35 -0
- package/dist/overlay/ghost.js +508 -0
- package/dist/overlay/injector.d.ts +22 -0
- package/dist/overlay/injector.js +198 -0
- package/dist/plugin/interface.d.ts +134 -0
- package/dist/plugin/interface.js +1 -0
- package/dist/plugin/loader.d.ts +18 -0
- package/dist/plugin/loader.js +155 -0
- package/dist/plugin/registry.d.ts +50 -0
- package/dist/plugin/registry.js +234 -0
- package/dist/security/blacklist.d.ts +24 -0
- package/dist/security/blacklist.js +80 -0
- package/dist/security/filter.d.ts +2 -0
- package/dist/security/filter.js +20 -0
- package/dist/security/guard.d.ts +8 -0
- package/dist/security/guard.js +35 -0
- package/dist/security/ratelimit.d.ts +11 -0
- package/dist/security/ratelimit.js +57 -0
- package/dist/security/secrets.d.ts +75 -0
- package/dist/security/secrets.js +365 -0
- package/dist/sensors/actionability.d.ts +81 -0
- package/dist/sensors/actionability.js +303 -0
- package/dist/sensors/guard.d.ts +25 -0
- package/dist/sensors/guard.js +168 -0
- package/dist/sensors/mutations.d.ts +23 -0
- package/dist/sensors/mutations.js +93 -0
- package/dist/sensors/network.d.ts +67 -0
- package/dist/sensors/network.js +193 -0
- package/dist/sensors/operation_monitor.d.ts +80 -0
- package/dist/sensors/operation_monitor.js +203 -0
- package/dist/sensors/pacing.d.ts +44 -0
- package/dist/sensors/pacing.js +111 -0
- package/dist/sensors/thoughts.d.ts +2 -0
- package/dist/sensors/thoughts.js +59 -0
- package/dist/sensors/wait.d.ts +12 -0
- package/dist/sensors/wait.js +44 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +1687 -0
- package/dist/state/agent.d.ts +36 -0
- package/dist/state/agent.js +101 -0
- package/dist/state/cache.d.ts +16 -0
- package/dist/state/cache.js +92 -0
- package/dist/state/cache.test.d.ts +2 -0
- package/dist/state/cache.test.js +76 -0
- package/dist/state/delta.d.ts +71 -0
- package/dist/state/delta.js +234 -0
- package/dist/state/delta.test.d.ts +2 -0
- package/dist/state/delta.test.js +279 -0
- package/dist/state/groups.d.ts +15 -0
- package/dist/state/groups.js +80 -0
- package/dist/state/session.d.ts +30 -0
- package/dist/state/session.js +155 -0
- package/dist/tools/accessibility.d.ts +14 -0
- package/dist/tools/accessibility.js +32 -0
- package/dist/tools/ask.d.ts +14 -0
- package/dist/tools/ask.js +49 -0
- package/dist/tools/automation.d.ts +36 -0
- package/dist/tools/automation.js +218 -0
- package/dist/tools/browser_detect.d.ts +13 -0
- package/dist/tools/browser_detect.js +73 -0
- package/dist/tools/burst.d.ts +35 -0
- package/dist/tools/burst.js +344 -0
- package/dist/tools/canvas.d.ts +25 -0
- package/dist/tools/canvas.js +132 -0
- package/dist/tools/captcha.d.ts +38 -0
- package/dist/tools/captcha.js +209 -0
- package/dist/tools/click.d.ts +36 -0
- package/dist/tools/click.js +561 -0
- package/dist/tools/click_and_wait.d.ts +33 -0
- package/dist/tools/click_and_wait.js +149 -0
- package/dist/tools/clipboard.d.ts +29 -0
- package/dist/tools/clipboard.js +279 -0
- package/dist/tools/console.d.ts +21 -0
- package/dist/tools/console.js +107 -0
- package/dist/tools/cookies.d.ts +67 -0
- package/dist/tools/cookies.js +121 -0
- package/dist/tools/design_extract.d.ts +109 -0
- package/dist/tools/design_extract.js +641 -0
- package/dist/tools/dialog.d.ts +24 -0
- package/dist/tools/dialog.js +109 -0
- package/dist/tools/drag.d.ts +15 -0
- package/dist/tools/drag.js +29 -0
- package/dist/tools/emulation.d.ts +33 -0
- package/dist/tools/emulation.js +69 -0
- package/dist/tools/eval.d.ts +20 -0
- package/dist/tools/eval.js +92 -0
- package/dist/tools/fill.d.ts +41 -0
- package/dist/tools/fill.js +667 -0
- package/dist/tools/find.d.ts +30 -0
- package/dist/tools/find.js +60 -0
- package/dist/tools/geo.d.ts +19 -0
- package/dist/tools/geo.js +29 -0
- package/dist/tools/github/analyze.d.ts +4 -0
- package/dist/tools/github/analyze.js +113 -0
- package/dist/tools/github/cache.d.ts +21 -0
- package/dist/tools/github/cache.js +28 -0
- package/dist/tools/github/index.d.ts +4 -0
- package/dist/tools/github/index.js +4 -0
- package/dist/tools/github/issues.d.ts +9 -0
- package/dist/tools/github/issues.js +88 -0
- package/dist/tools/github/pr.d.ts +5 -0
- package/dist/tools/github/pr.js +88 -0
- package/dist/tools/github/rateLimiter.d.ts +4 -0
- package/dist/tools/github/rateLimiter.js +46 -0
- package/dist/tools/github/read.d.ts +34 -0
- package/dist/tools/github/read.js +74 -0
- package/dist/tools/github/repoContext.d.ts +20 -0
- package/dist/tools/github/repoContext.js +129 -0
- package/dist/tools/highlight.d.ts +20 -0
- package/dist/tools/highlight.js +140 -0
- package/dist/tools/hover.d.ts +19 -0
- package/dist/tools/hover.js +57 -0
- package/dist/tools/inspect.d.ts +17 -0
- package/dist/tools/inspect.js +125 -0
- package/dist/tools/integration.test.d.ts +2 -0
- package/dist/tools/integration.test.js +96 -0
- package/dist/tools/key.d.ts +25 -0
- package/dist/tools/key.js +173 -0
- package/dist/tools/macro_compiler.d.ts +7 -0
- package/dist/tools/macro_compiler.js +82 -0
- package/dist/tools/mouse.d.ts +21 -0
- package/dist/tools/mouse.js +62 -0
- package/dist/tools/navigate.d.ts +16 -0
- package/dist/tools/navigate.js +188 -0
- package/dist/tools/network_intercept.d.ts +17 -0
- package/dist/tools/network_intercept.js +151 -0
- package/dist/tools/office/index.d.ts +9 -0
- package/dist/tools/office/index.js +7 -0
- package/dist/tools/office/list.d.ts +15 -0
- package/dist/tools/office/list.js +41 -0
- package/dist/tools/office/parse.d.ts +20 -0
- package/dist/tools/office/parse.js +172 -0
- package/dist/tools/pdf.d.ts +22 -0
- package/dist/tools/pdf.js +42 -0
- package/dist/tools/performance.d.ts +12 -0
- package/dist/tools/performance.js +47 -0
- package/dist/tools/performance_test.d.ts +70 -0
- package/dist/tools/performance_test.js +128 -0
- package/dist/tools/plan.d.ts +39 -0
- package/dist/tools/plan.js +161 -0
- package/dist/tools/pointer.d.ts +35 -0
- package/dist/tools/pointer.js +522 -0
- package/dist/tools/read.d.ts +30 -0
- package/dist/tools/read.js +488 -0
- package/dist/tools/recording.d.ts +45 -0
- package/dist/tools/recording.js +124 -0
- package/dist/tools/response.d.ts +129 -0
- package/dist/tools/response.js +5 -0
- package/dist/tools/scrape.d.ts +24 -0
- package/dist/tools/scrape.js +140 -0
- package/dist/tools/screenshot.d.ts +40 -0
- package/dist/tools/screenshot.js +79 -0
- package/dist/tools/scroll.d.ts +23 -0
- package/dist/tools/scroll.js +196 -0
- package/dist/tools/select.d.ts +13 -0
- package/dist/tools/select.js +217 -0
- package/dist/tools/snapshot.d.ts +27 -0
- package/dist/tools/snapshot.js +99 -0
- package/dist/tools/storage.d.ts +18 -0
- package/dist/tools/storage.js +29 -0
- package/dist/tools/tabs.d.ts +77 -0
- package/dist/tools/tabs.js +342 -0
- package/dist/tools/thought.d.ts +16 -0
- package/dist/tools/thought.js +79 -0
- package/dist/tools/type.d.ts +65 -0
- package/dist/tools/type.js +1061 -0
- package/dist/tools/type_secret.d.ts +23 -0
- package/dist/tools/type_secret.js +229 -0
- package/dist/tools/upload.d.ts +19 -0
- package/dist/tools/upload.js +103 -0
- package/dist/tools/video_compiler.d.ts +24 -0
- package/dist/tools/video_compiler.js +199 -0
- package/dist/tools/viewport.d.ts +44 -0
- package/dist/tools/viewport.js +230 -0
- package/dist/tools/wait.d.ts +19 -0
- package/dist/tools/wait.js +202 -0
- package/dist/tools/wait_for.d.ts +23 -0
- package/dist/tools/wait_for.js +30 -0
- package/dist/tools/windows.d.ts +22 -0
- package/dist/tools/windows.js +57 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +12 -0
- package/docs/DEV_MODE.md +68 -0
- package/docs/DOCUMENTATION.md +532 -0
- package/docs/PLUGINS.md +538 -0
- package/docs/TOOLS.md +104 -0
- package/docs/dev_plugin.md +310 -0
- package/ext-dist/background.js +467 -0
- package/ext-dist/content.js +3089 -0
- package/ext-dist/dashboard.html +583 -0
- package/ext-dist/hud/hud.js +73 -0
- package/ext-dist/hud/index.html +20 -0
- package/ext-dist/manifest.json +77 -0
- package/ext-dist/sidepanel/sidepanel.js +25 -0
- package/ext-dist/sidepanel.html +547 -0
- package/ext-dist/styles.css +218 -0
- package/icon.png +0 -0
- package/mcp.json +796 -0
- package/package.json +113 -0
- package/server.json +23 -0
- package/smithery.yaml +11 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = dirname(__filename);
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Verification Registry
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
export const OFFICIAL_PLUGIN_PREFIX = "@weavetab/plugin-";
|
|
10
|
+
// The production remote URL — points to weavetab/registry on GitHub.
|
|
11
|
+
export const REGISTRY_URL = "https://raw.githubusercontent.com/weavetab/registry/main/verified.json";
|
|
12
|
+
// Local override: when this file exists at the root of the repo, we use it instead
|
|
13
|
+
// of the remote URL. This allows full offline/local testing.
|
|
14
|
+
const LOCAL_REGISTRY_PATH = join(__dirname, "..", "..", "..", "registry", "verified.json");
|
|
15
|
+
/** In-memory cache so we only fetch once per process. */
|
|
16
|
+
let _verifiedCache = null;
|
|
17
|
+
let _cacheExpiry = 0; // unix ms — refresh every 30 min in long-running processes
|
|
18
|
+
const CACHE_TTL_MS = 30 * 60 * 1000;
|
|
19
|
+
function extractPackageNames(rawList) {
|
|
20
|
+
const set = new Set();
|
|
21
|
+
for (const item of rawList) {
|
|
22
|
+
if (typeof item === "string" && item.trim()) {
|
|
23
|
+
set.add(item.trim());
|
|
24
|
+
}
|
|
25
|
+
else if (item && typeof item === "object") {
|
|
26
|
+
const obj = item;
|
|
27
|
+
if (typeof obj.id === "string" && obj.id.trim())
|
|
28
|
+
set.add(obj.id.trim());
|
|
29
|
+
if (typeof obj.name === "string" && obj.name.trim())
|
|
30
|
+
set.add(obj.name.trim());
|
|
31
|
+
if (typeof obj.package === "string" && obj.package.trim())
|
|
32
|
+
set.add(obj.package.trim());
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return set;
|
|
36
|
+
}
|
|
37
|
+
function loadLocalRegistry() {
|
|
38
|
+
if (!existsSync(LOCAL_REGISTRY_PATH))
|
|
39
|
+
return null;
|
|
40
|
+
try {
|
|
41
|
+
const raw = readFileSync(LOCAL_REGISTRY_PATH, "utf-8");
|
|
42
|
+
const data = JSON.parse(raw);
|
|
43
|
+
if (Array.isArray(data.verified)) {
|
|
44
|
+
return extractPackageNames(data.verified);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch { /* malformed file */ }
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
async function fetchRemoteRegistry() {
|
|
51
|
+
const res = await fetch(REGISTRY_URL, {
|
|
52
|
+
signal: AbortSignal.timeout(5000),
|
|
53
|
+
headers: { "User-Agent": "Weavetab-Registry-Check/1.0" },
|
|
54
|
+
});
|
|
55
|
+
if (!res.ok)
|
|
56
|
+
throw new Error(`registry fetch failed: ${res.status}`);
|
|
57
|
+
const data = await res.json();
|
|
58
|
+
if (!Array.isArray(data.verified))
|
|
59
|
+
throw new Error("invalid registry format");
|
|
60
|
+
return extractPackageNames(data.verified);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Returns the set of verified package names.
|
|
64
|
+
* - Tries local registry/verified.json first (dev/test mode).
|
|
65
|
+
* - Falls back to GitHub remote.
|
|
66
|
+
* - Falls back to empty set on any failure (fail open — just shows unverified).
|
|
67
|
+
*/
|
|
68
|
+
export async function getVerifiedPackages() {
|
|
69
|
+
if (_verifiedCache && Date.now() < _cacheExpiry)
|
|
70
|
+
return _verifiedCache;
|
|
71
|
+
// 1. Local override (testing mode)
|
|
72
|
+
const local = loadLocalRegistry();
|
|
73
|
+
if (local) {
|
|
74
|
+
_verifiedCache = local;
|
|
75
|
+
_cacheExpiry = Date.now() + CACHE_TTL_MS;
|
|
76
|
+
return local;
|
|
77
|
+
}
|
|
78
|
+
// 2. Remote registry
|
|
79
|
+
try {
|
|
80
|
+
const remote = await fetchRemoteRegistry();
|
|
81
|
+
_verifiedCache = remote;
|
|
82
|
+
_cacheExpiry = Date.now() + CACHE_TTL_MS;
|
|
83
|
+
return remote;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// 3. Fail open — return whatever we had before (or empty)
|
|
87
|
+
return _verifiedCache ?? new Set();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Clear in-memory verified packages cache (useful for tests or forced refresh).
|
|
92
|
+
*/
|
|
93
|
+
export function invalidateRegistryCache() {
|
|
94
|
+
_verifiedCache = null;
|
|
95
|
+
_cacheExpiry = 0;
|
|
96
|
+
}
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Trust Resolution
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
/**
|
|
101
|
+
* Determine the trust tier for a plugin based on its package name and the
|
|
102
|
+
* verified registry.
|
|
103
|
+
*/
|
|
104
|
+
export function resolvePluginTrust(packageName, verifiedPackages) {
|
|
105
|
+
// Tier 1: Official — @weavetab/plugin-* packages are first-party and trusted.
|
|
106
|
+
if (packageName.startsWith(OFFICIAL_PLUGIN_PREFIX)) {
|
|
107
|
+
return {
|
|
108
|
+
tier: "official",
|
|
109
|
+
label: "Official",
|
|
110
|
+
badge: "\u001b[1;44m \u271a \u001b[0m\u001b[34m [Official]\u001b[0m",
|
|
111
|
+
tooltip: "Made by the Weavetab team. Fully trusted and audited.",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// Tier 2: Verified community — listed in the registry.
|
|
115
|
+
if (verifiedPackages.has(packageName)) {
|
|
116
|
+
return {
|
|
117
|
+
tier: "verified",
|
|
118
|
+
label: "Verified",
|
|
119
|
+
badge: "\u001b[1;42m \u2714 \u001b[0m\u001b[32m [Verified]\u001b[0m",
|
|
120
|
+
tooltip: "Community-verified plugin. Reviewed and approved by Weavetab.",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// Tier 3: Unverified — unknown origin / community package.
|
|
124
|
+
return {
|
|
125
|
+
tier: "unverified",
|
|
126
|
+
label: "Unverified",
|
|
127
|
+
badge: "\u001b[1;43m \u26a0 \u001b[0m\u001b[33m [Unverified]\u001b[0m",
|
|
128
|
+
tooltip: "This plugin has not been verified by Weavetab. Review its permissions carefully.",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// NPM Registry Existence & Candidate Resolution
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
/**
|
|
135
|
+
* Checks if a package exists on the npm registry via a fast HEAD request.
|
|
136
|
+
*/
|
|
137
|
+
export async function checkNpmPackageExists(pkgName) {
|
|
138
|
+
try {
|
|
139
|
+
const encodedName = pkgName.startsWith("@")
|
|
140
|
+
? `@${encodeURIComponent(pkgName.slice(1))}`
|
|
141
|
+
: encodeURIComponent(pkgName);
|
|
142
|
+
const res = await fetch(`https://registry.npmjs.org/${encodedName}`, {
|
|
143
|
+
method: "HEAD",
|
|
144
|
+
signal: AbortSignal.timeout(3000),
|
|
145
|
+
headers: { "User-Agent": "Weavetab-CLI/2.5.0" },
|
|
146
|
+
});
|
|
147
|
+
return res.status === 200;
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Resolves potential package candidates for an input name.
|
|
155
|
+
* - If name is already explicitly scoped (starts with @), resolves directly.
|
|
156
|
+
* - If short name is provided (e.g. "n8n-wt"), checks both:
|
|
157
|
+
* 1. Official shortcut: @weavetab/plugin-<name>
|
|
158
|
+
* 2. Community package: <name>
|
|
159
|
+
*/
|
|
160
|
+
export async function resolvePluginCandidates(inputName, verifiedPackages) {
|
|
161
|
+
const trimmed = inputName.trim();
|
|
162
|
+
// If already full scoped package name, resolve directly
|
|
163
|
+
if (trimmed.startsWith("@")) {
|
|
164
|
+
const trust = resolvePluginTrust(trimmed, verifiedPackages);
|
|
165
|
+
return [
|
|
166
|
+
{
|
|
167
|
+
packageName: trimmed,
|
|
168
|
+
tier: trust.tier,
|
|
169
|
+
label: trust.label,
|
|
170
|
+
badge: trust.badge,
|
|
171
|
+
tooltip: trust.tooltip,
|
|
172
|
+
isOfficialShortcut: trimmed.startsWith(OFFICIAL_PLUGIN_PREFIX),
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
const officialCandidateName = `${OFFICIAL_PLUGIN_PREFIX}${trimmed}`;
|
|
177
|
+
const communityCandidateName = trimmed;
|
|
178
|
+
// Check existence in verified registry or npm
|
|
179
|
+
const [officialOnNpm, communityOnNpm] = await Promise.all([
|
|
180
|
+
checkNpmPackageExists(officialCandidateName),
|
|
181
|
+
checkNpmPackageExists(communityCandidateName),
|
|
182
|
+
]);
|
|
183
|
+
const candidates = [];
|
|
184
|
+
// 1. Check Official Candidate
|
|
185
|
+
if (officialOnNpm) {
|
|
186
|
+
const trust = resolvePluginTrust(officialCandidateName, verifiedPackages);
|
|
187
|
+
candidates.push({
|
|
188
|
+
packageName: officialCandidateName,
|
|
189
|
+
tier: "official",
|
|
190
|
+
label: "Official",
|
|
191
|
+
badge: trust.badge,
|
|
192
|
+
tooltip: "Official Weavetab plugin maintained by the core team",
|
|
193
|
+
isOfficialShortcut: true,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
// 2. Check Community Candidate
|
|
197
|
+
const isCommunityVerified = verifiedPackages.has(communityCandidateName);
|
|
198
|
+
if (communityOnNpm || isCommunityVerified) {
|
|
199
|
+
const trust = resolvePluginTrust(communityCandidateName, verifiedPackages);
|
|
200
|
+
candidates.push({
|
|
201
|
+
packageName: communityCandidateName,
|
|
202
|
+
tier: trust.tier,
|
|
203
|
+
label: trust.label,
|
|
204
|
+
badge: trust.badge,
|
|
205
|
+
tooltip: isCommunityVerified
|
|
206
|
+
? "Community plugin reviewed and listed in Weavetab verified registry"
|
|
207
|
+
: "Third-party community package on npm",
|
|
208
|
+
isOfficialShortcut: false,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
// If neither was confirmed on npm (e.g. offline / network timeout or private repo),
|
|
212
|
+
// fallback to providing both possibilities as options
|
|
213
|
+
if (candidates.length === 0) {
|
|
214
|
+
const officialTrust = resolvePluginTrust(officialCandidateName, verifiedPackages);
|
|
215
|
+
const communityTrust = resolvePluginTrust(communityCandidateName, verifiedPackages);
|
|
216
|
+
candidates.push({
|
|
217
|
+
packageName: officialCandidateName,
|
|
218
|
+
tier: "official",
|
|
219
|
+
label: "Official",
|
|
220
|
+
badge: officialTrust.badge,
|
|
221
|
+
tooltip: "Official Weavetab plugin shortcut",
|
|
222
|
+
isOfficialShortcut: true,
|
|
223
|
+
});
|
|
224
|
+
candidates.push({
|
|
225
|
+
packageName: communityCandidateName,
|
|
226
|
+
tier: communityTrust.tier,
|
|
227
|
+
label: communityTrust.label,
|
|
228
|
+
badge: communityTrust.badge,
|
|
229
|
+
tooltip: communityTrust.tooltip,
|
|
230
|
+
isOfficialShortcut: false,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return candidates;
|
|
234
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Config, PolicyMode } from "../config/loader.js";
|
|
2
|
+
export interface AllowResult {
|
|
3
|
+
allowed: true;
|
|
4
|
+
mode?: PolicyMode;
|
|
5
|
+
}
|
|
6
|
+
export interface BlockResult {
|
|
7
|
+
allowed: false;
|
|
8
|
+
reason: string;
|
|
9
|
+
mode?: PolicyMode;
|
|
10
|
+
}
|
|
11
|
+
export interface AuditResult {
|
|
12
|
+
allowed: true;
|
|
13
|
+
mode: "audit-only";
|
|
14
|
+
}
|
|
15
|
+
export interface ReadOnlyResult {
|
|
16
|
+
allowed: true;
|
|
17
|
+
mode: "read-only";
|
|
18
|
+
}
|
|
19
|
+
export type DomainCheckResult = AllowResult | BlockResult | AuditResult | ReadOnlyResult;
|
|
20
|
+
export declare function checkDomain(url: string, config: Config): DomainCheckResult;
|
|
21
|
+
export declare function isToolAllowedInMode(toolName: string, mode: PolicyMode): {
|
|
22
|
+
allowed: boolean;
|
|
23
|
+
reason?: string;
|
|
24
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { getConfigPolicies, getConfigBlock } from "../config/loader.js";
|
|
2
|
+
// Escape all regex metacharacters (including `?`) before replacing `*` with `.*`,
|
|
3
|
+
// then guard against patterns that produce >3 `.*` segments. This prevents both
|
|
4
|
+
// `?` being treated as an optional quantifier and catastrophic backtracking from
|
|
5
|
+
// patterns like `*.*.*.*.*.*`.
|
|
6
|
+
function matchesPattern(url, pattern) {
|
|
7
|
+
// Limit wildcard count to prevent ReDoS from patterns like *.*.*.*.*.*.*.*
|
|
8
|
+
const wildcardCount = (pattern.match(/\*/g) || []).length;
|
|
9
|
+
if (wildcardCount > 5)
|
|
10
|
+
return false;
|
|
11
|
+
const escaped = pattern
|
|
12
|
+
.replace(/[.+^${}()|[\]\\?]/g, "\\$&") // escape ALL regex metacharacters (including ?)
|
|
13
|
+
.replace(/\\\*/g, ".*"); // only then expand * → .*
|
|
14
|
+
try {
|
|
15
|
+
return new RegExp(`^${escaped}$`, "i").test(extractHostname(url));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function extractHostname(url) {
|
|
22
|
+
try {
|
|
23
|
+
return new URL(url).hostname;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return url;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function checkDomain(url, config) {
|
|
30
|
+
const hostname = extractHostname(url);
|
|
31
|
+
// Phase 7c: Check explicit policies first (most specific wins)
|
|
32
|
+
for (const policy of getConfigPolicies(config)) {
|
|
33
|
+
if (matchesPattern(hostname, policy.pattern)) {
|
|
34
|
+
switch (policy.mode) {
|
|
35
|
+
case "deny":
|
|
36
|
+
return { allowed: false, reason: `Domain denied by policy: ${policy.pattern}`, mode: "deny" };
|
|
37
|
+
case "audit-only":
|
|
38
|
+
return { allowed: true, mode: "audit-only" };
|
|
39
|
+
case "read-only":
|
|
40
|
+
return { allowed: true, mode: "read-only" };
|
|
41
|
+
case "allow":
|
|
42
|
+
return { allowed: true, mode: "allow" };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Legacy blocklist check (backward compatible)
|
|
47
|
+
for (const pattern of getConfigBlock(config)) {
|
|
48
|
+
if (matchesPattern(hostname, pattern)) {
|
|
49
|
+
return { allowed: false, reason: `Domain blocked by config: ${pattern}`, mode: "deny" };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { allowed: true };
|
|
53
|
+
}
|
|
54
|
+
const READ_ONLY_TOOLS = new Set([
|
|
55
|
+
"browser_map", "browser_find", "browser_scrape", "browser_console",
|
|
56
|
+
"browser_eval", "browser_inspect", "browser_snapshot", "browser_pdf",
|
|
57
|
+
"browser_cookies",
|
|
58
|
+
]);
|
|
59
|
+
const WRITE_TOOLS = new Set([
|
|
60
|
+
"browser_click", "browser_type", "browser_key", "browser_navigate",
|
|
61
|
+
"browser_scroll", "browser_dialog", "browser_pointer", "browser_select",
|
|
62
|
+
"browser_upload", "browser_storage", "browser_network_intercept",
|
|
63
|
+
"browser_viewport", "browser_clipboard", "browser_canvas",
|
|
64
|
+
"browser_automation", "browser_recording",
|
|
65
|
+
]);
|
|
66
|
+
export function isToolAllowedInMode(toolName, mode) {
|
|
67
|
+
switch (mode) {
|
|
68
|
+
case "allow":
|
|
69
|
+
return { allowed: true };
|
|
70
|
+
case "deny":
|
|
71
|
+
return { allowed: false, reason: "Domain is denied" };
|
|
72
|
+
case "audit-only":
|
|
73
|
+
return { allowed: true };
|
|
74
|
+
case "read-only":
|
|
75
|
+
if (READ_ONLY_TOOLS.has(toolName)) {
|
|
76
|
+
return { allowed: true };
|
|
77
|
+
}
|
|
78
|
+
return { allowed: false, reason: `Tool '${toolName}' is not allowed in read-only mode. Only map/find/scrape/console/eval/inspect/snapshot/pdf/cookies are permitted.` };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const MAX_LABEL_LENGTH = 500;
|
|
2
|
+
// Comprehensive zero-width and invisible Unicode blocklist for prompt injection
|
|
3
|
+
// defense: LRM/RLM, line/paragraph separators, bidi isolates, combining grapheme
|
|
4
|
+
// joiner, variation selectors, and en/em/thin/hair spaces.
|
|
5
|
+
const INVISIBLE_CHAR_REGEX = /[\u200B-\u200F\uFEFF\u00AD\u2060\u180E\u00A0\u2028\u2029\u2066-\u2069\u034F\uFE00-\uFE0F\u2000-\u200A]/;
|
|
6
|
+
function isLikelySuspicious(label) {
|
|
7
|
+
if (label.length > MAX_LABEL_LENGTH)
|
|
8
|
+
return true;
|
|
9
|
+
// NFKC normalization collapses homoglyph/ligature attacks before checking
|
|
10
|
+
const normalized = label.normalize("NFKC");
|
|
11
|
+
if (INVISIBLE_CHAR_REGEX.test(normalized))
|
|
12
|
+
return true;
|
|
13
|
+
// Excessive whitespace: more than 5 consecutive spaces/tabs not at start/end
|
|
14
|
+
if (/[ \t]{6,}/.test(normalized.trim()))
|
|
15
|
+
return true;
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
export function filterHidden(elements) {
|
|
19
|
+
return elements.filter((el) => !isLikelySuspicious(el.label));
|
|
20
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weavetab — security/guard.ts
|
|
3
|
+
* Root process guard and browser-side storage access blocker via CDP Page API.
|
|
4
|
+
* Weavetab by fy2ne
|
|
5
|
+
*/
|
|
6
|
+
import CDP from "chrome-remote-interface";
|
|
7
|
+
export declare function assertNotRoot(): void;
|
|
8
|
+
export declare function blockStorageAccess(session: CDP.Client): Promise<void>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export function assertNotRoot() {
|
|
2
|
+
// process.getuid is Unix-only; on Windows it doesn't exist which is fine
|
|
3
|
+
if (typeof process.getuid === "function" && process.getuid() === 0) {
|
|
4
|
+
throw new Error("[!] Weave failed: Running as root is not allowed. Start Weavetab as a normal user.");
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
const STORAGE_BLOCK_SCRIPT = `
|
|
8
|
+
(function() {
|
|
9
|
+
// Only block cookie WRITES for known tracking domains, not reads.
|
|
10
|
+
// Fully blocking storage breaks site rendering (GitHub, etc.)
|
|
11
|
+
var _origCookieDesc = Object.getOwnPropertyDescriptor(document, 'cookie');
|
|
12
|
+
if (_origCookieDesc) {
|
|
13
|
+
Object.defineProperty(document, 'cookie', {
|
|
14
|
+
get: function() { return _origCookieDesc.get.call(document); },
|
|
15
|
+
set: function(v) {
|
|
16
|
+
// Block cookies from known trackers
|
|
17
|
+
if (/(_ga|_gid|_fbp|_fbc|__utm|fr|_gcl)/.test(v)) return;
|
|
18
|
+
_origCookieDesc.set.call(document, v);
|
|
19
|
+
},
|
|
20
|
+
configurable: true
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
// Do NOT block localStorage/sessionStorage — sites need them to render
|
|
24
|
+
})();
|
|
25
|
+
`;
|
|
26
|
+
export async function blockStorageAccess(session) {
|
|
27
|
+
await session.Page.addScriptToEvaluateOnNewDocument({
|
|
28
|
+
source: STORAGE_BLOCK_SCRIPT,
|
|
29
|
+
});
|
|
30
|
+
// Also apply immediately to the current page context
|
|
31
|
+
await session.Runtime.evaluate({
|
|
32
|
+
expression: STORAGE_BLOCK_SCRIPT,
|
|
33
|
+
returnByValue: false,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Config } from "../config/loader.js";
|
|
2
|
+
export interface RateLimitAllowed {
|
|
3
|
+
allowed: true;
|
|
4
|
+
}
|
|
5
|
+
export interface RateLimitBlocked {
|
|
6
|
+
allowed: false;
|
|
7
|
+
retryAfterMs: number;
|
|
8
|
+
}
|
|
9
|
+
export type RateLimitResult = RateLimitAllowed | RateLimitBlocked;
|
|
10
|
+
export declare function checkRateLimit(config: Config): RateLimitResult;
|
|
11
|
+
export declare function resetRateLimit(): void;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { getConfigMaxActionsPerMinute } from "../config/loader.js";
|
|
2
|
+
// Circular buffer of timestamps per connection — one slot per allowed action in the window
|
|
3
|
+
const WINDOW_MS = 60_000;
|
|
4
|
+
const connectionTimestamps = new Map();
|
|
5
|
+
// Sweep old/unused connection IDs every 2 minutes so entries where all
|
|
6
|
+
// timestamps have expired (agent idle for >1 window) are removed instead of
|
|
7
|
+
// accumulating without bound.
|
|
8
|
+
setInterval(() => {
|
|
9
|
+
const cutoff = Date.now() - WINDOW_MS;
|
|
10
|
+
for (const [id, ts] of connectionTimestamps.entries()) {
|
|
11
|
+
if (ts.every(t => t <= cutoff))
|
|
12
|
+
connectionTimestamps.delete(id);
|
|
13
|
+
}
|
|
14
|
+
}, 120_000).unref();
|
|
15
|
+
// Validate agentId format to prevent manipulation
|
|
16
|
+
function validateAgentId(agentId) {
|
|
17
|
+
// Only allow alphanumeric, underscore, dash, and dot
|
|
18
|
+
// This prevents special characters and path manipulation attempts
|
|
19
|
+
const validPattern = /^[a-zA-Z0-9_.-]{1,64}$/;
|
|
20
|
+
return validPattern.test(agentId);
|
|
21
|
+
}
|
|
22
|
+
// Generate a unique connection ID for rate limiting
|
|
23
|
+
let connectionCounter = 0;
|
|
24
|
+
function generateConnectionId() {
|
|
25
|
+
return `conn_${Date.now()}_${++connectionCounter}`;
|
|
26
|
+
}
|
|
27
|
+
// Current connection ID for this session
|
|
28
|
+
let currentConnectionId = generateConnectionId();
|
|
29
|
+
export function checkRateLimit(config) {
|
|
30
|
+
// Rate-limit per connection rather than per agentId so an attacker can't
|
|
31
|
+
// manipulate agentId to get fresh buckets.
|
|
32
|
+
const connectionId = currentConnectionId;
|
|
33
|
+
// Validate agentId format if provided, but don't use it for rate limiting
|
|
34
|
+
if (config.agentId && !validateAgentId(config.agentId)) {
|
|
35
|
+
// Log warning but don't block - the connection ID is still used for rate limiting
|
|
36
|
+
console.error(`[SECURITY] Invalid agentId format detected: ${config.agentId}`);
|
|
37
|
+
}
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
const windowStart = now - WINDOW_MS;
|
|
40
|
+
// Evict expired timestamps (sliding window)
|
|
41
|
+
let timestamps = connectionTimestamps.get(connectionId) || [];
|
|
42
|
+
timestamps = timestamps.filter((t) => t > windowStart);
|
|
43
|
+
if (timestamps.length >= getConfigMaxActionsPerMinute(config)) {
|
|
44
|
+
// Oldest timestamp tells us when a slot frees up
|
|
45
|
+
const oldestInWindow = timestamps[0];
|
|
46
|
+
const retryAfterMs = oldestInWindow + WINDOW_MS - now;
|
|
47
|
+
return { allowed: false, retryAfterMs };
|
|
48
|
+
}
|
|
49
|
+
timestamps.push(now);
|
|
50
|
+
connectionTimestamps.set(connectionId, timestamps);
|
|
51
|
+
return { allowed: true };
|
|
52
|
+
}
|
|
53
|
+
export function resetRateLimit() {
|
|
54
|
+
connectionTimestamps.clear();
|
|
55
|
+
// Generate a new connection ID on reset
|
|
56
|
+
currentConnectionId = generateConnectionId();
|
|
57
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export interface SecretEntry {
|
|
2
|
+
value: string;
|
|
3
|
+
/** Allowed hostnames. Empty array = any domain (strongly discouraged). */
|
|
4
|
+
domains: string[];
|
|
5
|
+
createdAt: string;
|
|
6
|
+
updatedAt: string;
|
|
7
|
+
}
|
|
8
|
+
export interface SecretsFile {
|
|
9
|
+
version: 1;
|
|
10
|
+
secrets: Record<string, SecretEntry>;
|
|
11
|
+
}
|
|
12
|
+
export interface SecretProvider {
|
|
13
|
+
getSecret(key: string): Promise<string | undefined>;
|
|
14
|
+
}
|
|
15
|
+
export interface SecretListEntry {
|
|
16
|
+
key: string;
|
|
17
|
+
domains: string[];
|
|
18
|
+
updatedAt: string;
|
|
19
|
+
}
|
|
20
|
+
export declare const GLOBAL_SECRETS_PATH: string;
|
|
21
|
+
declare class JsonSecretStoreImpl implements SecretProvider {
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a secret value by key.
|
|
24
|
+
* Returns undefined if the key doesn't exist.
|
|
25
|
+
* Does NOT check domain — use checkDomain() for that before calling this.
|
|
26
|
+
*/
|
|
27
|
+
getSecret(key: string): Promise<string | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* Check whether the given hostname is allowed for this key.
|
|
30
|
+
* Returns true if:
|
|
31
|
+
* - The key doesn't exist (will fail at getSecret time anyway)
|
|
32
|
+
* - The key's domains list is empty (wildcard — any domain)
|
|
33
|
+
* - The hostname matches one of the allowed domains (exact or suffix match)
|
|
34
|
+
*/
|
|
35
|
+
checkDomain(key: string, hostname: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Get the allowed domains list for a key (for error messages).
|
|
38
|
+
* Never returns the value.
|
|
39
|
+
*/
|
|
40
|
+
getDomains(key: string): string[] | null;
|
|
41
|
+
/**
|
|
42
|
+
* Store a secret. Value must be passed in-process — never as a CLI arg.
|
|
43
|
+
* domains: list of allowed hostnames. Empty = any domain (not recommended).
|
|
44
|
+
*/
|
|
45
|
+
setSecret(key: string, value: string, domains: string[]): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* List all secrets — returns key names, domains, and timestamps ONLY.
|
|
48
|
+
* Never returns values.
|
|
49
|
+
*/
|
|
50
|
+
listSecrets(): Promise<SecretListEntry[]>;
|
|
51
|
+
/**
|
|
52
|
+
* Remove a secret by key. Returns true if the key existed and was removed.
|
|
53
|
+
*/
|
|
54
|
+
removeSecret(key: string): Promise<boolean>;
|
|
55
|
+
/** Returns the active secrets file path for display purposes. */
|
|
56
|
+
getSecretsFilePath(): string;
|
|
57
|
+
}
|
|
58
|
+
export declare const secretStore: JsonSecretStoreImpl;
|
|
59
|
+
/** @deprecated Use secretStore instead. */
|
|
60
|
+
export declare class EnvSecretProvider implements SecretProvider {
|
|
61
|
+
getSecret(key: string): Promise<string | undefined>;
|
|
62
|
+
}
|
|
63
|
+
declare class SessionSecretTrackerImpl {
|
|
64
|
+
/** Maps sessionId (CDP targetId) → Set of backendNodeIds marked as secret. */
|
|
65
|
+
private trackedNodes;
|
|
66
|
+
/** Maps sessionId (CDP targetId) → Set of plaintext secret strings injected. */
|
|
67
|
+
private sessionSecrets;
|
|
68
|
+
markNodeAsSecret(sessionId: string, backendNodeId: number): void;
|
|
69
|
+
isNodeSecret(sessionId: string, backendNodeId: number): boolean;
|
|
70
|
+
registerSecretValue(sessionId: string, value: string): void;
|
|
71
|
+
redact(sessionId: string, text: string): string;
|
|
72
|
+
clearSession(sessionId: string): void;
|
|
73
|
+
}
|
|
74
|
+
export declare const SessionSecretTracker: SessionSecretTrackerImpl;
|
|
75
|
+
export {};
|