multicorn-shield 0.9.0 → 0.11.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/CHANGELOG.md +375 -0
- package/LICENSE +1 -1
- package/README.md +29 -1
- package/dist/badge.js +44 -0
- package/dist/index.cjs +438 -27
- package/dist/index.d.cts +128 -1
- package/dist/index.d.ts +128 -1
- package/dist/index.js +435 -28
- package/dist/openclaw-plugin/multicorn-shield.js +3 -1
- package/dist/shield-extension.js +126 -5
- package/package.json +22 -6
package/dist/shield-extension.js
CHANGED
|
@@ -22359,11 +22359,117 @@ async function writeExtensionBackup(claudeDesktopConfigPath, mcpServers) {
|
|
|
22359
22359
|
|
|
22360
22360
|
// package.json
|
|
22361
22361
|
var package_default = {
|
|
22362
|
-
version: "0.
|
|
22362
|
+
version: "0.11.0"};
|
|
22363
22363
|
|
|
22364
22364
|
// src/package-meta.ts
|
|
22365
22365
|
var PACKAGE_VERSION = package_default.version;
|
|
22366
22366
|
|
|
22367
|
+
// src/extension/proxy-url-validator.ts
|
|
22368
|
+
var ProxyUrlValidationError = class extends Error {
|
|
22369
|
+
constructor(message) {
|
|
22370
|
+
super(message);
|
|
22371
|
+
this.name = "ProxyUrlValidationError";
|
|
22372
|
+
}
|
|
22373
|
+
};
|
|
22374
|
+
function isPrivateOrReservedIpv4(hostname3) {
|
|
22375
|
+
const parts = hostname3.split(".");
|
|
22376
|
+
if (parts.length !== 4) {
|
|
22377
|
+
return false;
|
|
22378
|
+
}
|
|
22379
|
+
const octets = [];
|
|
22380
|
+
for (const p of parts) {
|
|
22381
|
+
if (!/^\d{1,3}$/.test(p)) {
|
|
22382
|
+
return false;
|
|
22383
|
+
}
|
|
22384
|
+
const n = Number.parseInt(p, 10);
|
|
22385
|
+
if (Number.isNaN(n) || n < 0 || n > 255) {
|
|
22386
|
+
return false;
|
|
22387
|
+
}
|
|
22388
|
+
octets.push(n);
|
|
22389
|
+
}
|
|
22390
|
+
const [a, b] = octets;
|
|
22391
|
+
if (a === void 0 || b === void 0) {
|
|
22392
|
+
return false;
|
|
22393
|
+
}
|
|
22394
|
+
if (a === 127) {
|
|
22395
|
+
return true;
|
|
22396
|
+
}
|
|
22397
|
+
if (a === 10) {
|
|
22398
|
+
return true;
|
|
22399
|
+
}
|
|
22400
|
+
if (a === 172 && b >= 16 && b <= 31) {
|
|
22401
|
+
return true;
|
|
22402
|
+
}
|
|
22403
|
+
if (a === 192 && b === 168) {
|
|
22404
|
+
return true;
|
|
22405
|
+
}
|
|
22406
|
+
if (a === 169 && b === 254) {
|
|
22407
|
+
return true;
|
|
22408
|
+
}
|
|
22409
|
+
if (a === 0 && b === 0 && octets[2] === 0 && octets[3] === 0) {
|
|
22410
|
+
return true;
|
|
22411
|
+
}
|
|
22412
|
+
return false;
|
|
22413
|
+
}
|
|
22414
|
+
function isBlockedIpv6(host) {
|
|
22415
|
+
const h = host.split("%")[0]?.toLowerCase() ?? host.toLowerCase();
|
|
22416
|
+
if (h === "::1") {
|
|
22417
|
+
return true;
|
|
22418
|
+
}
|
|
22419
|
+
if (h.startsWith("fe80:")) {
|
|
22420
|
+
return true;
|
|
22421
|
+
}
|
|
22422
|
+
if (h.startsWith("fc") || h.startsWith("fd")) {
|
|
22423
|
+
return true;
|
|
22424
|
+
}
|
|
22425
|
+
return false;
|
|
22426
|
+
}
|
|
22427
|
+
function hostForValidation(hostname3) {
|
|
22428
|
+
if (hostname3.startsWith("[") && hostname3.endsWith("]")) {
|
|
22429
|
+
return hostname3.slice(1, -1);
|
|
22430
|
+
}
|
|
22431
|
+
return hostname3;
|
|
22432
|
+
}
|
|
22433
|
+
function assertSafeProxyUrl(raw, options) {
|
|
22434
|
+
let url2;
|
|
22435
|
+
try {
|
|
22436
|
+
url2 = new URL(raw);
|
|
22437
|
+
} catch {
|
|
22438
|
+
throw new ProxyUrlValidationError(`Invalid proxy URL: ${raw}`);
|
|
22439
|
+
}
|
|
22440
|
+
if (url2.protocol !== "https:" && url2.protocol !== "http:") {
|
|
22441
|
+
throw new ProxyUrlValidationError(
|
|
22442
|
+
`Unsupported proxy URL scheme: ${url2.protocol} - only https: and http: are allowed`
|
|
22443
|
+
);
|
|
22444
|
+
}
|
|
22445
|
+
const hostname3 = url2.hostname;
|
|
22446
|
+
if (hostname3.length === 0) {
|
|
22447
|
+
throw new ProxyUrlValidationError(`Proxy URL has no hostname: ${raw}`);
|
|
22448
|
+
}
|
|
22449
|
+
if (options?.allowPrivateNetworks === true) {
|
|
22450
|
+
return;
|
|
22451
|
+
}
|
|
22452
|
+
const host = hostForValidation(hostname3);
|
|
22453
|
+
if (host.toLowerCase() === "localhost") {
|
|
22454
|
+
throw new ProxyUrlValidationError(
|
|
22455
|
+
`Proxy URL points to a private/reserved network address: ${url2.hostname}`
|
|
22456
|
+
);
|
|
22457
|
+
}
|
|
22458
|
+
if (host.includes(":")) {
|
|
22459
|
+
if (isBlockedIpv6(host)) {
|
|
22460
|
+
throw new ProxyUrlValidationError(
|
|
22461
|
+
`Proxy URL points to a private/reserved network address: ${url2.hostname}`
|
|
22462
|
+
);
|
|
22463
|
+
}
|
|
22464
|
+
return;
|
|
22465
|
+
}
|
|
22466
|
+
if (isPrivateOrReservedIpv4(host)) {
|
|
22467
|
+
throw new ProxyUrlValidationError(
|
|
22468
|
+
`Proxy URL points to a private/reserved network address: ${url2.hostname}`
|
|
22469
|
+
);
|
|
22470
|
+
}
|
|
22471
|
+
}
|
|
22472
|
+
|
|
22367
22473
|
// src/extension/proxy-client.ts
|
|
22368
22474
|
var MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
22369
22475
|
var ProxyConfigFetchError = class extends Error {
|
|
@@ -22404,8 +22510,12 @@ function isProxyConfigRow(v) {
|
|
|
22404
22510
|
const o = v;
|
|
22405
22511
|
return typeof o["proxy_url"] === "string" && o["proxy_url"].length > 0 && typeof o["server_name"] === "string" && typeof o["target_url"] === "string";
|
|
22406
22512
|
}
|
|
22407
|
-
async function fetchProxyConfigs(baseUrl, apiKey, timeoutMs) {
|
|
22513
|
+
async function fetchProxyConfigs(baseUrl, apiKey, timeoutMs, options) {
|
|
22408
22514
|
const url2 = `${normalizeBaseUrl(baseUrl)}/api/v1/proxy/config`;
|
|
22515
|
+
assertSafeProxyUrl(
|
|
22516
|
+
url2,
|
|
22517
|
+
options?.allowPrivateNetworks === true ? { allowPrivateNetworks: true } : void 0
|
|
22518
|
+
);
|
|
22409
22519
|
let response;
|
|
22410
22520
|
try {
|
|
22411
22521
|
response = await fetch(url2, {
|
|
@@ -22563,6 +22673,10 @@ var ProxySession = class {
|
|
|
22563
22673
|
this.nextId = 1;
|
|
22564
22674
|
this.sessionId = null;
|
|
22565
22675
|
this.closed = false;
|
|
22676
|
+
assertSafeProxyUrl(
|
|
22677
|
+
proxyUrl,
|
|
22678
|
+
options?.allowPrivateNetworks === true ? { allowPrivateNetworks: true } : void 0
|
|
22679
|
+
);
|
|
22566
22680
|
this.proxyUrl = proxyUrl.replace(/\/+$/, "") + "/mcp";
|
|
22567
22681
|
this.apiKey = apiKey;
|
|
22568
22682
|
this.requestTimeoutMs = options?.requestTimeoutMs ?? 6e4;
|
|
@@ -23554,6 +23668,8 @@ async function autoCreateProxyConfig(baseUrl, apiKey, serverName, entry, agentNa
|
|
|
23554
23668
|
const targetUrl = `stdio://${entry.command}/${entry.args.join("/")}`;
|
|
23555
23669
|
const url2 = `${baseUrl.replace(/\/+$/, "")}/api/v1/proxy/config`;
|
|
23556
23670
|
debugLog2(`[SHIELD] Auto-creating proxy config for "${serverName}".`);
|
|
23671
|
+
const allowPrivateNetworks = process.env["MULTICORN_ALLOW_PRIVATE_PROXY_HOSTS"] === "1";
|
|
23672
|
+
assertSafeProxyUrl(url2, { allowPrivateNetworks });
|
|
23557
23673
|
let response;
|
|
23558
23674
|
try {
|
|
23559
23675
|
response = await fetch(url2, {
|
|
@@ -23686,6 +23802,7 @@ async function runShieldExtension() {
|
|
|
23686
23802
|
`[SHIELD] Config read; ${String(serverCount)} MCP server(s) discovered (excluding Shield).`
|
|
23687
23803
|
);
|
|
23688
23804
|
debugLog2("[SHIELD] Resolving proxy configs (local config or API).");
|
|
23805
|
+
const allowPrivateNetworks = process.env["MULTICORN_ALLOW_PRIVATE_PROXY_HOSTS"] === "1";
|
|
23689
23806
|
let configs;
|
|
23690
23807
|
const localConfigs = await readProxyConfigsFromLocalMulticornConfig();
|
|
23691
23808
|
if (localConfigs.length > 0) {
|
|
@@ -23694,7 +23811,9 @@ async function runShieldExtension() {
|
|
|
23694
23811
|
} else {
|
|
23695
23812
|
debugLog2("[SHIELD] No local proxy configs; fetching from API.");
|
|
23696
23813
|
try {
|
|
23697
|
-
configs = await fetchProxyConfigs(baseUrl, apiKey, SETUP_TIMEOUT_MS
|
|
23814
|
+
configs = await fetchProxyConfigs(baseUrl, apiKey, SETUP_TIMEOUT_MS, {
|
|
23815
|
+
allowPrivateNetworks
|
|
23816
|
+
});
|
|
23698
23817
|
} catch (e) {
|
|
23699
23818
|
clearTimeout(setupTimeout);
|
|
23700
23819
|
if (e instanceof ProxyConfigFetchError) {
|
|
@@ -23728,7 +23847,9 @@ async function runShieldExtension() {
|
|
|
23728
23847
|
`[SHIELD] Auto-created ${String(createdCount)} proxy config(s); re-fetching from API.`
|
|
23729
23848
|
);
|
|
23730
23849
|
try {
|
|
23731
|
-
configs = await fetchProxyConfigs(baseUrl, apiKey, SETUP_TIMEOUT_MS
|
|
23850
|
+
configs = await fetchProxyConfigs(baseUrl, apiKey, SETUP_TIMEOUT_MS, {
|
|
23851
|
+
allowPrivateNetworks
|
|
23852
|
+
});
|
|
23732
23853
|
} catch (e) {
|
|
23733
23854
|
const message = e instanceof Error ? e.message : String(e);
|
|
23734
23855
|
debugLog2(`[SHIELD] Re-fetch after auto-creation failed: ${message}`);
|
|
@@ -23766,7 +23887,7 @@ async function runShieldExtension() {
|
|
|
23766
23887
|
const toolsByProxy = /* @__PURE__ */ new Map();
|
|
23767
23888
|
const sessionByProxyUrl = /* @__PURE__ */ new Map();
|
|
23768
23889
|
for (const cfg of configs) {
|
|
23769
|
-
const session = new ProxySession(cfg.proxy_url, apiKey);
|
|
23890
|
+
const session = new ProxySession(cfg.proxy_url, apiKey, { allowPrivateNetworks });
|
|
23770
23891
|
try {
|
|
23771
23892
|
debugLog2(`[SHIELD] Initializing proxy session for ${cfg.server_name}.`);
|
|
23772
23893
|
await session.initialize();
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "multicorn-shield",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "The control layer for AI agents: permissions, consent, spending limits, and audit logging.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"author": "Multicorn AI Pty Ltd",
|
|
6
7
|
"type": "module",
|
|
7
8
|
"main": "./dist/index.cjs",
|
|
8
9
|
"module": "./dist/index.js",
|
|
@@ -37,15 +38,25 @@
|
|
|
37
38
|
"dist",
|
|
38
39
|
"plugins/windsurf",
|
|
39
40
|
"LICENSE",
|
|
40
|
-
"README.md"
|
|
41
|
+
"README.md",
|
|
42
|
+
"CHANGELOG.md"
|
|
43
|
+
],
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public",
|
|
46
|
+
"provenance": true
|
|
47
|
+
},
|
|
48
|
+
"sideEffects": [
|
|
49
|
+
"dist/index.js",
|
|
50
|
+
"dist/index.cjs",
|
|
51
|
+
"dist/badge.js",
|
|
52
|
+
"src/badge/multicorn-badge.ts"
|
|
41
53
|
],
|
|
42
|
-
"sideEffects": false,
|
|
43
54
|
"engines": {
|
|
44
55
|
"node": ">=20"
|
|
45
56
|
},
|
|
46
57
|
"lint-staged": {
|
|
47
58
|
"*.ts": [
|
|
48
|
-
"eslint --fix",
|
|
59
|
+
"eslint --fix --no-warn-ignored",
|
|
49
60
|
"prettier --write"
|
|
50
61
|
],
|
|
51
62
|
"*.{json,md}": [
|
|
@@ -91,6 +102,11 @@
|
|
|
91
102
|
"path": "dist/index.cjs",
|
|
92
103
|
"limit": "50 kB",
|
|
93
104
|
"gzip": true
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"path": "dist/badge.js",
|
|
108
|
+
"limit": "5 kB",
|
|
109
|
+
"gzip": true
|
|
94
110
|
}
|
|
95
111
|
],
|
|
96
112
|
"keywords": [
|
|
@@ -117,8 +133,8 @@
|
|
|
117
133
|
"scripts": {
|
|
118
134
|
"build": "tsup",
|
|
119
135
|
"dev": "tsup --watch",
|
|
120
|
-
"lint": "eslint . && prettier --check .",
|
|
121
|
-
"lint:fix": "eslint --fix . && prettier --write .",
|
|
136
|
+
"lint": "eslint . --no-warn-ignored && prettier --check .",
|
|
137
|
+
"lint:fix": "eslint --fix . --no-warn-ignored && prettier --write .",
|
|
122
138
|
"test": "vitest run",
|
|
123
139
|
"test:watch": "vitest",
|
|
124
140
|
"test:coverage": "vitest run --coverage",
|