@suveren/gateway 0.4.1 → 0.6.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/bin/suveren-gateway.js +86 -5
- package/content/integrations/calendar.json +116 -22
- package/content/integrations/deploy-github.json +164 -0
- package/content/integrations/gmail.json +13 -4
- package/content/integrations/index.json +2 -1
- package/dist/control-plane/index.mjs +84 -7
- package/dist/mcp-server/http.mjs +147 -21
- package/dist/ui/assets/index-DecSrutK.js +105 -0
- package/dist/ui/index.html +1 -1
- package/node_modules/@hap/core/dist/index.d.mts +21 -0
- package/node_modules/@hap/core/dist/index.d.ts +21 -0
- package/node_modules/@hap/core/dist/index.js +17 -0
- package/node_modules/@hap/core/dist/index.mjs +17 -0
- package/node_modules/@hap/core/package.json +1 -1
- package/node_modules/@hap/core/src/gatekeeper.ts +28 -0
- package/node_modules/@hap/core/src/types.ts +21 -0
- package/node_modules/fast-uri/index.js +37 -1
- package/node_modules/fast-uri/package.json +1 -1
- package/node_modules/fast-uri/test/security.test.js +136 -0
- package/node_modules/hono/dist/cjs/helper/proxy/index.js +4 -0
- package/node_modules/hono/dist/cjs/jsx/base.js +1 -9
- package/node_modules/hono/dist/cjs/jsx/hooks/index.js +14 -11
- package/node_modules/hono/dist/cjs/middleware/cors/index.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/language/language.js +10 -6
- package/node_modules/hono/dist/cjs/utils/cookie.js +2 -1
- package/node_modules/hono/dist/helper/proxy/index.js +4 -0
- package/node_modules/hono/dist/jsx/base.js +1 -9
- package/node_modules/hono/dist/jsx/hooks/index.js +14 -11
- package/node_modules/hono/dist/middleware/cors/index.js +1 -1
- package/node_modules/hono/dist/middleware/language/language.js +10 -6
- package/node_modules/hono/dist/utils/cookie.js +2 -1
- package/node_modules/hono/package.json +2 -3
- package/node_modules/ip-address/README.md +142 -128
- package/node_modules/ip-address/dist/common.d.ts +6 -0
- package/node_modules/ip-address/dist/common.js +17 -1
- package/node_modules/ip-address/dist/common.js.map +1 -1
- package/node_modules/ip-address/dist/ipv4.js +3 -12
- package/node_modules/ip-address/dist/ipv4.js.map +1 -1
- package/node_modules/ip-address/dist/ipv6.d.ts +9 -2
- package/node_modules/ip-address/dist/ipv6.js +35 -16
- package/node_modules/ip-address/dist/ipv6.js.map +1 -1
- package/node_modules/ip-address/package.json +5 -3
- package/node_modules/jose/README.md +13 -13
- package/node_modules/jose/dist/types/index.d.ts +6 -5
- package/node_modules/jose/dist/types/jwe/compact/decrypt.d.ts +17 -11
- package/node_modules/jose/dist/types/jwe/compact/encrypt.d.ts +7 -19
- package/node_modules/jose/dist/types/jwe/flattened/decrypt.d.ts +17 -11
- package/node_modules/jose/dist/types/jwe/flattened/encrypt.d.ts +7 -19
- package/node_modules/jose/dist/types/jwe/general/decrypt.d.ts +18 -13
- package/node_modules/jose/dist/types/jwe/general/encrypt.d.ts +20 -21
- package/node_modules/jose/dist/types/jwk/embedded.d.ts +8 -12
- package/node_modules/jose/dist/types/jwk/thumbprint.d.ts +0 -15
- package/node_modules/jose/dist/types/jwks/local.d.ts +12 -21
- package/node_modules/jose/dist/types/jwks/remote.d.ts +34 -165
- package/node_modules/jose/dist/types/jws/compact/sign.d.ts +2 -13
- package/node_modules/jose/dist/types/jws/compact/verify.d.ts +17 -13
- package/node_modules/jose/dist/types/jws/flattened/sign.d.ts +2 -13
- package/node_modules/jose/dist/types/jws/flattened/verify.d.ts +17 -13
- package/node_modules/jose/dist/types/jws/general/sign.d.ts +16 -17
- package/node_modules/jose/dist/types/jws/general/verify.d.ts +18 -15
- package/node_modules/jose/dist/types/jwt/decrypt.d.ts +17 -11
- package/node_modules/jose/dist/types/jwt/encrypt.d.ts +10 -34
- package/node_modules/jose/dist/types/jwt/sign.d.ts +2 -13
- package/node_modules/jose/dist/types/jwt/unsecured.d.ts +4 -13
- package/node_modules/jose/dist/types/jwt/verify.d.ts +14 -13
- package/node_modules/jose/dist/types/key/export.d.ts +0 -14
- package/node_modules/jose/dist/types/key/generate_key_pair.d.ts +7 -14
- package/node_modules/jose/dist/types/key/generate_secret.d.ts +16 -14
- package/node_modules/jose/dist/types/key/import.d.ts +18 -28
- package/node_modules/jose/dist/types/types.d.ts +239 -222
- package/node_modules/jose/dist/types/util/base64url.d.ts +11 -4
- package/node_modules/jose/dist/types/util/decode_jwt.d.ts +1 -9
- package/node_modules/jose/dist/types/util/decode_protected_header.d.ts +0 -8
- package/node_modules/jose/dist/types/util/errors.d.ts +128 -121
- package/node_modules/jose/dist/webapi/jwe/compact/decrypt.js +4 -22
- package/node_modules/jose/dist/webapi/jwe/flattened/decrypt.js +5 -153
- package/node_modules/jose/dist/webapi/jwe/flattened/encrypt.js +16 -111
- package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +15 -3
- package/node_modules/jose/dist/webapi/jwe/general/encrypt.js +65 -81
- package/node_modules/jose/dist/webapi/jwk/embedded.js +5 -3
- package/node_modules/jose/dist/webapi/jwks/local.js +31 -72
- package/node_modules/jose/dist/webapi/jwks/remote.js +19 -28
- package/node_modules/jose/dist/webapi/jws/compact/sign.js +5 -2
- package/node_modules/jose/dist/webapi/jws/compact/verify.js +4 -16
- package/node_modules/jose/dist/webapi/jws/flattened/sign.js +7 -65
- package/node_modules/jose/dist/webapi/jws/flattened/verify.js +3 -89
- package/node_modules/jose/dist/webapi/jws/general/sign.js +19 -15
- package/node_modules/jose/dist/webapi/jws/general/verify.js +29 -9
- package/node_modules/jose/dist/webapi/jwt/decrypt.js +5 -5
- package/node_modules/jose/dist/webapi/jwt/sign.js +2 -3
- package/node_modules/jose/dist/webapi/jwt/unsecured.js +4 -3
- package/node_modules/jose/dist/webapi/jwt/verify.js +6 -6
- package/node_modules/jose/dist/webapi/key/export.js +37 -4
- package/node_modules/jose/dist/webapi/key/generate_key_pair.js +33 -82
- package/node_modules/jose/dist/webapi/key/import.js +8 -8
- package/node_modules/jose/dist/webapi/lib/asn1.js +53 -122
- package/node_modules/jose/dist/webapi/lib/buffer_utils.js +1 -0
- package/node_modules/jose/dist/webapi/lib/content_encryption.js +34 -109
- package/node_modules/jose/dist/webapi/lib/crypto_key.js +18 -126
- package/node_modules/jose/dist/webapi/lib/helpers.js +23 -0
- package/node_modules/jose/dist/webapi/lib/invalid_key_input.js +0 -1
- package/node_modules/jose/dist/webapi/lib/jwe_algorithms.js +116 -0
- package/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +186 -0
- package/node_modules/jose/dist/webapi/lib/jwe_encrypt.js +110 -0
- package/node_modules/jose/dist/webapi/lib/jwk_to_key.js +8 -98
- package/node_modules/jose/dist/webapi/lib/jws_algorithms.js +76 -0
- package/node_modules/jose/dist/webapi/lib/jws_sign.js +74 -0
- package/node_modules/jose/dist/webapi/lib/jws_verify.js +107 -0
- package/node_modules/jose/dist/webapi/lib/jwt_claims_set.js +68 -109
- package/node_modules/jose/dist/webapi/lib/key.js +132 -0
- package/node_modules/jose/dist/webapi/lib/key_algorithm.js +10 -0
- package/node_modules/jose/dist/webapi/lib/key_descriptor.js +7 -0
- package/node_modules/jose/dist/webapi/lib/key_management.js +162 -92
- package/node_modules/jose/dist/webapi/lib/options.js +48 -0
- package/node_modules/jose/dist/webapi/lib/signing.js +14 -56
- package/node_modules/jose/dist/webapi/lib/type_checks.js +13 -18
- package/node_modules/jose/dist/webapi/util/base64url.js +13 -4
- package/node_modules/jose/dist/webapi/util/decode_jwt.js +2 -2
- package/node_modules/jose/dist/webapi/util/decode_protected_header.js +5 -15
- package/node_modules/jose/dist/webapi/util/errors.js +1 -1
- package/node_modules/jose/package.json +2 -4
- package/package.json +2 -2
- package/profiles/deploy/0.6.profile.json +168 -0
- package/profiles/deploy/0.7.profile.json +168 -0
- package/profiles/email/0.4.profile.json +9 -2
- package/profiles/index.json +3 -1
- package/dist/ui/assets/index-Cog54HOo.js +0 -105
- package/node_modules/jose/dist/webapi/lib/aesgcmkw.js +0 -15
- package/node_modules/jose/dist/webapi/lib/aeskw.js +0 -25
- package/node_modules/jose/dist/webapi/lib/check_key_type.js +0 -122
- package/node_modules/jose/dist/webapi/lib/ecdhes.js +0 -52
- package/node_modules/jose/dist/webapi/lib/key_to_jwk.js +0 -34
- package/node_modules/jose/dist/webapi/lib/normalize_key.js +0 -166
- package/node_modules/jose/dist/webapi/lib/pbes2kw.js +0 -42
- package/node_modules/jose/dist/webapi/lib/rsaes.js +0 -24
- package/node_modules/jose/dist/webapi/lib/validate_algorithms.js +0 -10
- package/node_modules/jose/dist/webapi/lib/validate_crit.js +0 -33
|
@@ -356,6 +356,7 @@ async function activateIntegration(manifest) {
|
|
|
356
356
|
profile: manifest.profile,
|
|
357
357
|
toolGating: manifest.toolGating,
|
|
358
358
|
npmPackage: manifest.npmPackage,
|
|
359
|
+
...typeof manifest.readPolicy?.defaultAgeDays === "number" ? { readAgeDays: manifest.readPolicy.defaultAgeDays } : {},
|
|
359
360
|
enabled: true
|
|
360
361
|
});
|
|
361
362
|
}
|
|
@@ -391,6 +392,18 @@ async function removeIntegration(id) {
|
|
|
391
392
|
}
|
|
392
393
|
return res.json();
|
|
393
394
|
}
|
|
395
|
+
async function setReadPolicy(id, readAgeDays) {
|
|
396
|
+
const res = await fetch(`${MCP_BASE}/internal/integration/${encodeURIComponent(id)}/read-policy`, {
|
|
397
|
+
method: "PATCH",
|
|
398
|
+
headers: { ...internalHeaders(), "Content-Type": "application/json" },
|
|
399
|
+
body: JSON.stringify({ readAgeDays })
|
|
400
|
+
});
|
|
401
|
+
if (!res.ok) {
|
|
402
|
+
const err = await res.json().catch(() => ({ error: "Unknown error" }));
|
|
403
|
+
throw new Error(err.error);
|
|
404
|
+
}
|
|
405
|
+
return res.json();
|
|
406
|
+
}
|
|
394
407
|
async function getGateContent(path) {
|
|
395
408
|
const qs = path ? `?path=${encodeURIComponent(path)}` : "";
|
|
396
409
|
const res = await fetch(`${MCP_BASE}/internal/gate-content${qs}`, {
|
|
@@ -1364,6 +1377,30 @@ function requireAuthQueryOrHeader(vault2) {
|
|
|
1364
1377
|
};
|
|
1365
1378
|
}
|
|
1366
1379
|
|
|
1380
|
+
// src/lib/read-policy-defaults.ts
|
|
1381
|
+
async function backfillReadPolicyDefaults(data) {
|
|
1382
|
+
const defaults = /* @__PURE__ */ new Map();
|
|
1383
|
+
for (const m of data.manifests ?? []) {
|
|
1384
|
+
const days = m.readPolicy?.defaultAgeDays;
|
|
1385
|
+
if (typeof m.id === "string" && typeof days === "number" && Number.isInteger(days) && days >= 0) {
|
|
1386
|
+
defaults.set(m.id, days);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
if (defaults.size === 0) return;
|
|
1390
|
+
try {
|
|
1391
|
+
const current = await getIntegrations();
|
|
1392
|
+
for (const integration of current.integrations ?? []) {
|
|
1393
|
+
const fallback = defaults.get(integration.id);
|
|
1394
|
+
if (fallback === void 0) continue;
|
|
1395
|
+
if (integration.readAgeDays != null) continue;
|
|
1396
|
+
await setReadPolicy(integration.id, fallback);
|
|
1397
|
+
console.error(`[Control Plane] Read policy default applied: ${integration.id} \u2192 ${fallback}d`);
|
|
1398
|
+
}
|
|
1399
|
+
} catch (err) {
|
|
1400
|
+
console.error(`[Control Plane] \u26A0 Could not apply read-policy defaults: ${err instanceof Error ? err.message : err}`);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1367
1404
|
// src/routes/mcp.ts
|
|
1368
1405
|
import { Router as Router5 } from "express";
|
|
1369
1406
|
|
|
@@ -1563,16 +1600,23 @@ function createAutostartRouter() {
|
|
|
1563
1600
|
}
|
|
1564
1601
|
|
|
1565
1602
|
// src/lib/desktop-notify.ts
|
|
1566
|
-
import { spawn } from "child_process";
|
|
1603
|
+
import { spawn, spawnSync } from "child_process";
|
|
1567
1604
|
function osaQuote(value) {
|
|
1568
1605
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1569
1606
|
}
|
|
1570
1607
|
function psQuote(value) {
|
|
1571
1608
|
return `'${value.replace(/'/g, "''")}'`;
|
|
1572
1609
|
}
|
|
1573
|
-
function buildNotifyCommand(platform, title, message) {
|
|
1610
|
+
function buildNotifyCommand(platform, title, message, options = {}) {
|
|
1611
|
+
const { url, hasTerminalNotifier: hasTerminalNotifier2 = false } = options;
|
|
1574
1612
|
switch (platform) {
|
|
1575
1613
|
case "darwin":
|
|
1614
|
+
if (url && hasTerminalNotifier2) {
|
|
1615
|
+
return {
|
|
1616
|
+
cmd: "terminal-notifier",
|
|
1617
|
+
args: ["-title", title, "-message", message, "-open", url, "-group", "ai.suveren.gateway"]
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1576
1620
|
return {
|
|
1577
1621
|
cmd: "osascript",
|
|
1578
1622
|
args: ["-e", `display notification ${osaQuote(message)} with title ${osaQuote(title)}`]
|
|
@@ -1594,13 +1638,30 @@ function buildNotifyCommand(platform, title, message) {
|
|
|
1594
1638
|
}
|
|
1595
1639
|
}
|
|
1596
1640
|
function lockedNotification(port2) {
|
|
1641
|
+
const url = `http://localhost:${port2}`;
|
|
1597
1642
|
return {
|
|
1598
1643
|
title: "Suveren is running but locked",
|
|
1599
|
-
message: `Your agent has no authority until you unlock it. Open
|
|
1644
|
+
message: `Your agent has no authority until you unlock it. Open ${url} and enter your API key.`,
|
|
1645
|
+
url
|
|
1600
1646
|
};
|
|
1601
1647
|
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1648
|
+
var terminalNotifierCache = null;
|
|
1649
|
+
function hasTerminalNotifier() {
|
|
1650
|
+
if (terminalNotifierCache !== null) return terminalNotifierCache;
|
|
1651
|
+
try {
|
|
1652
|
+
const probe = spawnSync("terminal-notifier", ["-help"], { stdio: "ignore" });
|
|
1653
|
+
terminalNotifierCache = probe.error === void 0;
|
|
1654
|
+
} catch {
|
|
1655
|
+
terminalNotifierCache = false;
|
|
1656
|
+
}
|
|
1657
|
+
return terminalNotifierCache;
|
|
1658
|
+
}
|
|
1659
|
+
function notify(title, message, platform = process.platform, url) {
|
|
1660
|
+
const command = buildNotifyCommand(platform, title, message, {
|
|
1661
|
+
url,
|
|
1662
|
+
// Only probe when a URL could actually be attached.
|
|
1663
|
+
hasTerminalNotifier: url !== void 0 && platform === "darwin" ? hasTerminalNotifier() : false
|
|
1664
|
+
});
|
|
1604
1665
|
if (!command) return;
|
|
1605
1666
|
try {
|
|
1606
1667
|
const child = spawn(command.cmd, command.args, { detached: true, stdio: "ignore" });
|
|
@@ -2195,6 +2256,21 @@ app.use("/autostart", jsonParser, authGuard, createAutostartRouter());
|
|
|
2195
2256
|
app.use("/api/encrypt-intent", jsonParser, authGuard, createEncryptIntentRouter());
|
|
2196
2257
|
app.use("/api/decrypt-intent", jsonParser, authGuard, createDecryptIntentRouter(vault));
|
|
2197
2258
|
app.use("/api/approved-intents", jsonParser, authGuard, createApprovedIntentsRouter(vault));
|
|
2259
|
+
app.put("/integrations/:id/read-policy", jsonParser, authGuard, async (req, res) => {
|
|
2260
|
+
const id = req.params.id;
|
|
2261
|
+
const { readAgeDays } = req.body ?? {};
|
|
2262
|
+
if (readAgeDays !== null && !(typeof readAgeDays === "number" && Number.isInteger(readAgeDays) && readAgeDays >= 0)) {
|
|
2263
|
+
res.status(400).json({ error: "readAgeDays must be a non-negative integer, or null to clear it" });
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
try {
|
|
2267
|
+
const result = await setReadPolicy(id, readAgeDays);
|
|
2268
|
+
res.json(result);
|
|
2269
|
+
} catch (err) {
|
|
2270
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
2271
|
+
res.status(502).json({ error: `Could not set read policy: ${message}` });
|
|
2272
|
+
}
|
|
2273
|
+
});
|
|
2198
2274
|
app.get("/integrations/:id/discover/:field", authGuard, async (req, res) => {
|
|
2199
2275
|
const integrationId = req.params.id;
|
|
2200
2276
|
const fieldName = req.params.field;
|
|
@@ -2494,8 +2570,8 @@ if (existsSync5(UI_DIST)) {
|
|
|
2494
2570
|
app.listen(port, "0.0.0.0", () => {
|
|
2495
2571
|
console.error(`[Control Plane] Listening on http://0.0.0.0:${port}`);
|
|
2496
2572
|
if (process.env.SUVEREN_AUTOSTART === "1" && !vault.isUnlocked()) {
|
|
2497
|
-
const { title, message } = lockedNotification(port);
|
|
2498
|
-
notify(title, message);
|
|
2573
|
+
const { title, message, url } = lockedNotification(port);
|
|
2574
|
+
notify(title, message, process.platform, url);
|
|
2499
2575
|
}
|
|
2500
2576
|
console.error(`[Control Plane] SP proxy: ${SP_URL2}`);
|
|
2501
2577
|
console.error(`[Control Plane] UI dist: ${UI_DIST}`);
|
|
@@ -2507,6 +2583,7 @@ app.listen(port, "0.0.0.0", () => {
|
|
|
2507
2583
|
console.error(`[Control Plane] \u26A0 MCP server at ${MCP_BASE} returned 0 integration manifests \u2014 wrong SUVEREN_MCP_INTERNAL_URL? (dev MCP is :3431, npm is :3430)`);
|
|
2508
2584
|
} else {
|
|
2509
2585
|
console.error(`[Control Plane] Integrations: ${n} manifests loaded from ${MCP_BASE}`);
|
|
2586
|
+
void backfillReadPolicyDefaults(d);
|
|
2510
2587
|
}
|
|
2511
2588
|
}).catch((err) => {
|
|
2512
2589
|
console.error(`[Control Plane] \u26A0 Could not reach MCP server at ${MCP_BASE} for manifests \u2014 wrong SUVEREN_MCP_INTERNAL_URL? (dev=:3431, npm=:3430). ${err instanceof Error ? err.message : err}`);
|
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -1157,9 +1157,11 @@ function computeContentBinding(profileId, tool, toolArgs) {
|
|
|
1157
1157
|
if (binding.kind === "jcs") {
|
|
1158
1158
|
contentHash = computeContentHash(binding, toolArgs);
|
|
1159
1159
|
} else {
|
|
1160
|
-
const
|
|
1160
|
+
const declared = tool?.gating?.contentField;
|
|
1161
|
+
const field = declared ?? (tool ? detectContentField(tool) : null);
|
|
1161
1162
|
if (!field) return void 0;
|
|
1162
1163
|
const raw = typeof toolArgs[field] === "string" ? toolArgs[field] : "";
|
|
1164
|
+
if (raw === "") return void 0;
|
|
1163
1165
|
contentHash = computeContentHash(binding, raw);
|
|
1164
1166
|
}
|
|
1165
1167
|
return {
|
|
@@ -1332,6 +1334,29 @@ function getByDottedPath(obj, path) {
|
|
|
1332
1334
|
obj
|
|
1333
1335
|
);
|
|
1334
1336
|
}
|
|
1337
|
+
function firstParsableDate(obj, paths) {
|
|
1338
|
+
for (const path of Array.isArray(paths) ? paths : [paths]) {
|
|
1339
|
+
const parsed = parseMessageTimestamp(getByDottedPath(obj, path));
|
|
1340
|
+
if (parsed !== null) return parsed;
|
|
1341
|
+
}
|
|
1342
|
+
return null;
|
|
1343
|
+
}
|
|
1344
|
+
function renderAgeConstraint(template, maxAgeDays, nowMs) {
|
|
1345
|
+
const floorIso = new Date(nowMs - maxAgeDays * MS_PER_DAY).toISOString();
|
|
1346
|
+
return template.replace(/\{days\}/g, String(maxAgeDays)).replace(/\{date\}/g, floorIso.slice(0, 10));
|
|
1347
|
+
}
|
|
1348
|
+
function ageFloorValue(maxAgeDays, nowMs, format = "iso") {
|
|
1349
|
+
const floorMs = nowMs - maxAgeDays * MS_PER_DAY;
|
|
1350
|
+
if (format === "epoch_ms") return floorMs;
|
|
1351
|
+
if (format === "epoch_s") return String(Math.floor(floorMs / 1e3));
|
|
1352
|
+
return new Date(floorMs).toISOString();
|
|
1353
|
+
}
|
|
1354
|
+
function clampAgeFloor(requested, maxAgeDays, nowMs, format = "iso") {
|
|
1355
|
+
const floorMs = nowMs - maxAgeDays * MS_PER_DAY;
|
|
1356
|
+
const asked = parseMessageTimestamp(requested);
|
|
1357
|
+
if (asked !== null && asked >= floorMs) return requested;
|
|
1358
|
+
return ageFloorValue(maxAgeDays, nowMs, format);
|
|
1359
|
+
}
|
|
1335
1360
|
|
|
1336
1361
|
// src/lib/tool-proxy.ts
|
|
1337
1362
|
import { getProfile as getProfile3 } from "@hap/core";
|
|
@@ -1422,6 +1447,22 @@ function denyRead(state2, tool, reason, detail, target) {
|
|
|
1422
1447
|
return { content: [{ type: "text", text: `Read blocked by Gatekeeper: ${detail}` }], isError: true };
|
|
1423
1448
|
}
|
|
1424
1449
|
function createGatedToolHandler(tool, integrationManager2, state2) {
|
|
1450
|
+
const inner = createGatedToolHandlerInner(tool, integrationManager2, state2);
|
|
1451
|
+
const blocked = tool.gating?.blockedArgs ?? [];
|
|
1452
|
+
if (blocked.length === 0) return inner;
|
|
1453
|
+
return async (args) => {
|
|
1454
|
+
const present = blocked.filter((a) => a in (args ?? {}));
|
|
1455
|
+
if (present.length === 0) return inner(args);
|
|
1456
|
+
return {
|
|
1457
|
+
content: [{
|
|
1458
|
+
type: "text",
|
|
1459
|
+
text: `Refused: "${tool.namespacedName}" was called with ${present.map((b) => `"${b}"`).join(", ")}, which the integration manifest blocks. That argument carries the whole request in one opaque value, so the recipients, the content and the limits on this authorization cannot be read from it \u2014 the call could not be shown to stay in scope, and could not be shown to a human for approval in any form they could judge. Use the individual arguments instead.`
|
|
1460
|
+
}],
|
|
1461
|
+
isError: true
|
|
1462
|
+
};
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
1425
1466
|
if (!tool.gating || !tool.gating.profile) {
|
|
1426
1467
|
return async () => {
|
|
1427
1468
|
return {
|
|
@@ -1494,18 +1535,20 @@ function createGatedToolHandler(tool, integrationManager2, state2) {
|
|
|
1494
1535
|
const readProfile = readAdapter ? getProfile3(matchingAuths[0].profileId) : void 0;
|
|
1495
1536
|
const boundsSchema = readProfile?.boundsSchema;
|
|
1496
1537
|
const ageBoundField = readAdapter?.ageField ? resolveAgeBoundField(boundsSchema, readAdapter.ageField) : null;
|
|
1538
|
+
const hasAgeDimension = Boolean(readAdapter?.ageField);
|
|
1497
1539
|
let readMaxAge = null;
|
|
1498
|
-
if (
|
|
1499
|
-
|
|
1540
|
+
if (hasAgeDimension) {
|
|
1541
|
+
const localAge = integrationManager2.getReadAgeDays(tool.integrationId);
|
|
1542
|
+
readMaxAge = localAge ?? (ageBoundField ? maxReadAgeDays(
|
|
1500
1543
|
matchingAuths.map((a) => a.bounds ?? a.frame),
|
|
1501
1544
|
ageBoundField
|
|
1502
|
-
);
|
|
1545
|
+
) : null);
|
|
1503
1546
|
if (readMaxAge === null) {
|
|
1504
1547
|
return denyRead(
|
|
1505
1548
|
state2,
|
|
1506
1549
|
tool,
|
|
1507
1550
|
"unset_age",
|
|
1508
|
-
`no read-age window is set on your authorization
|
|
1551
|
+
`no read-age window is set for this integration${ageBoundField ? ` (or on your authorization's ${ageBoundField})` : ""}. Reads are not permitted with an unbounded window \u2014 set how far back the agent may read under the integration's Read policy, then try again. Nothing was read.`
|
|
1509
1552
|
);
|
|
1510
1553
|
}
|
|
1511
1554
|
}
|
|
@@ -1540,10 +1583,21 @@ function createGatedToolHandler(tool, integrationManager2, state2) {
|
|
|
1540
1583
|
if (readAdapter?.pinnedArgs) {
|
|
1541
1584
|
outgoing = { ...outgoing, ...readAdapter.pinnedArgs };
|
|
1542
1585
|
}
|
|
1586
|
+
if (readAdapter?.ageFloorArg && hasAgeDimension && readMaxAge !== null) {
|
|
1587
|
+
outgoing = {
|
|
1588
|
+
...outgoing,
|
|
1589
|
+
[readAdapter.ageFloorArg]: clampAgeFloor(
|
|
1590
|
+
outgoing[readAdapter.ageFloorArg],
|
|
1591
|
+
readMaxAge,
|
|
1592
|
+
Date.now(),
|
|
1593
|
+
readAdapter.ageFloorFormat
|
|
1594
|
+
)
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1543
1597
|
if (readAdapter?.queryArg) {
|
|
1544
1598
|
const clauses = [];
|
|
1545
|
-
if (
|
|
1546
|
-
clauses.push(readAdapter.ageConstraint
|
|
1599
|
+
if (hasAgeDimension && readAdapter.ageConstraint && readMaxAge !== null) {
|
|
1600
|
+
clauses.push(renderAgeConstraint(readAdapter.ageConstraint, readMaxAge, Date.now()));
|
|
1547
1601
|
}
|
|
1548
1602
|
if (clauses.length > 0) {
|
|
1549
1603
|
const base = typeof args[readAdapter.queryArg] === "string" ? args[readAdapter.queryArg] : "";
|
|
@@ -1581,10 +1635,10 @@ function createGatedToolHandler(tool, integrationManager2, state2) {
|
|
|
1581
1635
|
);
|
|
1582
1636
|
}
|
|
1583
1637
|
}
|
|
1584
|
-
if (readAdapter &&
|
|
1638
|
+
if (readAdapter && hasAgeDimension && readAdapter.resultDatePath && readMaxAge !== null) {
|
|
1585
1639
|
const parsed = parseFirstJson(result);
|
|
1586
|
-
const
|
|
1587
|
-
if (isOlderThanMaxAge(
|
|
1640
|
+
const itemDate = parsed === void 0 ? null : firstParsableDate(parsed, readAdapter.resultDatePath);
|
|
1641
|
+
if (isOlderThanMaxAge(itemDate, readMaxAge, Date.now())) {
|
|
1588
1642
|
return denyRead(
|
|
1589
1643
|
state2,
|
|
1590
1644
|
tool,
|
|
@@ -2571,6 +2625,23 @@ function ensureIntegrationsDir() {
|
|
|
2571
2625
|
writeFileSync5(pkgPath, JSON.stringify({ name: "suveren-integrations", version: "1.0.0", private: true }, null, 2));
|
|
2572
2626
|
}
|
|
2573
2627
|
}
|
|
2628
|
+
function readAgeOf(config) {
|
|
2629
|
+
const days = config.readAgeDays;
|
|
2630
|
+
return typeof days === "number" && Number.isFinite(days) && days >= 0 ? days : null;
|
|
2631
|
+
}
|
|
2632
|
+
function withoutBlockedArgs(schema, blocked) {
|
|
2633
|
+
if (!blocked?.length) return schema;
|
|
2634
|
+
const props = schema.properties;
|
|
2635
|
+
if (!props) return schema;
|
|
2636
|
+
const kept = Object.fromEntries(Object.entries(props).filter(([k]) => !blocked.includes(k)));
|
|
2637
|
+
const out = { ...schema, properties: kept };
|
|
2638
|
+
if (Array.isArray(schema.required)) {
|
|
2639
|
+
out.required = schema.required.filter(
|
|
2640
|
+
(r) => typeof r !== "string" || !blocked.includes(r)
|
|
2641
|
+
);
|
|
2642
|
+
}
|
|
2643
|
+
return out;
|
|
2644
|
+
}
|
|
2574
2645
|
var RESPAWN_DELAYS = [2e3, 4e3, 6e3];
|
|
2575
2646
|
var MAX_RESPAWN_ATTEMPTS = 3;
|
|
2576
2647
|
var CONNECT_TIMEOUT_MS = 3e4;
|
|
@@ -2766,7 +2837,14 @@ var IntegrationManager = class {
|
|
|
2766
2837
|
namespacedName: `${config.id}__${tool.name}`,
|
|
2767
2838
|
integrationId: config.id,
|
|
2768
2839
|
description: tool.description ?? "",
|
|
2769
|
-
|
|
2840
|
+
// A downstream schema otherwise reaches the agent verbatim, so a
|
|
2841
|
+
// connector can offer a control-bypassing argument and we would pass it
|
|
2842
|
+
// straight on. Anything the manifest blocks is removed here, before the
|
|
2843
|
+
// agent ever sees it exists.
|
|
2844
|
+
inputSchema: withoutBlockedArgs(
|
|
2845
|
+
tool.inputSchema ?? {},
|
|
2846
|
+
gating?.blockedArgs
|
|
2847
|
+
),
|
|
2770
2848
|
gating
|
|
2771
2849
|
};
|
|
2772
2850
|
});
|
|
@@ -2838,14 +2916,32 @@ var IntegrationManager = class {
|
|
|
2838
2916
|
return tools;
|
|
2839
2917
|
}
|
|
2840
2918
|
/**
|
|
2841
|
-
*
|
|
2842
|
-
*
|
|
2843
|
-
*
|
|
2844
|
-
*
|
|
2919
|
+
* The LOCAL read-age window (days) for a running integration, or null when
|
|
2920
|
+
* none is set here (the read path then falls back to the signed grant bound
|
|
2921
|
+
* — see `IntegrationConfig.readAgeDays`).
|
|
2922
|
+
*
|
|
2923
|
+
* 0 is a real value ("read nothing") and MUST survive this call, so the
|
|
2924
|
+
* guard tests the type, never truthiness.
|
|
2845
2925
|
*/
|
|
2846
2926
|
getReadAgeDays(integrationId) {
|
|
2847
|
-
const
|
|
2848
|
-
return
|
|
2927
|
+
const config = this.running.get(integrationId)?.config;
|
|
2928
|
+
return config ? readAgeOf(config) : null;
|
|
2929
|
+
}
|
|
2930
|
+
/**
|
|
2931
|
+
* Apply a new local read-age window to the RUNNING snapshot, so the change
|
|
2932
|
+
* takes effect on the next read without restarting the subprocess. Read
|
|
2933
|
+
* policy is enforced gateway-side and never reaches the downstream MCP
|
|
2934
|
+
* server, so nothing has to be re-spawned.
|
|
2935
|
+
*
|
|
2936
|
+
* Persistence is the registry's job — callers update both. Returns false if
|
|
2937
|
+
* the integration is not running (registry-only update still applies).
|
|
2938
|
+
*/
|
|
2939
|
+
setReadAgeDays(integrationId, days) {
|
|
2940
|
+
const entry = this.running.get(integrationId);
|
|
2941
|
+
if (!entry) return false;
|
|
2942
|
+
if (days === null) delete entry.config.readAgeDays;
|
|
2943
|
+
else entry.config.readAgeDays = days;
|
|
2944
|
+
return true;
|
|
2849
2945
|
}
|
|
2850
2946
|
/**
|
|
2851
2947
|
* Get status info for all known integrations.
|
|
@@ -2857,7 +2953,8 @@ var IntegrationManager = class {
|
|
|
2857
2953
|
id: entry.config.id,
|
|
2858
2954
|
name: entry.config.name,
|
|
2859
2955
|
running: true,
|
|
2860
|
-
toolCount: entry.tools.length
|
|
2956
|
+
toolCount: entry.tools.length,
|
|
2957
|
+
readAgeDays: readAgeOf(entry.config)
|
|
2861
2958
|
});
|
|
2862
2959
|
}
|
|
2863
2960
|
if (allConfigs) {
|
|
@@ -2867,7 +2964,8 @@ var IntegrationManager = class {
|
|
|
2867
2964
|
id: config.id,
|
|
2868
2965
|
name: config.name,
|
|
2869
2966
|
running: false,
|
|
2870
|
-
toolCount: 0
|
|
2967
|
+
toolCount: 0,
|
|
2968
|
+
readAgeDays: readAgeOf(config)
|
|
2871
2969
|
});
|
|
2872
2970
|
}
|
|
2873
2971
|
}
|
|
@@ -2951,13 +3049,20 @@ var IntegrationManager = class {
|
|
|
2951
3049
|
requiredValue: ext.requiredValue,
|
|
2952
3050
|
read: ext.read,
|
|
2953
3051
|
readGovernance: ext.readGovernance,
|
|
2954
|
-
readGovernanceReason: ext.readGovernanceReason
|
|
3052
|
+
readGovernanceReason: ext.readGovernanceReason,
|
|
3053
|
+
blockedArgs: ext.blockedArgs
|
|
2955
3054
|
};
|
|
2956
3055
|
}
|
|
2957
3056
|
return {
|
|
2958
3057
|
profile: profileId,
|
|
2959
3058
|
executionMapping: override.executionMapping,
|
|
2960
|
-
staticExecution: override.staticExecution
|
|
3059
|
+
staticExecution: override.staticExecution,
|
|
3060
|
+
// Which argument the receipt binds to. This resolver is an explicit
|
|
3061
|
+
// whitelist, so a field added to the manifest and the type but not
|
|
3062
|
+
// copied here is silently dropped — the receipt is still issued and
|
|
3063
|
+
// simply carries no binding, which only surfaces when a verifier asks.
|
|
3064
|
+
contentField: ext.contentField,
|
|
3065
|
+
blockedArgs: ext.blockedArgs
|
|
2961
3066
|
};
|
|
2962
3067
|
}
|
|
2963
3068
|
return {
|
|
@@ -3325,6 +3430,27 @@ app.delete("/internal/remove-integration/:id", internalOnly, async (req, res) =>
|
|
|
3325
3430
|
console.error(`[Suveren MCP] Integration ${id} removed`);
|
|
3326
3431
|
res.json({ ok: true, id });
|
|
3327
3432
|
});
|
|
3433
|
+
app.patch("/internal/integration/:id/read-policy", internalOnly, (req, res) => {
|
|
3434
|
+
const id = req.params.id;
|
|
3435
|
+
const raw = req.body.readAgeDays;
|
|
3436
|
+
let readAgeDays;
|
|
3437
|
+
if (raw === null) {
|
|
3438
|
+
readAgeDays = null;
|
|
3439
|
+
} else if (typeof raw === "number" && Number.isInteger(raw) && raw >= 0) {
|
|
3440
|
+
readAgeDays = raw;
|
|
3441
|
+
} else {
|
|
3442
|
+
res.status(400).json({ error: "readAgeDays must be a non-negative integer, or null to clear it" });
|
|
3443
|
+
return;
|
|
3444
|
+
}
|
|
3445
|
+
const updated = integrationRegistry.update(id, { readAgeDays: readAgeDays ?? void 0 });
|
|
3446
|
+
if (!updated) {
|
|
3447
|
+
res.status(404).json({ error: `Integration "${id}" not found` });
|
|
3448
|
+
return;
|
|
3449
|
+
}
|
|
3450
|
+
integrationManager.setReadAgeDays(id, readAgeDays);
|
|
3451
|
+
console.error(`[Suveren MCP] Read policy for ${id}: ${readAgeDays === null ? "cleared" : `${readAgeDays}d`}`);
|
|
3452
|
+
res.json({ ok: true, id, readAgeDays });
|
|
3453
|
+
});
|
|
3328
3454
|
app.get("/internal/integrations", internalOnly, (_req, res) => {
|
|
3329
3455
|
const configs = integrationRegistry.getAll();
|
|
3330
3456
|
const statuses = integrationManager.getStatus(configs);
|