@suveren/gateway 0.7.4 → 0.8.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/content/integrations/mollie.json +103 -28
- package/dist/control-plane/index.mjs +248 -58
- package/dist/mcp-server/http.mjs +577 -124
- package/dist/ui/assets/index-BFnSVa9N.js +110 -0
- package/dist/ui/assets/{index-CkWaRck1.css → index-BYVb_0oG.css} +1 -1
- package/dist/ui/index.html +2 -2
- package/node_modules/@hap/core/dist/index.d.mts +285 -13
- package/node_modules/@hap/core/dist/index.d.ts +285 -13
- package/node_modules/@hap/core/dist/index.js +281 -13
- package/node_modules/@hap/core/dist/index.mjs +266 -12
- package/node_modules/@hap/core/package.json +4 -2
- package/node_modules/@hap/core/src/did-key.ts +126 -0
- package/node_modules/@hap/core/src/frame.ts +115 -14
- package/node_modules/@hap/core/src/gatekeeper.ts +26 -0
- package/node_modules/@hap/core/src/identity.ts +21 -5
- package/node_modules/@hap/core/src/index.ts +3 -0
- package/node_modules/@hap/core/src/mandate.ts +207 -0
- package/node_modules/@hap/core/src/receipt.ts +74 -0
- package/node_modules/@hap/core/src/types.ts +87 -5
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/lib/accept.js +54 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/lib/charset.js +10 -34
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/lib/encoding.js +9 -30
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/lib/language.js +15 -36
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/lib/mediaType.js +16 -134
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/LICENSE +22 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/README.md +71 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/dist/index.d.ts +46 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/dist/index.js +176 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/dist/index.js.map +1 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/package.json +52 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/package.json +16 -10
- package/node_modules/@types/node/README.md +1 -1
- package/node_modules/@types/node/buffer.buffer.d.ts +10 -5
- package/node_modules/@types/node/crypto.d.ts +2 -4
- package/node_modules/@types/node/dgram.d.ts +119 -4
- package/node_modules/@types/node/ffi.d.ts +1 -1
- package/node_modules/@types/node/fs/promises.d.ts +61 -65
- package/node_modules/@types/node/fs.d.ts +86 -76
- package/node_modules/@types/node/http.d.ts +17 -1
- package/node_modules/@types/node/http2.d.ts +12 -4
- package/node_modules/@types/node/index.d.ts +1 -0
- package/node_modules/@types/node/inspector.d.ts +2 -1
- package/node_modules/@types/node/net.d.ts +108 -16
- package/node_modules/@types/node/package.json +2 -2
- package/node_modules/@types/node/process.d.ts +52 -0
- package/node_modules/@types/node/quic.d.ts +246 -38
- package/node_modules/@types/node/sqlite.d.ts +6 -2
- package/node_modules/@types/node/stream/iter.d.ts +4 -4
- package/node_modules/@types/node/test.d.ts +36 -0
- package/node_modules/@types/node/tls.d.ts +24 -0
- package/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +5 -0
- package/node_modules/@types/node/ts5.6/index.d.ts +1 -0
- package/node_modules/@types/node/ts5.7/index.d.ts +1 -0
- package/node_modules/@types/node/tty.d.ts +4 -3
- package/node_modules/@types/node/vfs.d.ts +210 -0
- package/node_modules/@types/node/vm.d.ts +1 -1
- package/node_modules/express-rate-limit/dist/index.cjs +6 -3
- package/node_modules/express-rate-limit/dist/index.d.cts +11 -0
- package/node_modules/express-rate-limit/dist/index.d.mts +11 -0
- package/node_modules/express-rate-limit/dist/index.d.ts +11 -0
- package/node_modules/express-rate-limit/dist/index.mjs +6 -3
- package/node_modules/express-rate-limit/{license.md → license} +7 -6
- package/node_modules/express-rate-limit/package.json +5 -5
- package/node_modules/express-rate-limit/readme.md +1 -0
- package/node_modules/fast-uri/index.js +215 -47
- package/node_modules/fast-uri/lib/schemes.js +9 -4
- package/node_modules/fast-uri/lib/utils.js +396 -92
- package/node_modules/fast-uri/package.json +1 -1
- package/node_modules/fast-uri/test/component-safe-serialization.test.js +163 -0
- package/node_modules/fast-uri/test/equal.test.js +31 -3
- package/node_modules/fast-uri/test/fixtures/uri-js-parse.json +2 -0
- package/node_modules/fast-uri/test/ipv6-canonical.test.js +34 -0
- package/node_modules/fast-uri/test/ipv6-validation.test.js +124 -0
- package/node_modules/fast-uri/test/malformed-percent.test.js +77 -0
- package/node_modules/fast-uri/test/malformed-urn.test.js +61 -0
- package/node_modules/fast-uri/test/parse.test.js +7 -3
- package/node_modules/fast-uri/test/query-fragment-normalization.test.js +33 -0
- package/node_modules/fast-uri/test/reserved-path-normalization.test.js +109 -0
- package/node_modules/fast-uri/test/scheme-validation.test.js +124 -0
- package/node_modules/fast-uri/test/security-normalization.test.js +101 -0
- package/node_modules/fast-uri/test/security.test.js +75 -3
- package/node_modules/fast-uri/test/urn-full-input.test.js +29 -0
- package/node_modules/fast-uri/test/websocket-query-preservation.test.js +24 -0
- package/node_modules/hono/dist/cjs/client/client.js +25 -11
- package/node_modules/hono/dist/cjs/client/utils.js +4 -1
- package/node_modules/hono/dist/cjs/context.js +4 -0
- package/node_modules/hono/dist/cjs/helper/accepts/accepts.js +36 -2
- package/node_modules/hono/dist/cjs/helper/ssg/ssg.js +1 -1
- package/node_modules/hono/dist/cjs/helper/ssg/utils.js +30 -10
- package/node_modules/hono/dist/cjs/jsx/dom/render.js +2 -0
- package/node_modules/hono/dist/cjs/middleware/cache/index.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/cors/index.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/csrf/index.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/etag/digest.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/etag/index.js +3 -3
- package/node_modules/hono/dist/cjs/middleware/pretty-json/index.js +3 -1
- package/node_modules/hono/dist/cjs/request.js +8 -4
- package/node_modules/hono/dist/cjs/router/linear-router/router.js +7 -2
- package/node_modules/hono/dist/cjs/router/pattern-router/router.js +3 -9
- package/node_modules/hono/dist/cjs/router/reg-exp-router/node.js +10 -3
- package/node_modules/hono/dist/cjs/router/reg-exp-router/router.js +47 -83
- package/node_modules/hono/dist/cjs/router/reg-exp-router/trie.js +2 -1
- package/node_modules/hono/dist/cjs/router/trie-router/node.js +46 -69
- package/node_modules/hono/dist/cjs/router/trie-router/router.js +3 -11
- package/node_modules/hono/dist/cjs/router/utils.js +27 -0
- package/node_modules/hono/dist/cjs/utils/body.js +15 -3
- package/node_modules/hono/dist/cjs/utils/cookie.js +1 -1
- package/node_modules/hono/dist/cjs/utils/ipaddr.js +5 -3
- package/node_modules/hono/dist/cjs/utils/stream.js +7 -1
- package/node_modules/hono/dist/cjs/utils/url.js +9 -1
- package/node_modules/hono/dist/client/client.js +25 -11
- package/node_modules/hono/dist/client/utils.js +4 -1
- package/node_modules/hono/dist/context.js +4 -0
- package/node_modules/hono/dist/helper/accepts/accepts.js +36 -2
- package/node_modules/hono/dist/helper/ssg/ssg.js +1 -1
- package/node_modules/hono/dist/helper/ssg/utils.js +30 -10
- package/node_modules/hono/dist/jsx/dom/render.js +2 -0
- package/node_modules/hono/dist/middleware/cache/index.js +1 -1
- package/node_modules/hono/dist/middleware/cors/index.js +1 -1
- package/node_modules/hono/dist/middleware/csrf/index.js +1 -1
- package/node_modules/hono/dist/middleware/etag/digest.js +1 -1
- package/node_modules/hono/dist/middleware/etag/index.js +3 -3
- package/node_modules/hono/dist/middleware/pretty-json/index.js +3 -1
- package/node_modules/hono/dist/request.js +8 -4
- package/node_modules/hono/dist/router/linear-router/router.js +7 -2
- package/node_modules/hono/dist/router/pattern-router/router.js +3 -9
- package/node_modules/hono/dist/router/reg-exp-router/node.js +6 -2
- package/node_modules/hono/dist/router/reg-exp-router/router.js +53 -84
- package/node_modules/hono/dist/router/reg-exp-router/trie.js +2 -1
- package/node_modules/hono/dist/router/trie-router/node.js +46 -69
- package/node_modules/hono/dist/router/trie-router/router.js +3 -11
- package/node_modules/hono/dist/router/utils.js +5 -0
- package/node_modules/hono/dist/types/context.d.ts +4 -0
- package/node_modules/hono/dist/types/router/reg-exp-router/node.d.ts +3 -0
- package/node_modules/hono/dist/types/router/trie-router/node.d.ts +1 -2
- package/node_modules/hono/dist/types/router/trie-router/router.d.ts +0 -1
- package/node_modules/hono/dist/types/router/utils.d.ts +1 -0
- package/node_modules/hono/dist/types/utils/url.d.ts +4 -0
- package/node_modules/hono/dist/utils/body.js +15 -3
- package/node_modules/hono/dist/utils/cookie.js +1 -1
- package/node_modules/hono/dist/utils/ipaddr.js +5 -3
- package/node_modules/hono/dist/utils/stream.js +7 -1
- package/node_modules/hono/dist/utils/url.js +9 -1
- package/node_modules/hono/package.json +1 -1
- package/node_modules/ip-address/README.md +92 -82
- package/node_modules/ip-address/dist/common.d.ts +20 -0
- package/node_modules/ip-address/dist/common.js +36 -0
- package/node_modules/ip-address/dist/common.js.map +1 -1
- package/node_modules/ip-address/dist/ipv4.d.ts +50 -0
- package/node_modules/ip-address/dist/ipv4.js +75 -0
- package/node_modules/ip-address/dist/ipv4.js.map +1 -1
- package/node_modules/ip-address/dist/ipv6.d.ts +79 -10
- package/node_modules/ip-address/dist/ipv6.js +120 -30
- package/node_modules/ip-address/dist/ipv6.js.map +1 -1
- package/node_modules/ip-address/dist/v4/constants.d.ts +12 -0
- package/node_modules/ip-address/dist/v4/constants.js +40 -1
- package/node_modules/ip-address/dist/v4/constants.js.map +1 -1
- package/node_modules/ip-address/dist/v6/constants.d.ts +14 -0
- package/node_modules/ip-address/dist/v6/constants.js +47 -1
- package/node_modules/ip-address/dist/v6/constants.js.map +1 -1
- package/node_modules/ip-address/package.json +1 -1
- package/node_modules/is-plain-object/README.md +39 -95
- package/node_modules/is-plain-object/dist/is-plain-object.js +0 -2
- package/node_modules/is-plain-object/is-plain-object.d.ts +6 -1
- package/node_modules/is-plain-object/package.json +8 -34
- package/node_modules/jose/dist/types/jwe/compact/decrypt.d.ts +7 -16
- package/node_modules/jose/dist/types/jwe/compact/encrypt.d.ts +10 -20
- package/node_modules/jose/dist/types/jwe/flattened/decrypt.d.ts +7 -16
- package/node_modules/jose/dist/types/jwe/flattened/encrypt.d.ts +16 -38
- package/node_modules/jose/dist/types/jwe/general/decrypt.d.ts +7 -16
- package/node_modules/jose/dist/types/jwe/general/encrypt.d.ts +19 -38
- package/node_modules/jose/dist/types/jwk/embedded.d.ts +3 -8
- package/node_modules/jose/dist/types/jwk/thumbprint.d.ts +5 -5
- package/node_modules/jose/dist/types/jwks/local.d.ts +6 -10
- package/node_modules/jose/dist/types/jwks/remote.d.ts +17 -28
- package/node_modules/jose/dist/types/jws/compact/sign.d.ts +5 -10
- package/node_modules/jose/dist/types/jws/compact/verify.d.ts +9 -18
- package/node_modules/jose/dist/types/jws/flattened/sign.d.ts +7 -16
- package/node_modules/jose/dist/types/jws/flattened/verify.d.ts +9 -18
- package/node_modules/jose/dist/types/jws/general/sign.d.ts +11 -21
- package/node_modules/jose/dist/types/jws/general/verify.d.ts +12 -21
- package/node_modules/jose/dist/types/jwt/decrypt.d.ts +10 -20
- package/node_modules/jose/dist/types/jwt/encrypt.d.ts +17 -29
- package/node_modules/jose/dist/types/jwt/sign.d.ts +12 -19
- package/node_modules/jose/dist/types/jwt/unsecured.d.ts +17 -28
- package/node_modules/jose/dist/types/jwt/verify.d.ts +9 -19
- package/node_modules/jose/dist/types/key/export.d.ts +4 -4
- package/node_modules/jose/dist/types/key/generate_key_pair.d.ts +6 -8
- package/node_modules/jose/dist/types/key/generate_secret.d.ts +4 -11
- package/node_modules/jose/dist/types/key/import.d.ts +10 -17
- package/node_modules/jose/dist/types/types.d.ts +48 -86
- package/node_modules/jose/dist/types/util/base64url.d.ts +2 -2
- package/node_modules/jose/dist/types/util/decode_jwt.d.ts +3 -10
- package/node_modules/jose/dist/types/util/decode_protected_header.d.ts +4 -4
- package/node_modules/jose/dist/types/util/errors.d.ts +26 -53
- package/node_modules/jose/dist/webapi/index.js +70 -32
- package/node_modules/jose/dist/webapi/jwe/compact/decrypt.js +7 -8
- package/node_modules/jose/dist/webapi/jwe/compact/encrypt.js +42 -26
- package/node_modules/jose/dist/webapi/jwe/flattened/decrypt.js +14 -10
- package/node_modules/jose/dist/webapi/jwe/flattened/encrypt.js +53 -71
- package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +38 -49
- package/node_modules/jose/dist/webapi/jwe/general/encrypt.js +121 -158
- package/node_modules/jose/dist/webapi/jwk/embedded.js +30 -18
- package/node_modules/jose/dist/webapi/jwk/thumbprint.js +54 -64
- package/node_modules/jose/dist/webapi/jwks/local.js +49 -74
- package/node_modules/jose/dist/webapi/jwks/remote.js +91 -160
- package/node_modules/jose/dist/webapi/jws/compact/sign.js +21 -20
- package/node_modules/jose/dist/webapi/jws/compact/verify.js +7 -8
- package/node_modules/jose/dist/webapi/jws/flattened/sign.js +29 -30
- package/node_modules/jose/dist/webapi/jws/flattened/verify.js +21 -23
- package/node_modules/jose/dist/webapi/jws/general/sign.js +61 -70
- package/node_modules/jose/dist/webapi/jws/general/verify.js +47 -41
- package/node_modules/jose/dist/webapi/jwt/decrypt.js +13 -22
- package/node_modules/jose/dist/webapi/jwt/encrypt.js +58 -100
- package/node_modules/jose/dist/webapi/jwt/sign.js +18 -50
- package/node_modules/jose/dist/webapi/jwt/unsecured.js +34 -63
- package/node_modules/jose/dist/webapi/jwt/verify.js +12 -14
- package/node_modules/jose/dist/webapi/key/export.js +32 -37
- package/node_modules/jose/dist/webapi/key/generate_key_pair.js +40 -44
- package/node_modules/jose/dist/webapi/key/generate_secret.js +35 -39
- package/node_modules/jose/dist/webapi/key/import.js +52 -52
- package/node_modules/jose/dist/webapi/lib/asn1.js +130 -164
- package/node_modules/jose/dist/webapi/lib/base64.js +18 -20
- package/node_modules/jose/dist/webapi/lib/buffer_utils.js +34 -38
- package/node_modules/jose/dist/webapi/lib/content_encryption.js +79 -123
- package/node_modules/jose/dist/webapi/lib/crypto_key.js +24 -25
- package/node_modules/jose/dist/webapi/lib/deflate.js +46 -40
- package/node_modules/jose/dist/webapi/lib/helpers.js +40 -37
- package/node_modules/jose/dist/webapi/lib/invalid_key_input.js +10 -24
- package/node_modules/jose/dist/webapi/lib/is_key_like.js +17 -15
- package/node_modules/jose/dist/webapi/lib/jwe_algorithms.js +106 -93
- package/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +210 -176
- package/node_modules/jose/dist/webapi/lib/jwe_encrypt.js +105 -107
- package/node_modules/jose/dist/webapi/lib/jwk_metadata.js +19 -0
- package/node_modules/jose/dist/webapi/lib/jwk_to_key.js +9 -16
- package/node_modules/jose/dist/webapi/lib/jws_algorithms.js +62 -61
- package/node_modules/jose/dist/webapi/lib/jws_sign.js +58 -72
- package/node_modules/jose/dist/webapi/lib/jws_verify.js +90 -103
- package/node_modules/jose/dist/webapi/lib/jwt_claims_set.js +134 -185
- package/node_modules/jose/dist/webapi/lib/key.js +84 -121
- package/node_modules/jose/dist/webapi/lib/key_algorithm.js +13 -8
- package/node_modules/jose/dist/webapi/lib/key_descriptor.js +8 -6
- package/node_modules/jose/dist/webapi/lib/key_management.js +200 -225
- package/node_modules/jose/dist/webapi/lib/key_options.js +8 -0
- package/node_modules/jose/dist/webapi/lib/options.js +57 -44
- package/node_modules/jose/dist/webapi/lib/signing.js +18 -22
- package/node_modules/jose/dist/webapi/lib/type_checks.js +28 -33
- package/node_modules/jose/dist/webapi/util/base64url.js +26 -35
- package/node_modules/jose/dist/webapi/util/decode_jwt.js +32 -31
- package/node_modules/jose/dist/webapi/util/decode_protected_header.js +18 -23
- package/node_modules/jose/dist/webapi/util/errors.js +94 -84
- package/node_modules/jose/package.json +17 -81
- package/package.json +2 -2
- package/profiles/calendar/0.5.profile.json +190 -0
- package/profiles/charge/0.5.profile.json +210 -0
- package/profiles/customers/0.7.profile.json +157 -0
- package/profiles/deploy/0.9.profile.json +176 -0
- package/profiles/email/0.6.profile.json +208 -0
- package/profiles/index.json +9 -1
- package/profiles/publish/0.5.profile.json +186 -0
- package/profiles/purchase/0.5.profile.json +231 -0
- package/profiles/records/0.5.profile.json +117 -0
- package/dist/ui/assets/index-0Q2eNytX.js +0 -110
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/HISTORY.md +0 -114
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -33,6 +33,85 @@ async function notifyControlPlane(type) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// src/lib/client-version.ts
|
|
37
|
+
import { createRequire } from "module";
|
|
38
|
+
import { existsSync, readFileSync } from "fs";
|
|
39
|
+
import { join } from "path";
|
|
40
|
+
var UNKNOWN = "unknown";
|
|
41
|
+
function readJson(path) {
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function detectGatewayVersion() {
|
|
49
|
+
const override = process.env.SUVEREN_GATEWAY_VERSION;
|
|
50
|
+
if (override) return override;
|
|
51
|
+
const start = import.meta.dirname ?? process.cwd();
|
|
52
|
+
let dir = start;
|
|
53
|
+
for (let up = 0; up < 6; up++) {
|
|
54
|
+
const pkg = readJson(join(dir, "package.json"));
|
|
55
|
+
const name = pkg?.name;
|
|
56
|
+
const version = pkg?.version;
|
|
57
|
+
if (typeof version === "string" && (name === "@suveren/gateway" || name === "suveren-gateway")) {
|
|
58
|
+
return version;
|
|
59
|
+
}
|
|
60
|
+
const parent = join(dir, "..");
|
|
61
|
+
if (parent === dir) break;
|
|
62
|
+
dir = parent;
|
|
63
|
+
}
|
|
64
|
+
return process.env.HAP_BUILD_SHA ?? UNKNOWN;
|
|
65
|
+
}
|
|
66
|
+
function findPackageVersion(entry, wantName) {
|
|
67
|
+
let dir = join(entry, "..");
|
|
68
|
+
for (let up = 0; up < 6; up++) {
|
|
69
|
+
const candidate = join(dir, "package.json");
|
|
70
|
+
if (existsSync(candidate)) {
|
|
71
|
+
const pkg = readJson(candidate);
|
|
72
|
+
if (typeof pkg?.version === "string" && (pkg.name === wantName || pkg.name === "@hap/core")) {
|
|
73
|
+
return pkg.name === wantName ? pkg.version : null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const parent = join(dir, "..");
|
|
77
|
+
if (parent === dir) break;
|
|
78
|
+
dir = parent;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
function detectHapCoreVersion() {
|
|
83
|
+
const CORE = "@humanagencyp/hap-core";
|
|
84
|
+
let req;
|
|
85
|
+
try {
|
|
86
|
+
req = createRequire(import.meta.url);
|
|
87
|
+
} catch {
|
|
88
|
+
return UNKNOWN;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const v = findPackageVersion(req.resolve(CORE), CORE);
|
|
92
|
+
if (v) return v;
|
|
93
|
+
} catch {
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const wrapperEntry = req.resolve("@hap/core");
|
|
97
|
+
const v = findPackageVersion(wrapperEntry, CORE);
|
|
98
|
+
if (v) return v;
|
|
99
|
+
const wrapperReq = createRequire(wrapperEntry);
|
|
100
|
+
const v2 = findPackageVersion(wrapperReq.resolve(CORE), CORE);
|
|
101
|
+
if (v2) return v2;
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
return UNKNOWN;
|
|
105
|
+
}
|
|
106
|
+
var GATEWAY_VERSION = detectGatewayVersion();
|
|
107
|
+
var HAP_CORE_VERSION = detectHapCoreVersion();
|
|
108
|
+
function clientVersionHeaders() {
|
|
109
|
+
return {
|
|
110
|
+
"x-suveren-gateway-version": GATEWAY_VERSION,
|
|
111
|
+
"x-hap-core-version": HAP_CORE_VERSION
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
36
115
|
// src/lib/sp-client.ts
|
|
37
116
|
var SPReceiptError = class extends Error {
|
|
38
117
|
constructor(message, statusCode, body) {
|
|
@@ -55,6 +134,10 @@ var SPClient = class {
|
|
|
55
134
|
sessionCookie = "";
|
|
56
135
|
apiKey = "";
|
|
57
136
|
receiptRetry;
|
|
137
|
+
/** The AS base URL this client talks to (for provenance records). */
|
|
138
|
+
get url() {
|
|
139
|
+
return this.baseUrl;
|
|
140
|
+
}
|
|
58
141
|
setApiKey(key) {
|
|
59
142
|
this.apiKey = key;
|
|
60
143
|
}
|
|
@@ -78,6 +161,11 @@ var SPClient = class {
|
|
|
78
161
|
const url = `${this.baseUrl}${path}`;
|
|
79
162
|
const headers = {
|
|
80
163
|
"Content-Type": "application/json",
|
|
164
|
+
// Identify the caller so the AS can answer "what is deployed against
|
|
165
|
+
// me?" and name a version in an upgrade instruction. Advisory only —
|
|
166
|
+
// never used for authorization. Sent on every call, not just receipts,
|
|
167
|
+
// so the picture is not skewed toward one endpoint.
|
|
168
|
+
...clientVersionHeaders(),
|
|
81
169
|
...init?.headers
|
|
82
170
|
};
|
|
83
171
|
if (this.sessionCookie) {
|
|
@@ -374,11 +462,11 @@ var AttestationCache = class {
|
|
|
374
462
|
};
|
|
375
463
|
|
|
376
464
|
// src/lib/gate-store.ts
|
|
377
|
-
import { mkdirSync, readFileSync, writeFileSync, existsSync, unlinkSync } from "fs";
|
|
465
|
+
import { mkdirSync, readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, unlinkSync } from "fs";
|
|
378
466
|
import { homedir } from "os";
|
|
379
|
-
import { dirname, join } from "path";
|
|
467
|
+
import { dirname, join as join2 } from "path";
|
|
380
468
|
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
381
|
-
var DEFAULT_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
469
|
+
var DEFAULT_DIR = process.env.SUVEREN_DATA_DIR ?? join2(homedir(), ".suveren");
|
|
382
470
|
var GateStore = class {
|
|
383
471
|
entries = /* @__PURE__ */ new Map();
|
|
384
472
|
baseDir;
|
|
@@ -396,11 +484,11 @@ var GateStore = class {
|
|
|
396
484
|
*/
|
|
397
485
|
setVaultKey(key) {
|
|
398
486
|
this.vaultKey = key;
|
|
399
|
-
if (
|
|
487
|
+
if (existsSync2(this.encryptedFilePath)) {
|
|
400
488
|
this.loadEncrypted();
|
|
401
489
|
} else if (this.entries.size > 0) {
|
|
402
490
|
this.persistEncrypted();
|
|
403
|
-
if (
|
|
491
|
+
if (existsSync2(this.plaintextFilePath)) {
|
|
404
492
|
try {
|
|
405
493
|
unlinkSync(this.plaintextFilePath);
|
|
406
494
|
} catch {
|
|
@@ -451,19 +539,19 @@ var GateStore = class {
|
|
|
451
539
|
}
|
|
452
540
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
453
541
|
get plaintextFilePath() {
|
|
454
|
-
return
|
|
542
|
+
return join2(this.baseDir, "gates.json");
|
|
455
543
|
}
|
|
456
544
|
get encryptedFilePath() {
|
|
457
|
-
return
|
|
545
|
+
return join2(this.baseDir, "gates.enc.json");
|
|
458
546
|
}
|
|
459
547
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
460
548
|
loadPlaintext() {
|
|
461
|
-
if (!
|
|
549
|
+
if (!existsSync2(this.plaintextFilePath)) {
|
|
462
550
|
mkdirSync(this.baseDir, { recursive: true });
|
|
463
551
|
return;
|
|
464
552
|
}
|
|
465
553
|
try {
|
|
466
|
-
const raw =
|
|
554
|
+
const raw = readFileSync2(this.plaintextFilePath, "utf-8");
|
|
467
555
|
const data = JSON.parse(raw);
|
|
468
556
|
this.entries = new Map(Object.entries(data.entries));
|
|
469
557
|
} catch {
|
|
@@ -472,9 +560,9 @@ var GateStore = class {
|
|
|
472
560
|
}
|
|
473
561
|
}
|
|
474
562
|
loadEncrypted() {
|
|
475
|
-
if (!
|
|
563
|
+
if (!existsSync2(this.encryptedFilePath)) return;
|
|
476
564
|
try {
|
|
477
|
-
const raw =
|
|
565
|
+
const raw = readFileSync2(this.encryptedFilePath, "utf-8");
|
|
478
566
|
const data = JSON.parse(raw);
|
|
479
567
|
this.entries = /* @__PURE__ */ new Map();
|
|
480
568
|
for (const [key, blob] of Object.entries(data.entries)) {
|
|
@@ -515,11 +603,11 @@ var GateStore = class {
|
|
|
515
603
|
};
|
|
516
604
|
|
|
517
605
|
// src/lib/execution-log.ts
|
|
518
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
606
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
519
607
|
import { homedir as homedir2 } from "os";
|
|
520
|
-
import { dirname as dirname2, join as
|
|
608
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
521
609
|
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes2 } from "crypto";
|
|
522
|
-
var DEFAULT_DIR2 = process.env.SUVEREN_DATA_DIR ??
|
|
610
|
+
var DEFAULT_DIR2 = process.env.SUVEREN_DATA_DIR ?? join3(homedir2(), ".suveren");
|
|
523
611
|
var MAX_AGE_SECONDS = 31 * 24 * 60 * 60;
|
|
524
612
|
var ExecutionLog = class {
|
|
525
613
|
entries = [];
|
|
@@ -538,11 +626,11 @@ var ExecutionLog = class {
|
|
|
538
626
|
*/
|
|
539
627
|
setVaultKey(key) {
|
|
540
628
|
this.vaultKey = key;
|
|
541
|
-
if (
|
|
629
|
+
if (existsSync3(this.encryptedFilePath)) {
|
|
542
630
|
this.loadEncrypted();
|
|
543
631
|
} else if (this.entries.length > 0) {
|
|
544
632
|
this.persistEncrypted();
|
|
545
|
-
if (
|
|
633
|
+
if (existsSync3(this.plaintextFilePath)) {
|
|
546
634
|
try {
|
|
547
635
|
unlinkSync2(this.plaintextFilePath);
|
|
548
636
|
} catch {
|
|
@@ -637,19 +725,19 @@ var ExecutionLog = class {
|
|
|
637
725
|
}
|
|
638
726
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
639
727
|
get plaintextFilePath() {
|
|
640
|
-
return
|
|
728
|
+
return join3(this.baseDir, "execution-log.json");
|
|
641
729
|
}
|
|
642
730
|
get encryptedFilePath() {
|
|
643
|
-
return
|
|
731
|
+
return join3(this.baseDir, "execution-log.enc.json");
|
|
644
732
|
}
|
|
645
733
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
646
734
|
loadPlaintext() {
|
|
647
|
-
if (!
|
|
735
|
+
if (!existsSync3(this.plaintextFilePath)) {
|
|
648
736
|
mkdirSync2(this.baseDir, { recursive: true });
|
|
649
737
|
return;
|
|
650
738
|
}
|
|
651
739
|
try {
|
|
652
|
-
const raw =
|
|
740
|
+
const raw = readFileSync3(this.plaintextFilePath, "utf-8");
|
|
653
741
|
const data = JSON.parse(raw);
|
|
654
742
|
this.entries = data.entries;
|
|
655
743
|
this.prune();
|
|
@@ -659,9 +747,9 @@ var ExecutionLog = class {
|
|
|
659
747
|
}
|
|
660
748
|
}
|
|
661
749
|
loadEncrypted() {
|
|
662
|
-
if (!
|
|
750
|
+
if (!existsSync3(this.encryptedFilePath)) return;
|
|
663
751
|
try {
|
|
664
|
-
const raw =
|
|
752
|
+
const raw = readFileSync3(this.encryptedFilePath, "utf-8");
|
|
665
753
|
const data = JSON.parse(raw);
|
|
666
754
|
const decrypted = this.decrypt(data.blob);
|
|
667
755
|
const logFile = JSON.parse(decrypted);
|
|
@@ -708,11 +796,11 @@ function windowCutoff(window, now) {
|
|
|
708
796
|
}
|
|
709
797
|
|
|
710
798
|
// src/lib/denial-log.ts
|
|
711
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
799
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, unlinkSync as unlinkSync3 } from "fs";
|
|
712
800
|
import { homedir as homedir3 } from "os";
|
|
713
|
-
import { dirname as dirname3, join as
|
|
801
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
714
802
|
import { createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3, randomBytes as randomBytes3 } from "crypto";
|
|
715
|
-
var DEFAULT_DIR3 = process.env.SUVEREN_DATA_DIR ??
|
|
803
|
+
var DEFAULT_DIR3 = process.env.SUVEREN_DATA_DIR ?? join4(homedir3(), ".suveren");
|
|
716
804
|
var MAX_RECORDS = 200;
|
|
717
805
|
var MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
718
806
|
var DenialLog = class {
|
|
@@ -725,11 +813,11 @@ var DenialLog = class {
|
|
|
725
813
|
}
|
|
726
814
|
setVaultKey(key) {
|
|
727
815
|
this.vaultKey = key;
|
|
728
|
-
if (
|
|
816
|
+
if (existsSync4(this.encryptedFilePath)) {
|
|
729
817
|
this.loadEncrypted();
|
|
730
818
|
} else if (this.records.length > 0) {
|
|
731
819
|
this.persistEncrypted();
|
|
732
|
-
if (
|
|
820
|
+
if (existsSync4(this.plaintextFilePath)) {
|
|
733
821
|
try {
|
|
734
822
|
unlinkSync3(this.plaintextFilePath);
|
|
735
823
|
} catch {
|
|
@@ -779,18 +867,18 @@ var DenialLog = class {
|
|
|
779
867
|
return Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "hex")), decipher.final()]).toString("utf8");
|
|
780
868
|
}
|
|
781
869
|
get plaintextFilePath() {
|
|
782
|
-
return
|
|
870
|
+
return join4(this.baseDir, "denials.json");
|
|
783
871
|
}
|
|
784
872
|
get encryptedFilePath() {
|
|
785
|
-
return
|
|
873
|
+
return join4(this.baseDir, "denials.enc.json");
|
|
786
874
|
}
|
|
787
875
|
loadPlaintext() {
|
|
788
|
-
if (!
|
|
876
|
+
if (!existsSync4(this.plaintextFilePath)) {
|
|
789
877
|
mkdirSync3(this.baseDir, { recursive: true });
|
|
790
878
|
return;
|
|
791
879
|
}
|
|
792
880
|
try {
|
|
793
|
-
const data = JSON.parse(
|
|
881
|
+
const data = JSON.parse(readFileSync4(this.plaintextFilePath, "utf-8"));
|
|
794
882
|
this.records = data.records ?? [];
|
|
795
883
|
} catch {
|
|
796
884
|
console.error(`[DenialLog] Could not parse ${this.plaintextFilePath}, starting fresh`);
|
|
@@ -798,9 +886,9 @@ var DenialLog = class {
|
|
|
798
886
|
}
|
|
799
887
|
}
|
|
800
888
|
loadEncrypted() {
|
|
801
|
-
if (!
|
|
889
|
+
if (!existsSync4(this.encryptedFilePath)) return;
|
|
802
890
|
try {
|
|
803
|
-
const data = JSON.parse(
|
|
891
|
+
const data = JSON.parse(readFileSync4(this.encryptedFilePath, "utf-8"));
|
|
804
892
|
const logFile = JSON.parse(this.decrypt(data.blob));
|
|
805
893
|
this.records = logFile.records ?? [];
|
|
806
894
|
} catch (err) {
|
|
@@ -825,12 +913,242 @@ var DenialLog = class {
|
|
|
825
913
|
}
|
|
826
914
|
};
|
|
827
915
|
|
|
916
|
+
// src/lib/receipt-archive.ts
|
|
917
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, unlinkSync as unlinkSync4 } from "fs";
|
|
918
|
+
import { homedir as homedir4 } from "os";
|
|
919
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
920
|
+
import { createCipheriv as createCipheriv4, createDecipheriv as createDecipheriv4, randomBytes as randomBytes4 } from "crypto";
|
|
921
|
+
var DEFAULT_DIR4 = process.env.SUVEREN_DATA_DIR ?? join5(homedir4(), ".suveren");
|
|
922
|
+
function stripPreviews(proposal) {
|
|
923
|
+
const args = proposal.toolArgs;
|
|
924
|
+
if (!args || typeof args !== "object") return proposal;
|
|
925
|
+
const { _imagePreview, ...rest } = args;
|
|
926
|
+
if (_imagePreview === void 0) return proposal;
|
|
927
|
+
return { ...proposal, toolArgs: rest };
|
|
928
|
+
}
|
|
929
|
+
var ReceiptArchive = class {
|
|
930
|
+
data = { version: 1, authorizations: {}, receipts: [] };
|
|
931
|
+
baseDir;
|
|
932
|
+
vaultKey = null;
|
|
933
|
+
constructor(filePath) {
|
|
934
|
+
if (filePath && filePath.endsWith(".json")) {
|
|
935
|
+
this.baseDir = dirname4(filePath);
|
|
936
|
+
} else {
|
|
937
|
+
this.baseDir = filePath ?? DEFAULT_DIR4;
|
|
938
|
+
}
|
|
939
|
+
this.loadPlaintext();
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Set the vault key for encryption. Triggers migration from plaintext if needed.
|
|
943
|
+
*/
|
|
944
|
+
setVaultKey(key) {
|
|
945
|
+
this.vaultKey = key;
|
|
946
|
+
if (existsSync5(this.encryptedFilePath)) {
|
|
947
|
+
this.loadEncrypted();
|
|
948
|
+
} else if (this.data.receipts.length > 0 || Object.keys(this.data.authorizations).length > 0) {
|
|
949
|
+
this.persistEncrypted();
|
|
950
|
+
if (existsSync5(this.plaintextFilePath)) {
|
|
951
|
+
try {
|
|
952
|
+
unlinkSync4(this.plaintextFilePath);
|
|
953
|
+
} catch {
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* True when encrypted evidence exists on disk that this process cannot read
|
|
960
|
+
* yet (no vault key). Callers MUST NOT present the archive as "empty" in
|
|
961
|
+
* this state — that would report a hollow success over unreadable evidence.
|
|
962
|
+
*/
|
|
963
|
+
isLocked() {
|
|
964
|
+
return this.vaultKey === null && existsSync5(this.encryptedFilePath);
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Archive a signed receipt (and the grant it was issued under).
|
|
968
|
+
* Idempotent on receipt id — a replayed receipt is recorded once.
|
|
969
|
+
*/
|
|
970
|
+
record(entry, now = Math.floor(Date.now() / 1e3)) {
|
|
971
|
+
let changed = false;
|
|
972
|
+
const receiptId = typeof entry.receipt?.id === "string" ? entry.receipt.id : void 0;
|
|
973
|
+
const alreadyArchived = receiptId !== void 0 && this.data.receipts.some((r) => r.receipt.id === receiptId);
|
|
974
|
+
if (!alreadyArchived) {
|
|
975
|
+
this.data.receipts.push({
|
|
976
|
+
archivedAt: now,
|
|
977
|
+
authorizationId: entry.authorizationId,
|
|
978
|
+
asUrl: entry.asUrl,
|
|
979
|
+
asPublicKey: entry.asPublicKey,
|
|
980
|
+
receipt: entry.receipt,
|
|
981
|
+
...entry.proposal ? { proposal: stripPreviews(entry.proposal) } : {},
|
|
982
|
+
...entry.boundContent !== void 0 ? { boundContent: entry.boundContent } : {}
|
|
983
|
+
});
|
|
984
|
+
changed = true;
|
|
985
|
+
}
|
|
986
|
+
if (entry.authorization) {
|
|
987
|
+
const existing = this.data.authorizations[entry.authorizationId];
|
|
988
|
+
if (!existing) {
|
|
989
|
+
this.data.authorizations[entry.authorizationId] = {
|
|
990
|
+
authorizationId: entry.authorizationId,
|
|
991
|
+
profileId: entry.authorization.profileId,
|
|
992
|
+
boundsHash: entry.authorization.boundsHash,
|
|
993
|
+
contextHash: entry.authorization.contextHash,
|
|
994
|
+
bounds: entry.authorization.bounds,
|
|
995
|
+
context: entry.authorization.context,
|
|
996
|
+
intent: entry.authorization.intent,
|
|
997
|
+
attestations: [...entry.authorization.attestations],
|
|
998
|
+
archivedAt: now
|
|
999
|
+
};
|
|
1000
|
+
changed = true;
|
|
1001
|
+
} else {
|
|
1002
|
+
for (const att of entry.authorization.attestations) {
|
|
1003
|
+
const seen = existing.attestations.some(
|
|
1004
|
+
(a) => a.domain === att.domain && a.blob === att.blob
|
|
1005
|
+
);
|
|
1006
|
+
if (!seen) {
|
|
1007
|
+
existing.attestations.push(att);
|
|
1008
|
+
changed = true;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (!existing.bounds && entry.authorization.bounds) {
|
|
1012
|
+
existing.bounds = entry.authorization.bounds;
|
|
1013
|
+
changed = true;
|
|
1014
|
+
}
|
|
1015
|
+
if (!existing.context && entry.authorization.context) {
|
|
1016
|
+
existing.context = entry.authorization.context;
|
|
1017
|
+
changed = true;
|
|
1018
|
+
}
|
|
1019
|
+
if (!existing.intent && entry.authorization.intent) {
|
|
1020
|
+
existing.intent = entry.authorization.intent;
|
|
1021
|
+
changed = true;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
if (changed) this.persist();
|
|
1026
|
+
}
|
|
1027
|
+
getReceipts() {
|
|
1028
|
+
return [...this.data.receipts];
|
|
1029
|
+
}
|
|
1030
|
+
getAuthorizations() {
|
|
1031
|
+
return Object.values(this.data.authorizations);
|
|
1032
|
+
}
|
|
1033
|
+
get size() {
|
|
1034
|
+
return this.data.receipts.length;
|
|
1035
|
+
}
|
|
1036
|
+
// ─── Encryption helpers ─────────────────────────────────────────────────
|
|
1037
|
+
encrypt(plaintext) {
|
|
1038
|
+
if (!this.vaultKey) throw new Error("No vault key");
|
|
1039
|
+
const iv = randomBytes4(12);
|
|
1040
|
+
const cipher = createCipheriv4("aes-256-gcm", this.vaultKey, iv);
|
|
1041
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
1042
|
+
const tag = cipher.getAuthTag();
|
|
1043
|
+
return {
|
|
1044
|
+
iv: iv.toString("hex"),
|
|
1045
|
+
ciphertext: encrypted.toString("hex"),
|
|
1046
|
+
tag: tag.toString("hex")
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
decrypt(blob) {
|
|
1050
|
+
if (!this.vaultKey) throw new Error("No vault key");
|
|
1051
|
+
const decipher = createDecipheriv4(
|
|
1052
|
+
"aes-256-gcm",
|
|
1053
|
+
this.vaultKey,
|
|
1054
|
+
Buffer.from(blob.iv, "hex")
|
|
1055
|
+
);
|
|
1056
|
+
decipher.setAuthTag(Buffer.from(blob.tag, "hex"));
|
|
1057
|
+
const decrypted = Buffer.concat([
|
|
1058
|
+
decipher.update(Buffer.from(blob.ciphertext, "hex")),
|
|
1059
|
+
decipher.final()
|
|
1060
|
+
]);
|
|
1061
|
+
return decrypted.toString("utf8");
|
|
1062
|
+
}
|
|
1063
|
+
// ─── File paths ─────────────────────────────────────────────────────────
|
|
1064
|
+
get plaintextFilePath() {
|
|
1065
|
+
return join5(this.baseDir, "receipt-archive.json");
|
|
1066
|
+
}
|
|
1067
|
+
get encryptedFilePath() {
|
|
1068
|
+
return join5(this.baseDir, "receipt-archive.enc.json");
|
|
1069
|
+
}
|
|
1070
|
+
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
1071
|
+
loadPlaintext() {
|
|
1072
|
+
if (!existsSync5(this.plaintextFilePath)) {
|
|
1073
|
+
mkdirSync4(this.baseDir, { recursive: true });
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
try {
|
|
1077
|
+
const raw = readFileSync5(this.plaintextFilePath, "utf-8");
|
|
1078
|
+
const parsed = JSON.parse(raw);
|
|
1079
|
+
this.data = {
|
|
1080
|
+
version: 1,
|
|
1081
|
+
authorizations: parsed.authorizations ?? {},
|
|
1082
|
+
receipts: parsed.receipts ?? []
|
|
1083
|
+
};
|
|
1084
|
+
} catch {
|
|
1085
|
+
console.error(`[ReceiptArchive] Could not parse ${this.plaintextFilePath} \u2014 preserving as .corrupt`);
|
|
1086
|
+
try {
|
|
1087
|
+
writeFileSync4(
|
|
1088
|
+
`${this.plaintextFilePath}.corrupt-${Date.now()}`,
|
|
1089
|
+
readFileSync5(this.plaintextFilePath)
|
|
1090
|
+
);
|
|
1091
|
+
} catch {
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
loadEncrypted() {
|
|
1096
|
+
if (!existsSync5(this.encryptedFilePath)) return;
|
|
1097
|
+
try {
|
|
1098
|
+
const raw = readFileSync5(this.encryptedFilePath, "utf-8");
|
|
1099
|
+
const data = JSON.parse(raw);
|
|
1100
|
+
const decrypted = this.decrypt(data.blob);
|
|
1101
|
+
const parsed = JSON.parse(decrypted);
|
|
1102
|
+
this.data = {
|
|
1103
|
+
version: 1,
|
|
1104
|
+
authorizations: parsed.authorizations ?? {},
|
|
1105
|
+
receipts: parsed.receipts ?? []
|
|
1106
|
+
};
|
|
1107
|
+
} catch (err) {
|
|
1108
|
+
console.error(`[ReceiptArchive] Could not decrypt ${this.encryptedFilePath} \u2014 preserving as .corrupt:`, err);
|
|
1109
|
+
try {
|
|
1110
|
+
writeFileSync4(
|
|
1111
|
+
`${this.encryptedFilePath}.corrupt-${Date.now()}`,
|
|
1112
|
+
readFileSync5(this.encryptedFilePath)
|
|
1113
|
+
);
|
|
1114
|
+
} catch {
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
persist() {
|
|
1119
|
+
if (this.vaultKey) {
|
|
1120
|
+
this.persistEncrypted();
|
|
1121
|
+
} else {
|
|
1122
|
+
this.persistPlaintext();
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
persistPlaintext() {
|
|
1126
|
+
mkdirSync4(this.baseDir, { recursive: true, mode: 448 });
|
|
1127
|
+
writeFileSync4(this.plaintextFilePath, JSON.stringify(this.data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1128
|
+
}
|
|
1129
|
+
persistEncrypted() {
|
|
1130
|
+
const data = {
|
|
1131
|
+
version: 1,
|
|
1132
|
+
blob: this.encrypt(JSON.stringify(this.data))
|
|
1133
|
+
};
|
|
1134
|
+
mkdirSync4(this.baseDir, { recursive: true, mode: 448 });
|
|
1135
|
+
writeFileSync4(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
|
|
828
1139
|
// src/lib/gatekeeper.ts
|
|
829
1140
|
import { verify } from "@hap/core";
|
|
830
1141
|
var MCPGatekeeper = class {
|
|
831
|
-
|
|
1142
|
+
/**
|
|
1143
|
+
* No ExecutionLog dependency by construction. The local record is
|
|
1144
|
+
* display-only (protocol.md → "Executor Gating, Context vs Bounds,
|
|
1145
|
+
* Display-Only Logs"), so the Gatekeeper must not be able to read it: with
|
|
1146
|
+
* no log in scope, a local cumulative check cannot be reintroduced by
|
|
1147
|
+
* accident. `SharedState.executionLog` stays — it feeds the UI's consumption
|
|
1148
|
+
* display and nothing else.
|
|
1149
|
+
*/
|
|
1150
|
+
constructor(cache) {
|
|
832
1151
|
this.cache = cache;
|
|
833
|
-
this.executionLog = executionLog;
|
|
834
1152
|
}
|
|
835
1153
|
/**
|
|
836
1154
|
* Verify an execution request against a cached authorization.
|
|
@@ -877,16 +1195,15 @@ var MCPGatekeeper = class {
|
|
|
877
1195
|
attestations: auth.attestations.map((a) => a.blob),
|
|
878
1196
|
execution,
|
|
879
1197
|
context: resolvedContext,
|
|
880
|
-
//
|
|
881
|
-
//
|
|
882
|
-
//
|
|
883
|
-
//
|
|
884
|
-
//
|
|
885
|
-
//
|
|
886
|
-
// the local gate never fired and the AS was doing all the enforcing.
|
|
1198
|
+
// Identifies the grant whose bounds are being checked. hap-core reads it
|
|
1199
|
+
// only to scope a cumulative lookup, which no longer happens here (see
|
|
1200
|
+
// below); it is kept because it belongs to the request, not to the
|
|
1201
|
+
// dropped check. It cannot go inside `frame`: that is validated against
|
|
1202
|
+
// the profile's boundsSchema, which declares no `path` field in any
|
|
1203
|
+
// shipped profile, so an extra key there fails with "Unknown field".
|
|
887
1204
|
path: auth.path
|
|
888
1205
|
};
|
|
889
|
-
const result = await verify(request, publicKeyHex
|
|
1206
|
+
const result = await verify(request, publicKeyHex);
|
|
890
1207
|
return { result, authorization: auth };
|
|
891
1208
|
}
|
|
892
1209
|
};
|
|
@@ -898,6 +1215,7 @@ var SharedState = class {
|
|
|
898
1215
|
gateStore;
|
|
899
1216
|
executionLog;
|
|
900
1217
|
denialLog;
|
|
1218
|
+
receiptArchive;
|
|
901
1219
|
gatekeeper;
|
|
902
1220
|
constructor(spUrl2, gateStorePath) {
|
|
903
1221
|
this.spClient = new SPClient(spUrl2);
|
|
@@ -905,7 +1223,49 @@ var SharedState = class {
|
|
|
905
1223
|
this.gateStore = new GateStore(gateStorePath);
|
|
906
1224
|
this.executionLog = new ExecutionLog(gateStorePath);
|
|
907
1225
|
this.denialLog = new DenialLog(gateStorePath);
|
|
908
|
-
this.
|
|
1226
|
+
this.receiptArchive = new ReceiptArchive(gateStorePath);
|
|
1227
|
+
this.gatekeeper = new MCPGatekeeper(this.cache);
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Archive a signed receipt into the local receipt archive — the subject's
|
|
1231
|
+
* own durable copy of the evidence (see receipt-archive.ts).
|
|
1232
|
+
*
|
|
1233
|
+
* Best-effort by contract: the AS has already issued the receipt and holds
|
|
1234
|
+
* the authoritative copy, so an archive failure logs loudly but MUST NOT
|
|
1235
|
+
* block the execution it documents.
|
|
1236
|
+
*/
|
|
1237
|
+
async archiveReceipt(receipt, opts) {
|
|
1238
|
+
try {
|
|
1239
|
+
let asPublicKey;
|
|
1240
|
+
try {
|
|
1241
|
+
asPublicKey = await this.cache.getPublicKey();
|
|
1242
|
+
} catch {
|
|
1243
|
+
}
|
|
1244
|
+
this.receiptArchive.record({
|
|
1245
|
+
receipt,
|
|
1246
|
+
authorizationId: opts.authorizationId,
|
|
1247
|
+
asUrl: this.spClient.url,
|
|
1248
|
+
asPublicKey,
|
|
1249
|
+
proposal: opts.proposal,
|
|
1250
|
+
boundContent: opts.boundContent,
|
|
1251
|
+
authorization: opts.profileId ? {
|
|
1252
|
+
profileId: opts.profileId,
|
|
1253
|
+
boundsHash: opts.boundsHash,
|
|
1254
|
+
contextHash: opts.contextHash ?? this.gateStore.get(opts.authorizationId)?.contextHash,
|
|
1255
|
+
bounds: opts.bounds,
|
|
1256
|
+
context: opts.context,
|
|
1257
|
+
// Fall back to the gate store — the review path's cached auth
|
|
1258
|
+
// carries no gate content.
|
|
1259
|
+
intent: opts.intent ?? this.gateStore.get(opts.authorizationId)?.gateContent?.intent,
|
|
1260
|
+
attestations: opts.attestations ?? []
|
|
1261
|
+
} : void 0
|
|
1262
|
+
});
|
|
1263
|
+
} catch (err) {
|
|
1264
|
+
console.error(
|
|
1265
|
+
"[Suveren MCP] Receipt archive write failed (execution proceeds \u2014 AS retains authoritative copy):",
|
|
1266
|
+
err
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
909
1269
|
}
|
|
910
1270
|
setGateContent(path, authorizationId, profileId, content, opts) {
|
|
911
1271
|
this.gateStore.set(authorizationId, {
|
|
@@ -1012,17 +1372,17 @@ function formatConsumptionFull(entries) {
|
|
|
1012
1372
|
}
|
|
1013
1373
|
|
|
1014
1374
|
// src/lib/context-loader.ts
|
|
1015
|
-
import { readFileSync as
|
|
1016
|
-
import { homedir as
|
|
1017
|
-
import { join as
|
|
1018
|
-
var
|
|
1375
|
+
import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
|
|
1376
|
+
import { homedir as homedir5 } from "os";
|
|
1377
|
+
import { join as join6 } from "path";
|
|
1378
|
+
var DEFAULT_DIR5 = process.env.SUVEREN_DATA_DIR ?? join6(homedir5(), ".suveren");
|
|
1019
1379
|
var BRIEF_MAX_CHARS = 16 * 1024;
|
|
1020
1380
|
function readContextFile(dataDir) {
|
|
1021
|
-
const dir = dataDir ??
|
|
1022
|
-
const filePath =
|
|
1023
|
-
if (!
|
|
1381
|
+
const dir = dataDir ?? DEFAULT_DIR5;
|
|
1382
|
+
const filePath = join6(dir, "context.md");
|
|
1383
|
+
if (!existsSync6(filePath)) return null;
|
|
1024
1384
|
try {
|
|
1025
|
-
const content =
|
|
1385
|
+
const content = readFileSync6(filePath, "utf-8").trim();
|
|
1026
1386
|
return content || null;
|
|
1027
1387
|
} catch {
|
|
1028
1388
|
return null;
|
|
@@ -1192,7 +1552,7 @@ function appendVerificationFooter(tool, args, receiptId, subject) {
|
|
|
1192
1552
|
// src/lib/content-binding.ts
|
|
1193
1553
|
import {
|
|
1194
1554
|
computeContentHash,
|
|
1195
|
-
|
|
1555
|
+
selectBoundFields,
|
|
1196
1556
|
isFieldBinding,
|
|
1197
1557
|
bindingAppliesTo,
|
|
1198
1558
|
getProfile as getProfile2
|
|
@@ -1212,14 +1572,18 @@ function computeContentBinding(profileId, tool, toolArgs, actionType) {
|
|
|
1212
1572
|
}
|
|
1213
1573
|
return void 0;
|
|
1214
1574
|
}
|
|
1575
|
+
const bound = selectBoundFields(binding, toolArgs);
|
|
1215
1576
|
return {
|
|
1216
|
-
contentHash:
|
|
1217
|
-
contentBinding: { version: binding.version, kind: binding.kind, fields: binding.fields }
|
|
1577
|
+
contentHash: computeContentHash(binding, bound),
|
|
1578
|
+
contentBinding: { version: binding.version, kind: binding.kind, fields: binding.fields },
|
|
1579
|
+
boundContent: bound
|
|
1218
1580
|
};
|
|
1219
1581
|
}
|
|
1220
1582
|
let contentHash;
|
|
1583
|
+
let boundContent;
|
|
1221
1584
|
if (binding.kind === "jcs") {
|
|
1222
1585
|
contentHash = computeContentHash(binding, toolArgs);
|
|
1586
|
+
boundContent = toolArgs;
|
|
1223
1587
|
} else {
|
|
1224
1588
|
const declared = tool?.gating?.contentField;
|
|
1225
1589
|
const field = declared ?? (tool ? detectContentField(tool) : null);
|
|
@@ -1227,10 +1591,12 @@ function computeContentBinding(profileId, tool, toolArgs, actionType) {
|
|
|
1227
1591
|
const raw = typeof toolArgs[field] === "string" ? toolArgs[field] : "";
|
|
1228
1592
|
if (raw === "") return void 0;
|
|
1229
1593
|
contentHash = computeContentHash(binding, raw);
|
|
1594
|
+
boundContent = raw;
|
|
1230
1595
|
}
|
|
1231
1596
|
return {
|
|
1232
1597
|
contentHash,
|
|
1233
|
-
contentBinding: { version: binding.version, kind: binding.kind }
|
|
1598
|
+
contentBinding: { version: binding.version, kind: binding.kind },
|
|
1599
|
+
boundContent
|
|
1234
1600
|
};
|
|
1235
1601
|
}
|
|
1236
1602
|
function attachReceiptId(tool, args, receiptId) {
|
|
@@ -1657,10 +2023,11 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
1657
2023
|
return inner(args);
|
|
1658
2024
|
};
|
|
1659
2025
|
if (category === "disabled") {
|
|
2026
|
+
const reason = tool.gating.disabledReason;
|
|
1660
2027
|
return async () => ({
|
|
1661
2028
|
content: [{
|
|
1662
2029
|
type: "text",
|
|
1663
|
-
text: `Tool "${tool.namespacedName}" is disabled by the integration manifest and cannot be used.`
|
|
2030
|
+
text: reason ? `Tool "${tool.namespacedName}" is refused: ${reason}. A tool that no manifest entry describes cannot be gated \u2014 there is nothing declaring which action type it performs or how its arguments map to the bounds. Add an entry for "${tool.originalName}" under this integration's toolGating.overrides (with "category": "read" for a read-only tool) before it can be used.` : `Tool "${tool.namespacedName}" is disabled by the integration manifest and cannot be used.`
|
|
1664
2031
|
}],
|
|
1665
2032
|
isError: true
|
|
1666
2033
|
});
|
|
@@ -1849,6 +2216,29 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
1849
2216
|
}
|
|
1850
2217
|
}
|
|
1851
2218
|
}
|
|
2219
|
+
const declaredActionType = typeof execution.action_type === "string" && execution.action_type.length > 0 ? execution.action_type : void 0;
|
|
2220
|
+
if (!declaredActionType) {
|
|
2221
|
+
return {
|
|
2222
|
+
content: [{
|
|
2223
|
+
type: "text",
|
|
2224
|
+
text: `Blocked by Gatekeeper: tool ${tool.namespacedName} declares no action_type in its manifest's staticExecution. A write cannot be bounds-checked without its action type \u2014 fix the integration manifest.`
|
|
2225
|
+
}],
|
|
2226
|
+
isError: true
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2229
|
+
{
|
|
2230
|
+
const profileDef = getProfile3(profile);
|
|
2231
|
+
const registry = profileDef?.boundsSchema?.actionTypes;
|
|
2232
|
+
if (Array.isArray(registry) && registry.length > 0 && !registry.includes(declaredActionType)) {
|
|
2233
|
+
return {
|
|
2234
|
+
content: [{
|
|
2235
|
+
type: "text",
|
|
2236
|
+
text: `Blocked by Gatekeeper: action_type "${declaredActionType}" is not in profile ${profile}'s actionTypes registry (${registry.join(", ")}). Fix the integration manifest.`
|
|
2237
|
+
}],
|
|
2238
|
+
isError: true
|
|
2239
|
+
};
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
1852
2242
|
const auths = state2.getEnrichedAuthorizations();
|
|
1853
2243
|
const matchingAuths = auths.filter(
|
|
1854
2244
|
(a) => a.complete && profileMatches(a.profileId, profile)
|
|
@@ -1946,12 +2336,7 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
1946
2336
|
}
|
|
1947
2337
|
let receiptId;
|
|
1948
2338
|
try {
|
|
1949
|
-
const actionType =
|
|
1950
|
-
if (!actionType) {
|
|
1951
|
-
console.error(
|
|
1952
|
-
`[Suveren MCP] Warning: tool ${tool.namespacedName} has no action_type in staticExecution. Bounds check may be skipped. Fix the integration manifest.`
|
|
1953
|
-
);
|
|
1954
|
-
}
|
|
2339
|
+
const actionType = declaredActionType;
|
|
1955
2340
|
const binding = computeContentBinding(auth.profileId, tool, args, actionType);
|
|
1956
2341
|
const { receipt } = await state2.spClient.postReceipt({
|
|
1957
2342
|
authorizationId: authzId,
|
|
@@ -1963,9 +2348,27 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
1963
2348
|
executionContext: { ...execution },
|
|
1964
2349
|
amount: typeof execution.amount === "number" ? execution.amount : void 0,
|
|
1965
2350
|
idempotencyKey: randomUUID(),
|
|
1966
|
-
|
|
2351
|
+
// Privacy: send the hash and how to reproduce it — never the
|
|
2352
|
+
// preimage. `binding` also carries `boundContent`, the plaintext
|
|
2353
|
+
// that was hashed (an email's to/cc/subject/body); it stays on
|
|
2354
|
+
// this machine for the local archive, so it must be picked out
|
|
2355
|
+
// field by field rather than spread.
|
|
2356
|
+
...binding ? { contentHash: binding.contentHash, contentBinding: binding.contentBinding } : {}
|
|
1967
2357
|
});
|
|
1968
2358
|
receiptId = typeof receipt?.id === "string" ? receipt.id : void 0;
|
|
2359
|
+
await state2.archiveReceipt(receipt, {
|
|
2360
|
+
authorizationId: authzId,
|
|
2361
|
+
profileId: auth.profileId,
|
|
2362
|
+
boundsHash: auth.boundsHash,
|
|
2363
|
+
contextHash: auth.contextHash,
|
|
2364
|
+
bounds: auth.bounds ?? auth.frame,
|
|
2365
|
+
context: auth.context,
|
|
2366
|
+
intent: auth.gateContent?.intent,
|
|
2367
|
+
attestations: auth.attestations,
|
|
2368
|
+
// Automatic path has no proposal, so without this the archive
|
|
2369
|
+
// would hold a hash of content it cannot reproduce.
|
|
2370
|
+
boundContent: binding?.boundContent
|
|
2371
|
+
});
|
|
1969
2372
|
} catch (err) {
|
|
1970
2373
|
if (err instanceof ContentBindingError) {
|
|
1971
2374
|
return {
|
|
@@ -2100,7 +2503,7 @@ function buildCapabilityMap(profileId, toolGating, integrationManager2) {
|
|
|
2100
2503
|
const shortName = shortProfileName2(profileId);
|
|
2101
2504
|
const gated = [];
|
|
2102
2505
|
const readOnly = [];
|
|
2103
|
-
const
|
|
2506
|
+
const undescribed = [];
|
|
2104
2507
|
for (const tool of allTools) {
|
|
2105
2508
|
if (!tool.gating || !tool.gating.profile) {
|
|
2106
2509
|
continue;
|
|
@@ -2122,12 +2525,7 @@ function buildCapabilityMap(profileId, toolGating, integrationManager2) {
|
|
|
2122
2525
|
const actionType = override.staticExecution?.action_type ?? "unknown";
|
|
2123
2526
|
gated.push(` - ${tool.originalName}: ${actionType}${mappingDesc ? `, ${mappingDesc}` : ""}`);
|
|
2124
2527
|
} else {
|
|
2125
|
-
|
|
2126
|
-
if (defaultAction === "read" && Object.keys(toolGating.default.executionMapping ?? {}).length === 0) {
|
|
2127
|
-
defaultGated.push(tool.originalName);
|
|
2128
|
-
} else {
|
|
2129
|
-
gated.push(` - ${tool.originalName}: ${defaultAction ?? "default"} (default gating)`);
|
|
2130
|
-
}
|
|
2528
|
+
undescribed.push(tool.originalName);
|
|
2131
2529
|
}
|
|
2132
2530
|
}
|
|
2133
2531
|
const lines = [];
|
|
@@ -2139,10 +2537,12 @@ function buildCapabilityMap(profileId, toolGating, integrationManager2) {
|
|
|
2139
2537
|
if (readOnly.length > 0) {
|
|
2140
2538
|
lines.push(` Read-only (no authorization needed): ${readOnly.join(", ")}`);
|
|
2141
2539
|
}
|
|
2142
|
-
if (
|
|
2143
|
-
lines.push(
|
|
2540
|
+
if (undescribed.length > 0) {
|
|
2541
|
+
lines.push(
|
|
2542
|
+
` Refused (no manifest entry \u2014 do not call): ${undescribed.join(", ")}`
|
|
2543
|
+
);
|
|
2144
2544
|
}
|
|
2145
|
-
if (gated.length === 0 && readOnly.length === 0 &&
|
|
2545
|
+
if (gated.length === 0 && readOnly.length === 0 && undescribed.length === 0) {
|
|
2146
2546
|
lines.push(" No tools discovered for this profile.");
|
|
2147
2547
|
}
|
|
2148
2548
|
return lines.join("\n");
|
|
@@ -2431,9 +2831,25 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
2431
2831
|
// SPClient therefore treats proposalId as retry-safe on its own; a
|
|
2432
2832
|
// PROPOSAL_ALREADY_EXECUTED answer is still a definitive rejection
|
|
2433
2833
|
// (never retried) and means a *different* caller consumed the proposal.
|
|
2434
|
-
|
|
2834
|
+
// Privacy: hash + how to reproduce it only. `binding.boundContent` is
|
|
2835
|
+
// the plaintext preimage (what the human approved) and never leaves
|
|
2836
|
+
// this machine — see the archive call below.
|
|
2837
|
+
...binding ? { contentHash: binding.contentHash, contentBinding: binding.contentBinding } : {}
|
|
2435
2838
|
});
|
|
2436
2839
|
receiptId = typeof receipt?.id === "string" ? receipt.id : void 0;
|
|
2840
|
+
await state2.archiveReceipt(receipt, {
|
|
2841
|
+
authorizationId: proposal.authorizationId,
|
|
2842
|
+
profileId: proposal.profileId,
|
|
2843
|
+
boundsHash: cachedAuth?.boundsHash,
|
|
2844
|
+
contextHash: cachedAuth?.contextHash,
|
|
2845
|
+
bounds: cachedAuth?.bounds ?? cachedAuth?.frame,
|
|
2846
|
+
context: cachedAuth?.context,
|
|
2847
|
+
attestations: cachedAuth?.attestations,
|
|
2848
|
+
// What the human actually approved, kept next to the receipt that cites
|
|
2849
|
+
// it — otherwise `proposalId` is a pointer to a server we may not have.
|
|
2850
|
+
proposal,
|
|
2851
|
+
boundContent: binding?.boundContent
|
|
2852
|
+
});
|
|
2437
2853
|
} catch (err) {
|
|
2438
2854
|
if (err instanceof ContentBindingError2) {
|
|
2439
2855
|
return {
|
|
@@ -2756,16 +3172,16 @@ function verifyGateContentHashes(content, auth) {
|
|
|
2756
3172
|
}
|
|
2757
3173
|
|
|
2758
3174
|
// src/lib/integration-registry.ts
|
|
2759
|
-
import { mkdirSync as
|
|
2760
|
-
import { homedir as
|
|
2761
|
-
import { dirname as
|
|
2762
|
-
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
3175
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7 } from "fs";
|
|
3176
|
+
import { homedir as homedir6 } from "os";
|
|
3177
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
3178
|
+
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join7(homedir6(), ".suveren");
|
|
2763
3179
|
var IntegrationRegistry = class {
|
|
2764
3180
|
integrations = /* @__PURE__ */ new Map();
|
|
2765
3181
|
filePath;
|
|
2766
3182
|
constructor(dataDir) {
|
|
2767
3183
|
const dir = dataDir ?? DEFAULT_DATA_DIR;
|
|
2768
|
-
this.filePath =
|
|
3184
|
+
this.filePath = join7(dir, "integrations.json");
|
|
2769
3185
|
this.load();
|
|
2770
3186
|
}
|
|
2771
3187
|
// ─── CRUD ───────────────────────────────────────────────────────────────
|
|
@@ -2796,12 +3212,12 @@ var IntegrationRegistry = class {
|
|
|
2796
3212
|
}
|
|
2797
3213
|
// ─── Load / Save ────────────────────────────────────────────────────────
|
|
2798
3214
|
load() {
|
|
2799
|
-
if (!
|
|
2800
|
-
|
|
3215
|
+
if (!existsSync7(this.filePath)) {
|
|
3216
|
+
mkdirSync5(dirname5(this.filePath), { recursive: true });
|
|
2801
3217
|
return;
|
|
2802
3218
|
}
|
|
2803
3219
|
try {
|
|
2804
|
-
const raw =
|
|
3220
|
+
const raw = readFileSync7(this.filePath, "utf-8");
|
|
2805
3221
|
const data = JSON.parse(raw);
|
|
2806
3222
|
const migrated = data.integrations.map((i) => {
|
|
2807
3223
|
if ("toolGating" in i && !("profile" in i)) {
|
|
@@ -2830,33 +3246,39 @@ var IntegrationRegistry = class {
|
|
|
2830
3246
|
version: 1,
|
|
2831
3247
|
integrations: Array.from(this.integrations.values())
|
|
2832
3248
|
};
|
|
2833
|
-
|
|
2834
|
-
|
|
3249
|
+
mkdirSync5(dirname5(this.filePath), { recursive: true });
|
|
3250
|
+
writeFileSync5(this.filePath, JSON.stringify(data, null, 2), { encoding: "utf-8" });
|
|
2835
3251
|
}
|
|
2836
3252
|
};
|
|
2837
3253
|
|
|
2838
3254
|
// src/lib/integration-manager.ts
|
|
2839
|
-
import { homedir as
|
|
2840
|
-
import { join as
|
|
2841
|
-
import { existsSync as
|
|
3255
|
+
import { homedir as homedir7 } from "os";
|
|
3256
|
+
import { join as join8, delimiter } from "path";
|
|
3257
|
+
import { existsSync as existsSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync8, rmSync } from "fs";
|
|
2842
3258
|
import { execFile } from "child_process";
|
|
2843
3259
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2844
3260
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
2845
3261
|
import { getProfile as getProfile5 } from "@hap/core";
|
|
2846
|
-
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ??
|
|
2847
|
-
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ??
|
|
2848
|
-
var
|
|
3262
|
+
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join8(homedir7(), ".suveren");
|
|
3263
|
+
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ?? join8(DEFAULT_DATA_DIR2, "integrations");
|
|
3264
|
+
var installLock = Promise.resolve();
|
|
3265
|
+
function withInstallLock(task) {
|
|
3266
|
+
const run = installLock.then(task, task);
|
|
3267
|
+
installLock = run.then(() => void 0, () => void 0);
|
|
3268
|
+
return run;
|
|
3269
|
+
}
|
|
3270
|
+
var INTEGRATIONS_BIN = join8(INTEGRATIONS_DIR, "node_modules", ".bin");
|
|
2849
3271
|
function buildPath() {
|
|
2850
3272
|
const base = process.env.PATH ?? "";
|
|
2851
3273
|
return [INTEGRATIONS_BIN, base].join(delimiter);
|
|
2852
3274
|
}
|
|
2853
3275
|
function ensureIntegrationsDir() {
|
|
2854
|
-
if (!
|
|
2855
|
-
|
|
3276
|
+
if (!existsSync8(INTEGRATIONS_DIR)) {
|
|
3277
|
+
mkdirSync6(INTEGRATIONS_DIR, { recursive: true });
|
|
2856
3278
|
}
|
|
2857
|
-
const pkgPath =
|
|
2858
|
-
if (!
|
|
2859
|
-
|
|
3279
|
+
const pkgPath = join8(INTEGRATIONS_DIR, "package.json");
|
|
3280
|
+
if (!existsSync8(pkgPath)) {
|
|
3281
|
+
writeFileSync6(pkgPath, JSON.stringify({ name: "suveren-integrations", version: "1.0.0", private: true }, null, 2));
|
|
2860
3282
|
}
|
|
2861
3283
|
}
|
|
2862
3284
|
function readAgeOf(config) {
|
|
@@ -2922,19 +3344,19 @@ var IntegrationManager = class {
|
|
|
2922
3344
|
* exist, and treat anything else as not-installed (and reinstallable).
|
|
2923
3345
|
*/
|
|
2924
3346
|
isUsableInstall(npmPackage) {
|
|
2925
|
-
const pkgDir =
|
|
2926
|
-
const pkgJson =
|
|
2927
|
-
if (!
|
|
3347
|
+
const pkgDir = join8(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
3348
|
+
const pkgJson = join8(pkgDir, "package.json");
|
|
3349
|
+
if (!existsSync8(pkgDir) || !existsSync8(pkgJson)) return false;
|
|
2928
3350
|
try {
|
|
2929
|
-
const pkg = JSON.parse(
|
|
3351
|
+
const pkg = JSON.parse(readFileSync8(pkgJson, "utf8"));
|
|
2930
3352
|
const defaultBinName = (pkg.name ?? npmPackage).split("/").pop() ?? "";
|
|
2931
3353
|
const binMap = typeof pkg.bin === "string" ? { [defaultBinName]: pkg.bin } : pkg.bin ?? {};
|
|
2932
3354
|
const binEntries = Object.entries(binMap);
|
|
2933
3355
|
for (const [binName, target] of binEntries) {
|
|
2934
|
-
if (!
|
|
3356
|
+
if (!existsSync8(join8(pkgDir, target))) return false;
|
|
2935
3357
|
if (!this.binShimExists(binName)) return false;
|
|
2936
3358
|
}
|
|
2937
|
-
if (binEntries.length === 0 && pkg.main && !
|
|
3359
|
+
if (binEntries.length === 0 && pkg.main && !existsSync8(join8(pkgDir, pkg.main))) return false;
|
|
2938
3360
|
return true;
|
|
2939
3361
|
} catch {
|
|
2940
3362
|
return false;
|
|
@@ -2948,7 +3370,7 @@ var IntegrationManager = class {
|
|
|
2948
3370
|
binShimExists(binName) {
|
|
2949
3371
|
if (!binName) return false;
|
|
2950
3372
|
const candidates = process.platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.CMD`, `${binName}.ps1`] : [binName];
|
|
2951
|
-
return candidates.some((c) =>
|
|
3373
|
+
return candidates.some((c) => existsSync8(join8(INTEGRATIONS_BIN, c)));
|
|
2952
3374
|
}
|
|
2953
3375
|
/**
|
|
2954
3376
|
* Install an npm package into the managed integrations directory if not
|
|
@@ -2965,8 +3387,15 @@ var IntegrationManager = class {
|
|
|
2965
3387
|
async ensureInstalled(npmPackage) {
|
|
2966
3388
|
ensureIntegrationsDir();
|
|
2967
3389
|
if (this.isUsableInstall(npmPackage)) return;
|
|
2968
|
-
|
|
2969
|
-
|
|
3390
|
+
return withInstallLock(async () => {
|
|
3391
|
+
if (this.isUsableInstall(npmPackage)) return;
|
|
3392
|
+
await this.installNow(npmPackage);
|
|
3393
|
+
});
|
|
3394
|
+
}
|
|
3395
|
+
/** The actual install. Callers MUST hold the install lock. */
|
|
3396
|
+
async installNow(npmPackage) {
|
|
3397
|
+
const pkgDir = join8(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
3398
|
+
if (existsSync8(pkgDir)) {
|
|
2970
3399
|
console.error(`[IntegrationManager] Removing unusable install of ${npmPackage}`);
|
|
2971
3400
|
try {
|
|
2972
3401
|
rmSync(pkgDir, { recursive: true, force: true });
|
|
@@ -3338,8 +3767,9 @@ var IntegrationManager = class {
|
|
|
3338
3767
|
}
|
|
3339
3768
|
return {
|
|
3340
3769
|
profile: profileId,
|
|
3341
|
-
executionMapping:
|
|
3342
|
-
|
|
3770
|
+
executionMapping: {},
|
|
3771
|
+
category: "disabled",
|
|
3772
|
+
disabledReason: "not described in manifest"
|
|
3343
3773
|
};
|
|
3344
3774
|
}
|
|
3345
3775
|
/**
|
|
@@ -3381,28 +3811,28 @@ var IntegrationManager = class {
|
|
|
3381
3811
|
};
|
|
3382
3812
|
|
|
3383
3813
|
// src/lib/profile-loader.ts
|
|
3384
|
-
import { readFileSync as
|
|
3385
|
-
import { join as
|
|
3814
|
+
import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
|
|
3815
|
+
import { join as join9, resolve } from "path";
|
|
3386
3816
|
import { registerProfile } from "@hap/core";
|
|
3387
3817
|
function loadProfiles(profilesDir) {
|
|
3388
|
-
const dir = resolve(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ??
|
|
3389
|
-
const indexPath =
|
|
3390
|
-
if (!
|
|
3818
|
+
const dir = resolve(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ?? join9(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "..", "hap-profiles"));
|
|
3819
|
+
const indexPath = join9(dir, "index.json");
|
|
3820
|
+
if (!existsSync9(indexPath)) {
|
|
3391
3821
|
warnNoProfiles(`No index.json at ${indexPath}`);
|
|
3392
3822
|
return 0;
|
|
3393
3823
|
}
|
|
3394
3824
|
let index;
|
|
3395
3825
|
try {
|
|
3396
|
-
index = JSON.parse(
|
|
3826
|
+
index = JSON.parse(readFileSync9(indexPath, "utf-8"));
|
|
3397
3827
|
} catch (err) {
|
|
3398
3828
|
console.error(`[ProfileLoader] Failed to parse ${indexPath}:`, err);
|
|
3399
3829
|
return 0;
|
|
3400
3830
|
}
|
|
3401
3831
|
let loaded = 0;
|
|
3402
3832
|
for (const [profileId, relativePath] of Object.entries(index.profiles)) {
|
|
3403
|
-
const profilePath =
|
|
3833
|
+
const profilePath = join9(dir, relativePath);
|
|
3404
3834
|
try {
|
|
3405
|
-
const profile = JSON.parse(
|
|
3835
|
+
const profile = JSON.parse(readFileSync9(profilePath, "utf-8"));
|
|
3406
3836
|
registerProfile(profileId, profile);
|
|
3407
3837
|
const shortName = profileId.split("/").pop()?.replace(/@.*$/, "");
|
|
3408
3838
|
if (shortName && shortName !== profileId) {
|
|
@@ -3435,30 +3865,30 @@ function warnNoProfiles(reason) {
|
|
|
3435
3865
|
}
|
|
3436
3866
|
|
|
3437
3867
|
// src/lib/manifest-loader.ts
|
|
3438
|
-
import { readFileSync as
|
|
3439
|
-
import { join as
|
|
3868
|
+
import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
|
|
3869
|
+
import { join as join10, resolve as resolve2 } from "path";
|
|
3440
3870
|
var manifests = /* @__PURE__ */ new Map();
|
|
3441
3871
|
function loadManifests(integrationsDir) {
|
|
3442
3872
|
const dir = resolve2(
|
|
3443
|
-
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ??
|
|
3873
|
+
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join10(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
|
|
3444
3874
|
);
|
|
3445
|
-
const indexPath =
|
|
3446
|
-
if (!
|
|
3875
|
+
const indexPath = join10(dir, "index.json");
|
|
3876
|
+
if (!existsSync10(indexPath)) {
|
|
3447
3877
|
console.error(`[ManifestLoader] No index.json found at ${indexPath}, skipping manifest loading`);
|
|
3448
3878
|
return 0;
|
|
3449
3879
|
}
|
|
3450
3880
|
let index;
|
|
3451
3881
|
try {
|
|
3452
|
-
index = JSON.parse(
|
|
3882
|
+
index = JSON.parse(readFileSync10(indexPath, "utf-8"));
|
|
3453
3883
|
} catch (err) {
|
|
3454
3884
|
console.error(`[ManifestLoader] Failed to parse ${indexPath}:`, err);
|
|
3455
3885
|
return 0;
|
|
3456
3886
|
}
|
|
3457
3887
|
let loaded = 0;
|
|
3458
3888
|
for (const [id, relativePath] of Object.entries(index.integrations)) {
|
|
3459
|
-
const manifestPath =
|
|
3889
|
+
const manifestPath = join10(dir, relativePath);
|
|
3460
3890
|
try {
|
|
3461
|
-
const manifest = JSON.parse(
|
|
3891
|
+
const manifest = JSON.parse(readFileSync10(manifestPath, "utf-8"));
|
|
3462
3892
|
manifests.set(id, manifest);
|
|
3463
3893
|
loaded++;
|
|
3464
3894
|
} catch (err) {
|
|
@@ -3476,6 +3906,7 @@ function getAllManifests() {
|
|
|
3476
3906
|
}
|
|
3477
3907
|
|
|
3478
3908
|
// bin/http.ts
|
|
3909
|
+
import { decodeAttestationBlob as decodeAttestationBlob3 } from "@hap/core";
|
|
3479
3910
|
var spUrl = process.env.SUVEREN_AS_URL ?? "https://www.suveren.ai";
|
|
3480
3911
|
var port = parseInt(process.env.SUVEREN_MCP_PORT ?? "3430", 10);
|
|
3481
3912
|
var state = new SharedState(spUrl);
|
|
@@ -3539,7 +3970,8 @@ app.post("/internal/configure", internalOnly, (req, res) => {
|
|
|
3539
3970
|
const key = Buffer.from(vaultKeyHex, "hex");
|
|
3540
3971
|
state.gateStore.setVaultKey(key);
|
|
3541
3972
|
state.denialLog.setVaultKey(key);
|
|
3542
|
-
|
|
3973
|
+
state.receiptArchive.setVaultKey(key);
|
|
3974
|
+
console.error("[Suveren MCP] Vault key configured \u2014 gate store + denial log + receipt archive encryption active");
|
|
3543
3975
|
}
|
|
3544
3976
|
if (apiKey) {
|
|
3545
3977
|
state.spClient.setApiKey(apiKey);
|
|
@@ -3836,6 +4268,27 @@ app.get("/internal/authorizations", internalOnly, (_req, res) => {
|
|
|
3836
4268
|
app.get("/internal/manifests", internalOnly, (_req, res) => {
|
|
3837
4269
|
res.json({ manifests: getAllManifests() });
|
|
3838
4270
|
});
|
|
4271
|
+
app.get("/internal/evidence", internalOnly, (_req, res) => {
|
|
4272
|
+
if (state.receiptArchive.isLocked()) {
|
|
4273
|
+
res.status(503).json({ error: "Vault locked \u2014 the evidence archive is encrypted until sign-in." });
|
|
4274
|
+
return;
|
|
4275
|
+
}
|
|
4276
|
+
const authorizations = state.receiptArchive.getAuthorizations().map((a) => ({
|
|
4277
|
+
...a,
|
|
4278
|
+
attestations: a.attestations.map((att) => {
|
|
4279
|
+
try {
|
|
4280
|
+
return { ...att, decoded: decodeAttestationBlob3(att.blob) };
|
|
4281
|
+
} catch {
|
|
4282
|
+
return att;
|
|
4283
|
+
}
|
|
4284
|
+
})
|
|
4285
|
+
}));
|
|
4286
|
+
res.json({
|
|
4287
|
+
receipts: state.receiptArchive.getReceipts(),
|
|
4288
|
+
authorizations,
|
|
4289
|
+
gates: state.gateStore.getAll()
|
|
4290
|
+
});
|
|
4291
|
+
});
|
|
3839
4292
|
app.get("/internal/brief", internalOnly, (_req, res) => {
|
|
3840
4293
|
try {
|
|
3841
4294
|
const enriched = state.getEnrichedAuthorizations();
|