@suveren/gateway 0.7.3 → 0.7.6
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/gmail.json +3 -6
- package/dist/control-plane/index.mjs +294 -62
- package/dist/mcp-server/http.mjs +467 -73
- package/dist/ui/assets/{index-CkWaRck1.css → index-BYVb_0oG.css} +1 -1
- package/dist/ui/assets/index-CsrM6RQr.js +110 -0
- package/dist/ui/index.html +2 -2
- package/node_modules/@hap/core/dist/index.d.mts +258 -9
- package/node_modules/@hap/core/dist/index.d.ts +258 -9
- package/node_modules/@hap/core/dist/index.js +227 -5
- package/node_modules/@hap/core/dist/index.mjs +213 -4
- package/node_modules/@hap/core/package.json +3 -2
- package/node_modules/@hap/core/src/did-key.ts +126 -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 +81 -5
- package/node_modules/@hono/node-server/dist/index.cjs +151 -9
- package/node_modules/@hono/node-server/dist/index.mjs +151 -9
- package/node_modules/@hono/node-server/package.json +1 -1
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/README.md +2 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/dist/index.d.ts +20 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/dist/index.js +20 -14
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/dist/index.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/package.json +1 -1
- 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/@modelcontextprotocol/sdk/node_modules/type-is/node_modules/content-type/README.md +2 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/type-is/node_modules/content-type/dist/index.d.ts +20 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/type-is/node_modules/content-type/dist/index.js +20 -14
- package/node_modules/@modelcontextprotocol/sdk/node_modules/type-is/node_modules/content-type/dist/index.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/node_modules/type-is/node_modules/content-type/package.json +1 -1
- package/node_modules/@types/node/README.md +1 -1
- package/node_modules/@types/node/buffer.buffer.d.ts +5 -5
- package/node_modules/@types/node/crypto.d.ts +2 -2
- package/node_modules/@types/node/http.d.ts +16 -0
- package/node_modules/@types/node/http2.d.ts +10 -2
- 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 +210 -28
- package/node_modules/@types/node/sqlite.d.ts +6 -2
- package/node_modules/@types/node/test.d.ts +36 -0
- package/node_modules/eventsource-parser/dist/index.cjs +2 -2
- package/node_modules/eventsource-parser/dist/index.cjs.map +1 -1
- package/node_modules/eventsource-parser/dist/index.js +2 -2
- package/node_modules/eventsource-parser/dist/index.js.map +1 -1
- package/node_modules/eventsource-parser/package.json +1 -1
- package/node_modules/eventsource-parser/src/parse.ts +4 -2
- package/node_modules/fast-uri/index.js +201 -48
- package/node_modules/fast-uri/lib/schemes.js +9 -4
- package/node_modules/fast-uri/lib/utils.js +388 -91
- package/node_modules/fast-uri/package.json +1 -1
- package/node_modules/fast-uri/test/component-safe-serialization.test.js +105 -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 +90 -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 +27 -13
- 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/jsx/intrinsic-element/components.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/cache/index.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/cors/index.js +16 -13
- package/node_modules/hono/dist/cjs/middleware/csrf/index.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/etag/digest.js +47 -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/middleware/secure-headers/secure-headers.js +6 -3
- 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 +11 -3
- package/node_modules/hono/dist/client/client.js +27 -13
- 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/jsx/intrinsic-element/components.js +1 -1
- package/node_modules/hono/dist/middleware/cache/index.js +1 -1
- package/node_modules/hono/dist/middleware/cors/index.js +16 -13
- package/node_modules/hono/dist/middleware/csrf/index.js +1 -1
- package/node_modules/hono/dist/middleware/etag/digest.js +47 -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/middleware/secure-headers/secure-headers.js +6 -3
- 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/middleware/secure-headers/permissions-policy.d.ts +3 -3
- 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 +11 -3
- package/node_modules/hono/package.json +1 -1
- package/node_modules/ip-address/README.md +134 -134
- package/node_modules/ip-address/dist/address-error.d.ts +11 -0
- package/node_modules/ip-address/dist/address-error.js.map +1 -1
- package/node_modules/ip-address/dist/ipv4.d.ts +22 -6
- package/node_modules/ip-address/dist/ipv4.js +22 -6
- package/node_modules/ip-address/dist/ipv4.js.map +1 -1
- package/node_modules/ip-address/dist/ipv6.d.ts +10 -3
- package/node_modules/ip-address/dist/ipv6.js +26 -17
- package/node_modules/ip-address/dist/ipv6.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/encrypt.d.ts +4 -3
- package/node_modules/jose/dist/types/jwe/flattened/encrypt.d.ts +4 -3
- package/node_modules/jose/dist/types/jwe/general/encrypt.d.ts +4 -3
- package/node_modules/jose/dist/types/jwks/remote.d.ts +4 -4
- package/node_modules/jose/dist/types/jws/general/verify.d.ts +3 -3
- package/node_modules/jose/dist/types/jwt/encrypt.d.ts +12 -17
- package/node_modules/jose/dist/types/jwt/sign.d.ts +8 -14
- package/node_modules/jose/dist/types/jwt/unsecured.d.ts +9 -15
- package/node_modules/jose/dist/types/types.d.ts +4 -2
- package/node_modules/jose/dist/webapi/jwe/compact/encrypt.js +30 -8
- package/node_modules/jose/dist/webapi/jwe/flattened/decrypt.js +8 -4
- package/node_modules/jose/dist/webapi/jwe/flattened/encrypt.js +6 -13
- package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +19 -19
- package/node_modules/jose/dist/webapi/jwe/general/encrypt.js +32 -21
- package/node_modules/jose/dist/webapi/jwk/embedded.js +15 -1
- package/node_modules/jose/dist/webapi/jwk/thumbprint.js +11 -5
- package/node_modules/jose/dist/webapi/jwks/local.js +45 -53
- package/node_modules/jose/dist/webapi/jwks/remote.js +82 -103
- package/node_modules/jose/dist/webapi/jws/compact/sign.js +10 -9
- package/node_modules/jose/dist/webapi/jws/flattened/sign.js +2 -1
- package/node_modules/jose/dist/webapi/jws/flattened/verify.js +8 -7
- package/node_modules/jose/dist/webapi/jws/general/sign.js +5 -3
- package/node_modules/jose/dist/webapi/jws/general/verify.js +51 -21
- package/node_modules/jose/dist/webapi/jwt/decrypt.js +7 -9
- package/node_modules/jose/dist/webapi/jwt/encrypt.js +21 -50
- package/node_modules/jose/dist/webapi/jwt/sign.js +8 -41
- package/node_modules/jose/dist/webapi/jwt/unsecured.js +21 -42
- package/node_modules/jose/dist/webapi/key/generate_key_pair.js +6 -2
- package/node_modules/jose/dist/webapi/key/generate_secret.js +7 -6
- package/node_modules/jose/dist/webapi/key/import.js +16 -12
- package/node_modules/jose/dist/webapi/lib/asn1.js +7 -2
- package/node_modules/jose/dist/webapi/lib/content_encryption.js +7 -15
- package/node_modules/jose/dist/webapi/lib/deflate.js +8 -0
- package/node_modules/jose/dist/webapi/lib/helpers.js +1 -1
- package/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +85 -28
- package/node_modules/jose/dist/webapi/lib/jwe_encrypt.js +45 -13
- package/node_modules/jose/dist/webapi/lib/jwk_metadata.js +20 -0
- package/node_modules/jose/dist/webapi/lib/jws_sign.js +47 -36
- package/node_modules/jose/dist/webapi/lib/jws_verify.js +77 -45
- package/node_modules/jose/dist/webapi/lib/jwt_claims_set.js +82 -47
- package/node_modules/jose/dist/webapi/lib/key.js +24 -9
- package/node_modules/jose/dist/webapi/lib/key_management.js +10 -4
- package/node_modules/jose/dist/webapi/lib/key_options.js +6 -0
- package/node_modules/jose/dist/webapi/lib/options.js +26 -0
- package/node_modules/jose/dist/webapi/lib/type_checks.js +11 -13
- package/node_modules/jose/package.json +1 -1
- package/package.json +2 -2
- package/dist/ui/assets/index-DdJeSfV9.js +0 -110
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/HISTORY.md +0 -114
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -55,6 +55,10 @@ var SPClient = class {
|
|
|
55
55
|
sessionCookie = "";
|
|
56
56
|
apiKey = "";
|
|
57
57
|
receiptRetry;
|
|
58
|
+
/** The AS base URL this client talks to (for provenance records). */
|
|
59
|
+
get url() {
|
|
60
|
+
return this.baseUrl;
|
|
61
|
+
}
|
|
58
62
|
setApiKey(key) {
|
|
59
63
|
this.apiKey = key;
|
|
60
64
|
}
|
|
@@ -137,7 +141,7 @@ var SPClient = class {
|
|
|
137
141
|
*/
|
|
138
142
|
async postReceipt(data) {
|
|
139
143
|
const body = JSON.stringify(data);
|
|
140
|
-
const maxAttempts = data.idempotencyKey ? this.receiptRetry.maxAttempts : 1;
|
|
144
|
+
const maxAttempts = data.idempotencyKey || data.proposalId ? this.receiptRetry.maxAttempts : 1;
|
|
141
145
|
let lastError;
|
|
142
146
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
143
147
|
const isLast = attempt === maxAttempts;
|
|
@@ -825,6 +829,229 @@ var DenialLog = class {
|
|
|
825
829
|
}
|
|
826
830
|
};
|
|
827
831
|
|
|
832
|
+
// src/lib/receipt-archive.ts
|
|
833
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync4, unlinkSync as unlinkSync4 } from "fs";
|
|
834
|
+
import { homedir as homedir4 } from "os";
|
|
835
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
836
|
+
import { createCipheriv as createCipheriv4, createDecipheriv as createDecipheriv4, randomBytes as randomBytes4 } from "crypto";
|
|
837
|
+
var DEFAULT_DIR4 = process.env.SUVEREN_DATA_DIR ?? join4(homedir4(), ".suveren");
|
|
838
|
+
function stripPreviews(proposal) {
|
|
839
|
+
const args = proposal.toolArgs;
|
|
840
|
+
if (!args || typeof args !== "object") return proposal;
|
|
841
|
+
const { _imagePreview, ...rest } = args;
|
|
842
|
+
if (_imagePreview === void 0) return proposal;
|
|
843
|
+
return { ...proposal, toolArgs: rest };
|
|
844
|
+
}
|
|
845
|
+
var ReceiptArchive = class {
|
|
846
|
+
data = { version: 1, authorizations: {}, receipts: [] };
|
|
847
|
+
baseDir;
|
|
848
|
+
vaultKey = null;
|
|
849
|
+
constructor(filePath) {
|
|
850
|
+
if (filePath && filePath.endsWith(".json")) {
|
|
851
|
+
this.baseDir = dirname4(filePath);
|
|
852
|
+
} else {
|
|
853
|
+
this.baseDir = filePath ?? DEFAULT_DIR4;
|
|
854
|
+
}
|
|
855
|
+
this.loadPlaintext();
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Set the vault key for encryption. Triggers migration from plaintext if needed.
|
|
859
|
+
*/
|
|
860
|
+
setVaultKey(key) {
|
|
861
|
+
this.vaultKey = key;
|
|
862
|
+
if (existsSync4(this.encryptedFilePath)) {
|
|
863
|
+
this.loadEncrypted();
|
|
864
|
+
} else if (this.data.receipts.length > 0 || Object.keys(this.data.authorizations).length > 0) {
|
|
865
|
+
this.persistEncrypted();
|
|
866
|
+
if (existsSync4(this.plaintextFilePath)) {
|
|
867
|
+
try {
|
|
868
|
+
unlinkSync4(this.plaintextFilePath);
|
|
869
|
+
} catch {
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* True when encrypted evidence exists on disk that this process cannot read
|
|
876
|
+
* yet (no vault key). Callers MUST NOT present the archive as "empty" in
|
|
877
|
+
* this state — that would report a hollow success over unreadable evidence.
|
|
878
|
+
*/
|
|
879
|
+
isLocked() {
|
|
880
|
+
return this.vaultKey === null && existsSync4(this.encryptedFilePath);
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Archive a signed receipt (and the grant it was issued under).
|
|
884
|
+
* Idempotent on receipt id — a replayed receipt is recorded once.
|
|
885
|
+
*/
|
|
886
|
+
record(entry, now = Math.floor(Date.now() / 1e3)) {
|
|
887
|
+
let changed = false;
|
|
888
|
+
const receiptId = typeof entry.receipt?.id === "string" ? entry.receipt.id : void 0;
|
|
889
|
+
const alreadyArchived = receiptId !== void 0 && this.data.receipts.some((r) => r.receipt.id === receiptId);
|
|
890
|
+
if (!alreadyArchived) {
|
|
891
|
+
this.data.receipts.push({
|
|
892
|
+
archivedAt: now,
|
|
893
|
+
authorizationId: entry.authorizationId,
|
|
894
|
+
asUrl: entry.asUrl,
|
|
895
|
+
asPublicKey: entry.asPublicKey,
|
|
896
|
+
receipt: entry.receipt,
|
|
897
|
+
...entry.proposal ? { proposal: stripPreviews(entry.proposal) } : {},
|
|
898
|
+
...entry.boundContent !== void 0 ? { boundContent: entry.boundContent } : {}
|
|
899
|
+
});
|
|
900
|
+
changed = true;
|
|
901
|
+
}
|
|
902
|
+
if (entry.authorization) {
|
|
903
|
+
const existing = this.data.authorizations[entry.authorizationId];
|
|
904
|
+
if (!existing) {
|
|
905
|
+
this.data.authorizations[entry.authorizationId] = {
|
|
906
|
+
authorizationId: entry.authorizationId,
|
|
907
|
+
profileId: entry.authorization.profileId,
|
|
908
|
+
boundsHash: entry.authorization.boundsHash,
|
|
909
|
+
contextHash: entry.authorization.contextHash,
|
|
910
|
+
bounds: entry.authorization.bounds,
|
|
911
|
+
context: entry.authorization.context,
|
|
912
|
+
intent: entry.authorization.intent,
|
|
913
|
+
attestations: [...entry.authorization.attestations],
|
|
914
|
+
archivedAt: now
|
|
915
|
+
};
|
|
916
|
+
changed = true;
|
|
917
|
+
} else {
|
|
918
|
+
for (const att of entry.authorization.attestations) {
|
|
919
|
+
const seen = existing.attestations.some(
|
|
920
|
+
(a) => a.domain === att.domain && a.blob === att.blob
|
|
921
|
+
);
|
|
922
|
+
if (!seen) {
|
|
923
|
+
existing.attestations.push(att);
|
|
924
|
+
changed = true;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
if (!existing.bounds && entry.authorization.bounds) {
|
|
928
|
+
existing.bounds = entry.authorization.bounds;
|
|
929
|
+
changed = true;
|
|
930
|
+
}
|
|
931
|
+
if (!existing.context && entry.authorization.context) {
|
|
932
|
+
existing.context = entry.authorization.context;
|
|
933
|
+
changed = true;
|
|
934
|
+
}
|
|
935
|
+
if (!existing.intent && entry.authorization.intent) {
|
|
936
|
+
existing.intent = entry.authorization.intent;
|
|
937
|
+
changed = true;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (changed) this.persist();
|
|
942
|
+
}
|
|
943
|
+
getReceipts() {
|
|
944
|
+
return [...this.data.receipts];
|
|
945
|
+
}
|
|
946
|
+
getAuthorizations() {
|
|
947
|
+
return Object.values(this.data.authorizations);
|
|
948
|
+
}
|
|
949
|
+
get size() {
|
|
950
|
+
return this.data.receipts.length;
|
|
951
|
+
}
|
|
952
|
+
// ─── Encryption helpers ─────────────────────────────────────────────────
|
|
953
|
+
encrypt(plaintext) {
|
|
954
|
+
if (!this.vaultKey) throw new Error("No vault key");
|
|
955
|
+
const iv = randomBytes4(12);
|
|
956
|
+
const cipher = createCipheriv4("aes-256-gcm", this.vaultKey, iv);
|
|
957
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
958
|
+
const tag = cipher.getAuthTag();
|
|
959
|
+
return {
|
|
960
|
+
iv: iv.toString("hex"),
|
|
961
|
+
ciphertext: encrypted.toString("hex"),
|
|
962
|
+
tag: tag.toString("hex")
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
decrypt(blob) {
|
|
966
|
+
if (!this.vaultKey) throw new Error("No vault key");
|
|
967
|
+
const decipher = createDecipheriv4(
|
|
968
|
+
"aes-256-gcm",
|
|
969
|
+
this.vaultKey,
|
|
970
|
+
Buffer.from(blob.iv, "hex")
|
|
971
|
+
);
|
|
972
|
+
decipher.setAuthTag(Buffer.from(blob.tag, "hex"));
|
|
973
|
+
const decrypted = Buffer.concat([
|
|
974
|
+
decipher.update(Buffer.from(blob.ciphertext, "hex")),
|
|
975
|
+
decipher.final()
|
|
976
|
+
]);
|
|
977
|
+
return decrypted.toString("utf8");
|
|
978
|
+
}
|
|
979
|
+
// ─── File paths ─────────────────────────────────────────────────────────
|
|
980
|
+
get plaintextFilePath() {
|
|
981
|
+
return join4(this.baseDir, "receipt-archive.json");
|
|
982
|
+
}
|
|
983
|
+
get encryptedFilePath() {
|
|
984
|
+
return join4(this.baseDir, "receipt-archive.enc.json");
|
|
985
|
+
}
|
|
986
|
+
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
987
|
+
loadPlaintext() {
|
|
988
|
+
if (!existsSync4(this.plaintextFilePath)) {
|
|
989
|
+
mkdirSync4(this.baseDir, { recursive: true });
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
try {
|
|
993
|
+
const raw = readFileSync4(this.plaintextFilePath, "utf-8");
|
|
994
|
+
const parsed = JSON.parse(raw);
|
|
995
|
+
this.data = {
|
|
996
|
+
version: 1,
|
|
997
|
+
authorizations: parsed.authorizations ?? {},
|
|
998
|
+
receipts: parsed.receipts ?? []
|
|
999
|
+
};
|
|
1000
|
+
} catch {
|
|
1001
|
+
console.error(`[ReceiptArchive] Could not parse ${this.plaintextFilePath} \u2014 preserving as .corrupt`);
|
|
1002
|
+
try {
|
|
1003
|
+
writeFileSync4(
|
|
1004
|
+
`${this.plaintextFilePath}.corrupt-${Date.now()}`,
|
|
1005
|
+
readFileSync4(this.plaintextFilePath)
|
|
1006
|
+
);
|
|
1007
|
+
} catch {
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
loadEncrypted() {
|
|
1012
|
+
if (!existsSync4(this.encryptedFilePath)) return;
|
|
1013
|
+
try {
|
|
1014
|
+
const raw = readFileSync4(this.encryptedFilePath, "utf-8");
|
|
1015
|
+
const data = JSON.parse(raw);
|
|
1016
|
+
const decrypted = this.decrypt(data.blob);
|
|
1017
|
+
const parsed = JSON.parse(decrypted);
|
|
1018
|
+
this.data = {
|
|
1019
|
+
version: 1,
|
|
1020
|
+
authorizations: parsed.authorizations ?? {},
|
|
1021
|
+
receipts: parsed.receipts ?? []
|
|
1022
|
+
};
|
|
1023
|
+
} catch (err) {
|
|
1024
|
+
console.error(`[ReceiptArchive] Could not decrypt ${this.encryptedFilePath} \u2014 preserving as .corrupt:`, err);
|
|
1025
|
+
try {
|
|
1026
|
+
writeFileSync4(
|
|
1027
|
+
`${this.encryptedFilePath}.corrupt-${Date.now()}`,
|
|
1028
|
+
readFileSync4(this.encryptedFilePath)
|
|
1029
|
+
);
|
|
1030
|
+
} catch {
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
persist() {
|
|
1035
|
+
if (this.vaultKey) {
|
|
1036
|
+
this.persistEncrypted();
|
|
1037
|
+
} else {
|
|
1038
|
+
this.persistPlaintext();
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
persistPlaintext() {
|
|
1042
|
+
mkdirSync4(this.baseDir, { recursive: true, mode: 448 });
|
|
1043
|
+
writeFileSync4(this.plaintextFilePath, JSON.stringify(this.data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1044
|
+
}
|
|
1045
|
+
persistEncrypted() {
|
|
1046
|
+
const data = {
|
|
1047
|
+
version: 1,
|
|
1048
|
+
blob: this.encrypt(JSON.stringify(this.data))
|
|
1049
|
+
};
|
|
1050
|
+
mkdirSync4(this.baseDir, { recursive: true, mode: 448 });
|
|
1051
|
+
writeFileSync4(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
|
|
828
1055
|
// src/lib/gatekeeper.ts
|
|
829
1056
|
import { verify } from "@hap/core";
|
|
830
1057
|
var MCPGatekeeper = class {
|
|
@@ -898,6 +1125,7 @@ var SharedState = class {
|
|
|
898
1125
|
gateStore;
|
|
899
1126
|
executionLog;
|
|
900
1127
|
denialLog;
|
|
1128
|
+
receiptArchive;
|
|
901
1129
|
gatekeeper;
|
|
902
1130
|
constructor(spUrl2, gateStorePath) {
|
|
903
1131
|
this.spClient = new SPClient(spUrl2);
|
|
@@ -905,8 +1133,50 @@ var SharedState = class {
|
|
|
905
1133
|
this.gateStore = new GateStore(gateStorePath);
|
|
906
1134
|
this.executionLog = new ExecutionLog(gateStorePath);
|
|
907
1135
|
this.denialLog = new DenialLog(gateStorePath);
|
|
1136
|
+
this.receiptArchive = new ReceiptArchive(gateStorePath);
|
|
908
1137
|
this.gatekeeper = new MCPGatekeeper(this.cache, this.executionLog);
|
|
909
1138
|
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Archive a signed receipt into the local receipt archive — the subject's
|
|
1141
|
+
* own durable copy of the evidence (see receipt-archive.ts).
|
|
1142
|
+
*
|
|
1143
|
+
* Best-effort by contract: the AS has already issued the receipt and holds
|
|
1144
|
+
* the authoritative copy, so an archive failure logs loudly but MUST NOT
|
|
1145
|
+
* block the execution it documents.
|
|
1146
|
+
*/
|
|
1147
|
+
async archiveReceipt(receipt, opts) {
|
|
1148
|
+
try {
|
|
1149
|
+
let asPublicKey;
|
|
1150
|
+
try {
|
|
1151
|
+
asPublicKey = await this.cache.getPublicKey();
|
|
1152
|
+
} catch {
|
|
1153
|
+
}
|
|
1154
|
+
this.receiptArchive.record({
|
|
1155
|
+
receipt,
|
|
1156
|
+
authorizationId: opts.authorizationId,
|
|
1157
|
+
asUrl: this.spClient.url,
|
|
1158
|
+
asPublicKey,
|
|
1159
|
+
proposal: opts.proposal,
|
|
1160
|
+
boundContent: opts.boundContent,
|
|
1161
|
+
authorization: opts.profileId ? {
|
|
1162
|
+
profileId: opts.profileId,
|
|
1163
|
+
boundsHash: opts.boundsHash,
|
|
1164
|
+
contextHash: opts.contextHash ?? this.gateStore.get(opts.authorizationId)?.contextHash,
|
|
1165
|
+
bounds: opts.bounds,
|
|
1166
|
+
context: opts.context,
|
|
1167
|
+
// Fall back to the gate store — the review path's cached auth
|
|
1168
|
+
// carries no gate content.
|
|
1169
|
+
intent: opts.intent ?? this.gateStore.get(opts.authorizationId)?.gateContent?.intent,
|
|
1170
|
+
attestations: opts.attestations ?? []
|
|
1171
|
+
} : void 0
|
|
1172
|
+
});
|
|
1173
|
+
} catch (err) {
|
|
1174
|
+
console.error(
|
|
1175
|
+
"[Suveren MCP] Receipt archive write failed (execution proceeds \u2014 AS retains authoritative copy):",
|
|
1176
|
+
err
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
910
1180
|
setGateContent(path, authorizationId, profileId, content, opts) {
|
|
911
1181
|
this.gateStore.set(authorizationId, {
|
|
912
1182
|
authorizationId,
|
|
@@ -1012,17 +1282,17 @@ function formatConsumptionFull(entries) {
|
|
|
1012
1282
|
}
|
|
1013
1283
|
|
|
1014
1284
|
// src/lib/context-loader.ts
|
|
1015
|
-
import { readFileSync as
|
|
1016
|
-
import { homedir as
|
|
1017
|
-
import { join as
|
|
1018
|
-
var
|
|
1285
|
+
import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
|
|
1286
|
+
import { homedir as homedir5 } from "os";
|
|
1287
|
+
import { join as join5 } from "path";
|
|
1288
|
+
var DEFAULT_DIR5 = process.env.SUVEREN_DATA_DIR ?? join5(homedir5(), ".suveren");
|
|
1019
1289
|
var BRIEF_MAX_CHARS = 16 * 1024;
|
|
1020
1290
|
function readContextFile(dataDir) {
|
|
1021
|
-
const dir = dataDir ??
|
|
1022
|
-
const filePath =
|
|
1023
|
-
if (!
|
|
1291
|
+
const dir = dataDir ?? DEFAULT_DIR5;
|
|
1292
|
+
const filePath = join5(dir, "context.md");
|
|
1293
|
+
if (!existsSync5(filePath)) return null;
|
|
1024
1294
|
try {
|
|
1025
|
-
const content =
|
|
1295
|
+
const content = readFileSync5(filePath, "utf-8").trim();
|
|
1026
1296
|
return content || null;
|
|
1027
1297
|
} catch {
|
|
1028
1298
|
return null;
|
|
@@ -1192,7 +1462,7 @@ function appendVerificationFooter(tool, args, receiptId, subject) {
|
|
|
1192
1462
|
// src/lib/content-binding.ts
|
|
1193
1463
|
import {
|
|
1194
1464
|
computeContentHash,
|
|
1195
|
-
|
|
1465
|
+
selectBoundFields,
|
|
1196
1466
|
isFieldBinding,
|
|
1197
1467
|
bindingAppliesTo,
|
|
1198
1468
|
getProfile as getProfile2
|
|
@@ -1212,14 +1482,18 @@ function computeContentBinding(profileId, tool, toolArgs, actionType) {
|
|
|
1212
1482
|
}
|
|
1213
1483
|
return void 0;
|
|
1214
1484
|
}
|
|
1485
|
+
const bound = selectBoundFields(binding, toolArgs);
|
|
1215
1486
|
return {
|
|
1216
|
-
contentHash:
|
|
1217
|
-
contentBinding: { version: binding.version, kind: binding.kind, fields: binding.fields }
|
|
1487
|
+
contentHash: computeContentHash(binding, bound),
|
|
1488
|
+
contentBinding: { version: binding.version, kind: binding.kind, fields: binding.fields },
|
|
1489
|
+
boundContent: bound
|
|
1218
1490
|
};
|
|
1219
1491
|
}
|
|
1220
1492
|
let contentHash;
|
|
1493
|
+
let boundContent;
|
|
1221
1494
|
if (binding.kind === "jcs") {
|
|
1222
1495
|
contentHash = computeContentHash(binding, toolArgs);
|
|
1496
|
+
boundContent = toolArgs;
|
|
1223
1497
|
} else {
|
|
1224
1498
|
const declared = tool?.gating?.contentField;
|
|
1225
1499
|
const field = declared ?? (tool ? detectContentField(tool) : null);
|
|
@@ -1227,10 +1501,12 @@ function computeContentBinding(profileId, tool, toolArgs, actionType) {
|
|
|
1227
1501
|
const raw = typeof toolArgs[field] === "string" ? toolArgs[field] : "";
|
|
1228
1502
|
if (raw === "") return void 0;
|
|
1229
1503
|
contentHash = computeContentHash(binding, raw);
|
|
1504
|
+
boundContent = raw;
|
|
1230
1505
|
}
|
|
1231
1506
|
return {
|
|
1232
1507
|
contentHash,
|
|
1233
|
-
contentBinding: { version: binding.version, kind: binding.kind }
|
|
1508
|
+
contentBinding: { version: binding.version, kind: binding.kind },
|
|
1509
|
+
boundContent
|
|
1234
1510
|
};
|
|
1235
1511
|
}
|
|
1236
1512
|
function attachReceiptId(tool, args, receiptId) {
|
|
@@ -1849,6 +2125,29 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
1849
2125
|
}
|
|
1850
2126
|
}
|
|
1851
2127
|
}
|
|
2128
|
+
const declaredActionType = typeof execution.action_type === "string" && execution.action_type.length > 0 ? execution.action_type : void 0;
|
|
2129
|
+
if (!declaredActionType) {
|
|
2130
|
+
return {
|
|
2131
|
+
content: [{
|
|
2132
|
+
type: "text",
|
|
2133
|
+
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.`
|
|
2134
|
+
}],
|
|
2135
|
+
isError: true
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
{
|
|
2139
|
+
const profileDef = getProfile3(profile);
|
|
2140
|
+
const registry = profileDef?.boundsSchema?.actionTypes;
|
|
2141
|
+
if (Array.isArray(registry) && registry.length > 0 && !registry.includes(declaredActionType)) {
|
|
2142
|
+
return {
|
|
2143
|
+
content: [{
|
|
2144
|
+
type: "text",
|
|
2145
|
+
text: `Blocked by Gatekeeper: action_type "${declaredActionType}" is not in profile ${profile}'s actionTypes registry (${registry.join(", ")}). Fix the integration manifest.`
|
|
2146
|
+
}],
|
|
2147
|
+
isError: true
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
1852
2151
|
const auths = state2.getEnrichedAuthorizations();
|
|
1853
2152
|
const matchingAuths = auths.filter(
|
|
1854
2153
|
(a) => a.complete && profileMatches(a.profileId, profile)
|
|
@@ -1946,12 +2245,7 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
1946
2245
|
}
|
|
1947
2246
|
let receiptId;
|
|
1948
2247
|
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
|
-
}
|
|
2248
|
+
const actionType = declaredActionType;
|
|
1955
2249
|
const binding = computeContentBinding(auth.profileId, tool, args, actionType);
|
|
1956
2250
|
const { receipt } = await state2.spClient.postReceipt({
|
|
1957
2251
|
authorizationId: authzId,
|
|
@@ -1966,6 +2260,19 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
1966
2260
|
...binding ?? {}
|
|
1967
2261
|
});
|
|
1968
2262
|
receiptId = typeof receipt?.id === "string" ? receipt.id : void 0;
|
|
2263
|
+
await state2.archiveReceipt(receipt, {
|
|
2264
|
+
authorizationId: authzId,
|
|
2265
|
+
profileId: auth.profileId,
|
|
2266
|
+
boundsHash: auth.boundsHash,
|
|
2267
|
+
contextHash: auth.contextHash,
|
|
2268
|
+
bounds: auth.bounds ?? auth.frame,
|
|
2269
|
+
context: auth.context,
|
|
2270
|
+
intent: auth.gateContent?.intent,
|
|
2271
|
+
attestations: auth.attestations,
|
|
2272
|
+
// Automatic path has no proposal, so without this the archive
|
|
2273
|
+
// would hold a hash of content it cannot reproduce.
|
|
2274
|
+
boundContent: binding?.boundContent
|
|
2275
|
+
});
|
|
1969
2276
|
} catch (err) {
|
|
1970
2277
|
if (err instanceof ContentBindingError) {
|
|
1971
2278
|
return {
|
|
@@ -2403,6 +2710,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
2403
2710
|
`[Suveren MCP] Warning: proposal ${proposal.id} has no action_type in executionContext. Bounds check may be skipped. Fix the integration manifest for ${proposal.tool}.`
|
|
2404
2711
|
);
|
|
2405
2712
|
}
|
|
2713
|
+
const cachedAuth = state2.cache.getAllAuthorizations().find((a) => a.authorizationId === proposal.authorizationId);
|
|
2406
2714
|
let receiptId;
|
|
2407
2715
|
try {
|
|
2408
2716
|
const binding = computeContentBinding(
|
|
@@ -2413,6 +2721,9 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
2413
2721
|
);
|
|
2414
2722
|
const { receipt } = await state2.spClient.postReceipt({
|
|
2415
2723
|
authorizationId: proposal.authorizationId,
|
|
2724
|
+
// Optional cross-check — the AS fails closed on a mismatch. Parity
|
|
2725
|
+
// with the automatic path (tool-proxy.ts).
|
|
2726
|
+
boundsHash: cachedAuth?.boundsHash,
|
|
2416
2727
|
profileId: proposal.profileId,
|
|
2417
2728
|
action: proposal.tool,
|
|
2418
2729
|
actionType: proposalActionType,
|
|
@@ -2420,9 +2731,29 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
2420
2731
|
amount: typeof proposal.executionContext.amount === "number" ? proposal.executionContext.amount : void 0,
|
|
2421
2732
|
proposalId: proposal.id,
|
|
2422
2733
|
toolArgs: proposal.toolArgs,
|
|
2734
|
+
// Deliberately NO idempotencyKey: the spec has review-mode commits carry
|
|
2735
|
+
// a proposalId INSTEAD of a key — the proposal's committed→executed
|
|
2736
|
+
// transition is their replay protection, and the AS replays the ORIGINAL
|
|
2737
|
+
// receipt to the same caller if a retry arrives after it committed.
|
|
2738
|
+
// SPClient therefore treats proposalId as retry-safe on its own; a
|
|
2739
|
+
// PROPOSAL_ALREADY_EXECUTED answer is still a definitive rejection
|
|
2740
|
+
// (never retried) and means a *different* caller consumed the proposal.
|
|
2423
2741
|
...binding ?? {}
|
|
2424
2742
|
});
|
|
2425
2743
|
receiptId = typeof receipt?.id === "string" ? receipt.id : void 0;
|
|
2744
|
+
await state2.archiveReceipt(receipt, {
|
|
2745
|
+
authorizationId: proposal.authorizationId,
|
|
2746
|
+
profileId: proposal.profileId,
|
|
2747
|
+
boundsHash: cachedAuth?.boundsHash,
|
|
2748
|
+
contextHash: cachedAuth?.contextHash,
|
|
2749
|
+
bounds: cachedAuth?.bounds ?? cachedAuth?.frame,
|
|
2750
|
+
context: cachedAuth?.context,
|
|
2751
|
+
attestations: cachedAuth?.attestations,
|
|
2752
|
+
// What the human actually approved, kept next to the receipt that cites
|
|
2753
|
+
// it — otherwise `proposalId` is a pointer to a server we may not have.
|
|
2754
|
+
proposal,
|
|
2755
|
+
boundContent: binding?.boundContent
|
|
2756
|
+
});
|
|
2426
2757
|
} catch (err) {
|
|
2427
2758
|
if (err instanceof ContentBindingError2) {
|
|
2428
2759
|
return {
|
|
@@ -2451,7 +2782,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
2451
2782
|
let outgoingArgs = proposal.toolArgs;
|
|
2452
2783
|
if (discovered && receiptId) {
|
|
2453
2784
|
if (shouldAttachFooter()) {
|
|
2454
|
-
outgoingArgs = appendVerificationFooter(discovered, outgoingArgs, receiptId);
|
|
2785
|
+
outgoingArgs = appendVerificationFooter(discovered, outgoingArgs, receiptId, cachedAuth?.subjects?.[0]);
|
|
2455
2786
|
}
|
|
2456
2787
|
outgoingArgs = attachReceiptId(discovered, outgoingArgs, receiptId);
|
|
2457
2788
|
outgoingArgs = encodeOutgoingArgs(discovered, outgoingArgs);
|
|
@@ -2672,7 +3003,12 @@ function createMcpServer(state2, integrationManager2) {
|
|
|
2672
3003
|
}
|
|
2673
3004
|
}
|
|
2674
3005
|
for (const tool of allTools) {
|
|
2675
|
-
|
|
3006
|
+
const existing = proxiedTools.get(tool.namespacedName);
|
|
3007
|
+
if (existing) {
|
|
3008
|
+
if (existing.tool === tool) continue;
|
|
3009
|
+
existing.registered.remove();
|
|
3010
|
+
proxiedTools.delete(tool.namespacedName);
|
|
3011
|
+
}
|
|
2676
3012
|
const handler = createGatedToolHandler(tool, integrationManager2, state2);
|
|
2677
3013
|
const zodShape = jsonSchemaToZodShape(tool.inputSchema);
|
|
2678
3014
|
const description = buildProxiedToolDescription(tool, state2);
|
|
@@ -2740,16 +3076,16 @@ function verifyGateContentHashes(content, auth) {
|
|
|
2740
3076
|
}
|
|
2741
3077
|
|
|
2742
3078
|
// src/lib/integration-registry.ts
|
|
2743
|
-
import { mkdirSync as
|
|
2744
|
-
import { homedir as
|
|
2745
|
-
import { dirname as
|
|
2746
|
-
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
3079
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync6 } from "fs";
|
|
3080
|
+
import { homedir as homedir6 } from "os";
|
|
3081
|
+
import { dirname as dirname5, join as join6 } from "path";
|
|
3082
|
+
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join6(homedir6(), ".suveren");
|
|
2747
3083
|
var IntegrationRegistry = class {
|
|
2748
3084
|
integrations = /* @__PURE__ */ new Map();
|
|
2749
3085
|
filePath;
|
|
2750
3086
|
constructor(dataDir) {
|
|
2751
3087
|
const dir = dataDir ?? DEFAULT_DATA_DIR;
|
|
2752
|
-
this.filePath =
|
|
3088
|
+
this.filePath = join6(dir, "integrations.json");
|
|
2753
3089
|
this.load();
|
|
2754
3090
|
}
|
|
2755
3091
|
// ─── CRUD ───────────────────────────────────────────────────────────────
|
|
@@ -2780,12 +3116,12 @@ var IntegrationRegistry = class {
|
|
|
2780
3116
|
}
|
|
2781
3117
|
// ─── Load / Save ────────────────────────────────────────────────────────
|
|
2782
3118
|
load() {
|
|
2783
|
-
if (!
|
|
2784
|
-
|
|
3119
|
+
if (!existsSync6(this.filePath)) {
|
|
3120
|
+
mkdirSync5(dirname5(this.filePath), { recursive: true });
|
|
2785
3121
|
return;
|
|
2786
3122
|
}
|
|
2787
3123
|
try {
|
|
2788
|
-
const raw =
|
|
3124
|
+
const raw = readFileSync6(this.filePath, "utf-8");
|
|
2789
3125
|
const data = JSON.parse(raw);
|
|
2790
3126
|
const migrated = data.integrations.map((i) => {
|
|
2791
3127
|
if ("toolGating" in i && !("profile" in i)) {
|
|
@@ -2814,33 +3150,33 @@ var IntegrationRegistry = class {
|
|
|
2814
3150
|
version: 1,
|
|
2815
3151
|
integrations: Array.from(this.integrations.values())
|
|
2816
3152
|
};
|
|
2817
|
-
|
|
2818
|
-
|
|
3153
|
+
mkdirSync5(dirname5(this.filePath), { recursive: true });
|
|
3154
|
+
writeFileSync5(this.filePath, JSON.stringify(data, null, 2), { encoding: "utf-8" });
|
|
2819
3155
|
}
|
|
2820
3156
|
};
|
|
2821
3157
|
|
|
2822
3158
|
// src/lib/integration-manager.ts
|
|
2823
|
-
import { homedir as
|
|
2824
|
-
import { join as
|
|
2825
|
-
import { existsSync as
|
|
3159
|
+
import { homedir as homedir7 } from "os";
|
|
3160
|
+
import { join as join7, delimiter } from "path";
|
|
3161
|
+
import { existsSync as existsSync7, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync7, rmSync } from "fs";
|
|
2826
3162
|
import { execFile } from "child_process";
|
|
2827
3163
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2828
3164
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
2829
3165
|
import { getProfile as getProfile5 } from "@hap/core";
|
|
2830
|
-
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ??
|
|
2831
|
-
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ??
|
|
2832
|
-
var INTEGRATIONS_BIN =
|
|
3166
|
+
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join7(homedir7(), ".suveren");
|
|
3167
|
+
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ?? join7(DEFAULT_DATA_DIR2, "integrations");
|
|
3168
|
+
var INTEGRATIONS_BIN = join7(INTEGRATIONS_DIR, "node_modules", ".bin");
|
|
2833
3169
|
function buildPath() {
|
|
2834
3170
|
const base = process.env.PATH ?? "";
|
|
2835
3171
|
return [INTEGRATIONS_BIN, base].join(delimiter);
|
|
2836
3172
|
}
|
|
2837
3173
|
function ensureIntegrationsDir() {
|
|
2838
|
-
if (!
|
|
2839
|
-
|
|
3174
|
+
if (!existsSync7(INTEGRATIONS_DIR)) {
|
|
3175
|
+
mkdirSync6(INTEGRATIONS_DIR, { recursive: true });
|
|
2840
3176
|
}
|
|
2841
|
-
const pkgPath =
|
|
2842
|
-
if (!
|
|
2843
|
-
|
|
3177
|
+
const pkgPath = join7(INTEGRATIONS_DIR, "package.json");
|
|
3178
|
+
if (!existsSync7(pkgPath)) {
|
|
3179
|
+
writeFileSync6(pkgPath, JSON.stringify({ name: "suveren-integrations", version: "1.0.0", private: true }, null, 2));
|
|
2844
3180
|
}
|
|
2845
3181
|
}
|
|
2846
3182
|
function readAgeOf(config) {
|
|
@@ -2869,6 +3205,24 @@ var IntegrationManager = class {
|
|
|
2869
3205
|
}
|
|
2870
3206
|
running = /* @__PURE__ */ new Map();
|
|
2871
3207
|
onToolsChanged = null;
|
|
3208
|
+
/**
|
|
3209
|
+
* Per-integration operation queue. Start/stop for one id are serialized
|
|
3210
|
+
* through here; without it, a second start issued while the first is still
|
|
3211
|
+
* installing/handshaking sees `running.has(id) === false`, skips the stop,
|
|
3212
|
+
* and both children come up — last `running.set` wins, the loser's process
|
|
3213
|
+
* leaks, and the surviving GATING is whichever start happened to finish
|
|
3214
|
+
* last. That last-writer-wins gating is a fail-open: a permissive manifest
|
|
3215
|
+
* config can silently replace a stricter explicit one (observed as the
|
|
3216
|
+
* read-gate value-mismatch flake).
|
|
3217
|
+
*/
|
|
3218
|
+
opQueues = /* @__PURE__ */ new Map();
|
|
3219
|
+
/** Run `task` after every previously queued operation for `id` has settled. */
|
|
3220
|
+
runExclusive(id, task) {
|
|
3221
|
+
const prev = this.opQueues.get(id) ?? Promise.resolve();
|
|
3222
|
+
const run = prev.then(task, task);
|
|
3223
|
+
this.opQueues.set(id, run.then(() => void 0, () => void 0));
|
|
3224
|
+
return run;
|
|
3225
|
+
}
|
|
2872
3226
|
/**
|
|
2873
3227
|
* Register a callback invoked when the tool set changes
|
|
2874
3228
|
* (integration started, stopped, or crashed).
|
|
@@ -2888,19 +3242,19 @@ var IntegrationManager = class {
|
|
|
2888
3242
|
* exist, and treat anything else as not-installed (and reinstallable).
|
|
2889
3243
|
*/
|
|
2890
3244
|
isUsableInstall(npmPackage) {
|
|
2891
|
-
const pkgDir =
|
|
2892
|
-
const pkgJson =
|
|
2893
|
-
if (!
|
|
3245
|
+
const pkgDir = join7(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
3246
|
+
const pkgJson = join7(pkgDir, "package.json");
|
|
3247
|
+
if (!existsSync7(pkgDir) || !existsSync7(pkgJson)) return false;
|
|
2894
3248
|
try {
|
|
2895
|
-
const pkg = JSON.parse(
|
|
3249
|
+
const pkg = JSON.parse(readFileSync7(pkgJson, "utf8"));
|
|
2896
3250
|
const defaultBinName = (pkg.name ?? npmPackage).split("/").pop() ?? "";
|
|
2897
3251
|
const binMap = typeof pkg.bin === "string" ? { [defaultBinName]: pkg.bin } : pkg.bin ?? {};
|
|
2898
3252
|
const binEntries = Object.entries(binMap);
|
|
2899
3253
|
for (const [binName, target] of binEntries) {
|
|
2900
|
-
if (!
|
|
3254
|
+
if (!existsSync7(join7(pkgDir, target))) return false;
|
|
2901
3255
|
if (!this.binShimExists(binName)) return false;
|
|
2902
3256
|
}
|
|
2903
|
-
if (binEntries.length === 0 && pkg.main && !
|
|
3257
|
+
if (binEntries.length === 0 && pkg.main && !existsSync7(join7(pkgDir, pkg.main))) return false;
|
|
2904
3258
|
return true;
|
|
2905
3259
|
} catch {
|
|
2906
3260
|
return false;
|
|
@@ -2914,7 +3268,7 @@ var IntegrationManager = class {
|
|
|
2914
3268
|
binShimExists(binName) {
|
|
2915
3269
|
if (!binName) return false;
|
|
2916
3270
|
const candidates = process.platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.CMD`, `${binName}.ps1`] : [binName];
|
|
2917
|
-
return candidates.some((c) =>
|
|
3271
|
+
return candidates.some((c) => existsSync7(join7(INTEGRATIONS_BIN, c)));
|
|
2918
3272
|
}
|
|
2919
3273
|
/**
|
|
2920
3274
|
* Install an npm package into the managed integrations directory if not
|
|
@@ -2931,8 +3285,8 @@ var IntegrationManager = class {
|
|
|
2931
3285
|
async ensureInstalled(npmPackage) {
|
|
2932
3286
|
ensureIntegrationsDir();
|
|
2933
3287
|
if (this.isUsableInstall(npmPackage)) return;
|
|
2934
|
-
const pkgDir =
|
|
2935
|
-
if (
|
|
3288
|
+
const pkgDir = join7(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
3289
|
+
if (existsSync7(pkgDir)) {
|
|
2936
3290
|
console.error(`[IntegrationManager] Removing unusable install of ${npmPackage}`);
|
|
2937
3291
|
try {
|
|
2938
3292
|
rmSync(pkgDir, { recursive: true, force: true });
|
|
@@ -3018,10 +3372,19 @@ var IntegrationManager = class {
|
|
|
3018
3372
|
* Start a downstream MCP server integration.
|
|
3019
3373
|
* Installs npm package on-demand if needed, resolves envKeys,
|
|
3020
3374
|
* spawns the process, connects as MCP client, and discovers tools.
|
|
3375
|
+
*
|
|
3376
|
+
* Serialized per id: a start issued while another start/stop for the same
|
|
3377
|
+
* id is in flight waits for it, then applies its own semantics (restart, or
|
|
3378
|
+
* keep-if-running with `skipIfRunning`). See `opQueues`.
|
|
3021
3379
|
*/
|
|
3022
|
-
async startIntegration(config) {
|
|
3023
|
-
|
|
3024
|
-
|
|
3380
|
+
async startIntegration(config, opts = {}) {
|
|
3381
|
+
return this.runExclusive(config.id, () => this.startIntegrationLocked(config, opts));
|
|
3382
|
+
}
|
|
3383
|
+
async startIntegrationLocked(config, opts) {
|
|
3384
|
+
const existing = this.running.get(config.id);
|
|
3385
|
+
if (existing) {
|
|
3386
|
+
if (opts.skipIfRunning) return existing.tools;
|
|
3387
|
+
await this.stopIntegrationLocked(config.id);
|
|
3025
3388
|
}
|
|
3026
3389
|
if (config.npmPackage) {
|
|
3027
3390
|
await this.ensureInstalled(config.npmPackage);
|
|
@@ -3076,6 +3439,7 @@ var IntegrationManager = class {
|
|
|
3076
3439
|
};
|
|
3077
3440
|
this.running.set(config.id, entry);
|
|
3078
3441
|
transport.onclose = () => {
|
|
3442
|
+
if (this.running.get(config.id)?.transport !== transport) return;
|
|
3079
3443
|
console.error(`[IntegrationManager] Transport closed for ${config.id}`);
|
|
3080
3444
|
this.handleCrash(config.id);
|
|
3081
3445
|
};
|
|
@@ -3084,8 +3448,15 @@ var IntegrationManager = class {
|
|
|
3084
3448
|
}
|
|
3085
3449
|
/**
|
|
3086
3450
|
* Stop a running integration, closing its transport and removing its tools.
|
|
3451
|
+
*
|
|
3452
|
+
* Serialized per id: a stop issued while a start is in flight waits and
|
|
3453
|
+
* then stops the just-started instance, instead of silently doing nothing
|
|
3454
|
+
* because the entry wasn't in `running` yet.
|
|
3087
3455
|
*/
|
|
3088
3456
|
async stopIntegration(id) {
|
|
3457
|
+
return this.runExclusive(id, () => this.stopIntegrationLocked(id));
|
|
3458
|
+
}
|
|
3459
|
+
async stopIntegrationLocked(id) {
|
|
3089
3460
|
const entry = this.running.get(id);
|
|
3090
3461
|
if (!entry) return;
|
|
3091
3462
|
entry.transport.onclose = void 0;
|
|
@@ -3308,7 +3679,7 @@ var IntegrationManager = class {
|
|
|
3308
3679
|
console.error(`[IntegrationManager] ${id} crashed, respawning in ${delay}ms (attempt ${attempts + 1}/${MAX_RESPAWN_ATTEMPTS})`);
|
|
3309
3680
|
setTimeout(async () => {
|
|
3310
3681
|
try {
|
|
3311
|
-
await this.startIntegration(entry.config);
|
|
3682
|
+
await this.startIntegration(entry.config, { skipIfRunning: true });
|
|
3312
3683
|
const newEntry = this.running.get(id);
|
|
3313
3684
|
if (newEntry) {
|
|
3314
3685
|
newEntry.respawnAttempts = attempts + 1;
|
|
@@ -3330,28 +3701,28 @@ var IntegrationManager = class {
|
|
|
3330
3701
|
};
|
|
3331
3702
|
|
|
3332
3703
|
// src/lib/profile-loader.ts
|
|
3333
|
-
import { readFileSync as
|
|
3334
|
-
import { join as
|
|
3704
|
+
import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
|
|
3705
|
+
import { join as join8, resolve } from "path";
|
|
3335
3706
|
import { registerProfile } from "@hap/core";
|
|
3336
3707
|
function loadProfiles(profilesDir) {
|
|
3337
|
-
const dir = resolve(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ??
|
|
3338
|
-
const indexPath =
|
|
3339
|
-
if (!
|
|
3708
|
+
const dir = resolve(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ?? join8(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "..", "hap-profiles"));
|
|
3709
|
+
const indexPath = join8(dir, "index.json");
|
|
3710
|
+
if (!existsSync8(indexPath)) {
|
|
3340
3711
|
warnNoProfiles(`No index.json at ${indexPath}`);
|
|
3341
3712
|
return 0;
|
|
3342
3713
|
}
|
|
3343
3714
|
let index;
|
|
3344
3715
|
try {
|
|
3345
|
-
index = JSON.parse(
|
|
3716
|
+
index = JSON.parse(readFileSync8(indexPath, "utf-8"));
|
|
3346
3717
|
} catch (err) {
|
|
3347
3718
|
console.error(`[ProfileLoader] Failed to parse ${indexPath}:`, err);
|
|
3348
3719
|
return 0;
|
|
3349
3720
|
}
|
|
3350
3721
|
let loaded = 0;
|
|
3351
3722
|
for (const [profileId, relativePath] of Object.entries(index.profiles)) {
|
|
3352
|
-
const profilePath =
|
|
3723
|
+
const profilePath = join8(dir, relativePath);
|
|
3353
3724
|
try {
|
|
3354
|
-
const profile = JSON.parse(
|
|
3725
|
+
const profile = JSON.parse(readFileSync8(profilePath, "utf-8"));
|
|
3355
3726
|
registerProfile(profileId, profile);
|
|
3356
3727
|
const shortName = profileId.split("/").pop()?.replace(/@.*$/, "");
|
|
3357
3728
|
if (shortName && shortName !== profileId) {
|
|
@@ -3384,30 +3755,30 @@ function warnNoProfiles(reason) {
|
|
|
3384
3755
|
}
|
|
3385
3756
|
|
|
3386
3757
|
// src/lib/manifest-loader.ts
|
|
3387
|
-
import { readFileSync as
|
|
3388
|
-
import { join as
|
|
3758
|
+
import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
|
|
3759
|
+
import { join as join9, resolve as resolve2 } from "path";
|
|
3389
3760
|
var manifests = /* @__PURE__ */ new Map();
|
|
3390
3761
|
function loadManifests(integrationsDir) {
|
|
3391
3762
|
const dir = resolve2(
|
|
3392
|
-
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ??
|
|
3763
|
+
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join9(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
|
|
3393
3764
|
);
|
|
3394
|
-
const indexPath =
|
|
3395
|
-
if (!
|
|
3765
|
+
const indexPath = join9(dir, "index.json");
|
|
3766
|
+
if (!existsSync9(indexPath)) {
|
|
3396
3767
|
console.error(`[ManifestLoader] No index.json found at ${indexPath}, skipping manifest loading`);
|
|
3397
3768
|
return 0;
|
|
3398
3769
|
}
|
|
3399
3770
|
let index;
|
|
3400
3771
|
try {
|
|
3401
|
-
index = JSON.parse(
|
|
3772
|
+
index = JSON.parse(readFileSync9(indexPath, "utf-8"));
|
|
3402
3773
|
} catch (err) {
|
|
3403
3774
|
console.error(`[ManifestLoader] Failed to parse ${indexPath}:`, err);
|
|
3404
3775
|
return 0;
|
|
3405
3776
|
}
|
|
3406
3777
|
let loaded = 0;
|
|
3407
3778
|
for (const [id, relativePath] of Object.entries(index.integrations)) {
|
|
3408
|
-
const manifestPath =
|
|
3779
|
+
const manifestPath = join9(dir, relativePath);
|
|
3409
3780
|
try {
|
|
3410
|
-
const manifest = JSON.parse(
|
|
3781
|
+
const manifest = JSON.parse(readFileSync9(manifestPath, "utf-8"));
|
|
3411
3782
|
manifests.set(id, manifest);
|
|
3412
3783
|
loaded++;
|
|
3413
3784
|
} catch (err) {
|
|
@@ -3425,6 +3796,7 @@ function getAllManifests() {
|
|
|
3425
3796
|
}
|
|
3426
3797
|
|
|
3427
3798
|
// bin/http.ts
|
|
3799
|
+
import { decodeAttestationBlob as decodeAttestationBlob3 } from "@hap/core";
|
|
3428
3800
|
var spUrl = process.env.SUVEREN_AS_URL ?? "https://www.suveren.ai";
|
|
3429
3801
|
var port = parseInt(process.env.SUVEREN_MCP_PORT ?? "3430", 10);
|
|
3430
3802
|
var state = new SharedState(spUrl);
|
|
@@ -3488,7 +3860,8 @@ app.post("/internal/configure", internalOnly, (req, res) => {
|
|
|
3488
3860
|
const key = Buffer.from(vaultKeyHex, "hex");
|
|
3489
3861
|
state.gateStore.setVaultKey(key);
|
|
3490
3862
|
state.denialLog.setVaultKey(key);
|
|
3491
|
-
|
|
3863
|
+
state.receiptArchive.setVaultKey(key);
|
|
3864
|
+
console.error("[Suveren MCP] Vault key configured \u2014 gate store + denial log + receipt archive encryption active");
|
|
3492
3865
|
}
|
|
3493
3866
|
if (apiKey) {
|
|
3494
3867
|
state.spClient.setApiKey(apiKey);
|
|
@@ -3785,6 +4158,27 @@ app.get("/internal/authorizations", internalOnly, (_req, res) => {
|
|
|
3785
4158
|
app.get("/internal/manifests", internalOnly, (_req, res) => {
|
|
3786
4159
|
res.json({ manifests: getAllManifests() });
|
|
3787
4160
|
});
|
|
4161
|
+
app.get("/internal/evidence", internalOnly, (_req, res) => {
|
|
4162
|
+
if (state.receiptArchive.isLocked()) {
|
|
4163
|
+
res.status(503).json({ error: "Vault locked \u2014 the evidence archive is encrypted until sign-in." });
|
|
4164
|
+
return;
|
|
4165
|
+
}
|
|
4166
|
+
const authorizations = state.receiptArchive.getAuthorizations().map((a) => ({
|
|
4167
|
+
...a,
|
|
4168
|
+
attestations: a.attestations.map((att) => {
|
|
4169
|
+
try {
|
|
4170
|
+
return { ...att, decoded: decodeAttestationBlob3(att.blob) };
|
|
4171
|
+
} catch {
|
|
4172
|
+
return att;
|
|
4173
|
+
}
|
|
4174
|
+
})
|
|
4175
|
+
}));
|
|
4176
|
+
res.json({
|
|
4177
|
+
receipts: state.receiptArchive.getReceipts(),
|
|
4178
|
+
authorizations,
|
|
4179
|
+
gates: state.gateStore.getAll()
|
|
4180
|
+
});
|
|
4181
|
+
});
|
|
3788
4182
|
app.get("/internal/brief", internalOnly, (_req, res) => {
|
|
3789
4183
|
try {
|
|
3790
4184
|
const enriched = state.getEnrichedAuthorizations();
|
|
@@ -3833,7 +4227,7 @@ async function startOneIntegration(config) {
|
|
|
3833
4227
|
const manifest = getManifest(config.id);
|
|
3834
4228
|
const effectiveConfig = manifest ? { ...config, toolGating: manifest.toolGating, npmPackage: manifest.npmPackage ?? config.npmPackage } : config;
|
|
3835
4229
|
try {
|
|
3836
|
-
await integrationManager.startIntegration(effectiveConfig);
|
|
4230
|
+
await integrationManager.startIntegration(effectiveConfig, { skipIfRunning: true });
|
|
3837
4231
|
} catch (err) {
|
|
3838
4232
|
console.error(`[Suveren MCP] Failed to start integration ${config.id}:`, err);
|
|
3839
4233
|
}
|