@suveren/gateway 0.9.0 → 0.10.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/README.md +33 -1
- package/bin/suveren-gateway.js +203 -5
- package/content/integrations/erp.json +184 -0
- package/content/integrations/index.json +2 -1
- package/dist/control-plane/index.mjs +653 -179
- package/dist/mcp-server/http.mjs +466 -259
- package/dist/ui/assets/{index-CIiOCgjJ.js → index-B1qNl7zo.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/lib/as-pairing.mjs +77 -0
- package/lib/config.mjs +48 -0
- package/node_modules/undici/LICENSE +21 -0
- package/node_modules/undici/README.md +741 -0
- package/node_modules/undici/docs/docs/api/Agent.md +84 -0
- package/node_modules/undici/docs/docs/api/BalancedPool.md +99 -0
- package/node_modules/undici/docs/docs/api/CacheStorage.md +30 -0
- package/node_modules/undici/docs/docs/api/CacheStore.md +164 -0
- package/node_modules/undici/docs/docs/api/Client.md +288 -0
- package/node_modules/undici/docs/docs/api/ClientStats.md +27 -0
- package/node_modules/undici/docs/docs/api/Connector.md +115 -0
- package/node_modules/undici/docs/docs/api/ContentType.md +57 -0
- package/node_modules/undici/docs/docs/api/Cookies.md +128 -0
- package/node_modules/undici/docs/docs/api/Debug.md +62 -0
- package/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +317 -0
- package/node_modules/undici/docs/docs/api/Dispatcher.md +1393 -0
- package/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +159 -0
- package/node_modules/undici/docs/docs/api/Errors.md +49 -0
- package/node_modules/undici/docs/docs/api/EventSource.md +45 -0
- package/node_modules/undici/docs/docs/api/Fetch.md +60 -0
- package/node_modules/undici/docs/docs/api/GlobalInstallation.md +139 -0
- package/node_modules/undici/docs/docs/api/H2CClient.md +263 -0
- package/node_modules/undici/docs/docs/api/MockAgent.md +603 -0
- package/node_modules/undici/docs/docs/api/MockCallHistory.md +197 -0
- package/node_modules/undici/docs/docs/api/MockCallHistoryLog.md +43 -0
- package/node_modules/undici/docs/docs/api/MockClient.md +81 -0
- package/node_modules/undici/docs/docs/api/MockErrors.md +12 -0
- package/node_modules/undici/docs/docs/api/MockPool.md +555 -0
- package/node_modules/undici/docs/docs/api/Pool.md +84 -0
- package/node_modules/undici/docs/docs/api/PoolStats.md +35 -0
- package/node_modules/undici/docs/docs/api/ProxyAgent.md +229 -0
- package/node_modules/undici/docs/docs/api/RedirectHandler.md +93 -0
- package/node_modules/undici/docs/docs/api/RetryAgent.md +50 -0
- package/node_modules/undici/docs/docs/api/RetryHandler.md +118 -0
- package/node_modules/undici/docs/docs/api/RoundRobinPool.md +145 -0
- package/node_modules/undici/docs/docs/api/SnapshotAgent.md +616 -0
- package/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +275 -0
- package/node_modules/undici/docs/docs/api/Util.md +25 -0
- package/node_modules/undici/docs/docs/api/WebSocket.md +141 -0
- package/node_modules/undici/docs/docs/api/api-lifecycle.md +91 -0
- package/node_modules/undici/docs/docs/best-practices/client-certificate.md +64 -0
- package/node_modules/undici/docs/docs/best-practices/crawling.md +58 -0
- package/node_modules/undici/docs/docs/best-practices/mocking-request.md +190 -0
- package/node_modules/undici/docs/docs/best-practices/proxy.md +127 -0
- package/node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md +224 -0
- package/node_modules/undici/docs/docs/best-practices/writing-tests.md +63 -0
- package/node_modules/undici/index-fetch.js +65 -0
- package/node_modules/undici/index.d.ts +3 -0
- package/node_modules/undici/index.js +234 -0
- package/node_modules/undici/lib/api/abort-signal.js +59 -0
- package/node_modules/undici/lib/api/api-connect.js +110 -0
- package/node_modules/undici/lib/api/api-pipeline.js +252 -0
- package/node_modules/undici/lib/api/api-request.js +214 -0
- package/node_modules/undici/lib/api/api-stream.js +209 -0
- package/node_modules/undici/lib/api/api-upgrade.js +111 -0
- package/node_modules/undici/lib/api/index.js +7 -0
- package/node_modules/undici/lib/api/readable.js +580 -0
- package/node_modules/undici/lib/cache/memory-cache-store.js +279 -0
- package/node_modules/undici/lib/cache/sqlite-cache-store.js +467 -0
- package/node_modules/undici/lib/core/connect.js +153 -0
- package/node_modules/undici/lib/core/constants.js +143 -0
- package/node_modules/undici/lib/core/diagnostics.js +227 -0
- package/node_modules/undici/lib/core/errors.js +477 -0
- package/node_modules/undici/lib/core/request.js +485 -0
- package/node_modules/undici/lib/core/socks5-client.js +422 -0
- package/node_modules/undici/lib/core/socks5-utils.js +212 -0
- package/node_modules/undici/lib/core/symbols.js +75 -0
- package/node_modules/undici/lib/core/tree.js +160 -0
- package/node_modules/undici/lib/core/util.js +992 -0
- package/node_modules/undici/lib/dispatcher/agent.js +158 -0
- package/node_modules/undici/lib/dispatcher/balanced-pool.js +221 -0
- package/node_modules/undici/lib/dispatcher/client-h1.js +1746 -0
- package/node_modules/undici/lib/dispatcher/client-h2.js +1060 -0
- package/node_modules/undici/lib/dispatcher/client.js +668 -0
- package/node_modules/undici/lib/dispatcher/dispatcher-base.js +184 -0
- package/node_modules/undici/lib/dispatcher/dispatcher.js +48 -0
- package/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +146 -0
- package/node_modules/undici/lib/dispatcher/fixed-queue.js +135 -0
- package/node_modules/undici/lib/dispatcher/h2c-client.js +51 -0
- package/node_modules/undici/lib/dispatcher/pool-base.js +214 -0
- package/node_modules/undici/lib/dispatcher/pool.js +118 -0
- package/node_modules/undici/lib/dispatcher/proxy-agent.js +319 -0
- package/node_modules/undici/lib/dispatcher/retry-agent.js +35 -0
- package/node_modules/undici/lib/dispatcher/round-robin-pool.js +137 -0
- package/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +260 -0
- package/node_modules/undici/lib/encoding/index.js +33 -0
- package/node_modules/undici/lib/global.js +59 -0
- package/node_modules/undici/lib/handler/cache-handler.js +776 -0
- package/node_modules/undici/lib/handler/cache-revalidation-handler.js +124 -0
- package/node_modules/undici/lib/handler/decorator-handler.js +67 -0
- package/node_modules/undici/lib/handler/deduplication-handler.js +460 -0
- package/node_modules/undici/lib/handler/redirect-handler.js +238 -0
- package/node_modules/undici/lib/handler/retry-handler.js +504 -0
- package/node_modules/undici/lib/handler/unwrap-handler.js +106 -0
- package/node_modules/undici/lib/handler/wrap-handler.js +105 -0
- package/node_modules/undici/lib/interceptor/cache.js +621 -0
- package/node_modules/undici/lib/interceptor/decompress.js +551 -0
- package/node_modules/undici/lib/interceptor/deduplicate.js +117 -0
- package/node_modules/undici/lib/interceptor/dns.js +571 -0
- package/node_modules/undici/lib/interceptor/dump.js +99 -0
- package/node_modules/undici/lib/interceptor/redirect.js +21 -0
- package/node_modules/undici/lib/interceptor/response-error.js +95 -0
- package/node_modules/undici/lib/interceptor/retry.js +19 -0
- package/node_modules/undici/lib/llhttp/.gitkeep +0 -0
- package/node_modules/undici/lib/llhttp/constants.d.ts +195 -0
- package/node_modules/undici/lib/llhttp/constants.js +531 -0
- package/node_modules/undici/lib/llhttp/llhttp-wasm.js +15 -0
- package/node_modules/undici/lib/llhttp/llhttp_simd-wasm.js +15 -0
- package/node_modules/undici/lib/llhttp/utils.d.ts +2 -0
- package/node_modules/undici/lib/llhttp/utils.js +12 -0
- package/node_modules/undici/lib/mock/mock-agent.js +232 -0
- package/node_modules/undici/lib/mock/mock-call-history.js +248 -0
- package/node_modules/undici/lib/mock/mock-client.js +68 -0
- package/node_modules/undici/lib/mock/mock-errors.js +29 -0
- package/node_modules/undici/lib/mock/mock-interceptor.js +209 -0
- package/node_modules/undici/lib/mock/mock-pool.js +68 -0
- package/node_modules/undici/lib/mock/mock-symbols.js +32 -0
- package/node_modules/undici/lib/mock/mock-utils.js +486 -0
- package/node_modules/undici/lib/mock/pending-interceptors-formatter.js +43 -0
- package/node_modules/undici/lib/mock/snapshot-agent.js +353 -0
- package/node_modules/undici/lib/mock/snapshot-recorder.js +588 -0
- package/node_modules/undici/lib/mock/snapshot-utils.js +158 -0
- package/node_modules/undici/lib/util/cache.js +676 -0
- package/node_modules/undici/lib/util/date.js +670 -0
- package/node_modules/undici/lib/util/promise.js +28 -0
- package/node_modules/undici/lib/util/runtime-features.js +124 -0
- package/node_modules/undici/lib/util/stats.js +32 -0
- package/node_modules/undici/lib/util/timers.js +425 -0
- package/node_modules/undici/lib/web/cache/cache.js +864 -0
- package/node_modules/undici/lib/web/cache/cachestorage.js +152 -0
- package/node_modules/undici/lib/web/cache/util.js +45 -0
- package/node_modules/undici/lib/web/cookies/constants.js +12 -0
- package/node_modules/undici/lib/web/cookies/index.js +199 -0
- package/node_modules/undici/lib/web/cookies/parse.js +314 -0
- package/node_modules/undici/lib/web/cookies/util.js +352 -0
- package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +494 -0
- package/node_modules/undici/lib/web/eventsource/eventsource.js +501 -0
- package/node_modules/undici/lib/web/eventsource/util.js +29 -0
- package/node_modules/undici/lib/web/fetch/LICENSE +21 -0
- package/node_modules/undici/lib/web/fetch/body.js +509 -0
- package/node_modules/undici/lib/web/fetch/constants.js +131 -0
- package/node_modules/undici/lib/web/fetch/data-url.js +596 -0
- package/node_modules/undici/lib/web/fetch/formdata-parser.js +586 -0
- package/node_modules/undici/lib/web/fetch/formdata.js +259 -0
- package/node_modules/undici/lib/web/fetch/global.js +40 -0
- package/node_modules/undici/lib/web/fetch/headers.js +719 -0
- package/node_modules/undici/lib/web/fetch/index.js +2413 -0
- package/node_modules/undici/lib/web/fetch/request.js +1115 -0
- package/node_modules/undici/lib/web/fetch/response.js +641 -0
- package/node_modules/undici/lib/web/fetch/util.js +1522 -0
- package/node_modules/undici/lib/web/infra/index.js +229 -0
- package/node_modules/undici/lib/web/subresource-integrity/Readme.md +9 -0
- package/node_modules/undici/lib/web/subresource-integrity/subresource-integrity.js +307 -0
- package/node_modules/undici/lib/web/webidl/index.js +1006 -0
- package/node_modules/undici/lib/web/websocket/connection.js +339 -0
- package/node_modules/undici/lib/web/websocket/constants.js +126 -0
- package/node_modules/undici/lib/web/websocket/events.js +331 -0
- package/node_modules/undici/lib/web/websocket/frame.js +133 -0
- package/node_modules/undici/lib/web/websocket/permessage-deflate.js +105 -0
- package/node_modules/undici/lib/web/websocket/receiver.js +507 -0
- package/node_modules/undici/lib/web/websocket/sender.js +109 -0
- package/node_modules/undici/lib/web/websocket/stream/websocketerror.js +104 -0
- package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +496 -0
- package/node_modules/undici/lib/web/websocket/util.js +347 -0
- package/node_modules/undici/lib/web/websocket/websocket.js +758 -0
- package/node_modules/undici/package.json +152 -0
- package/node_modules/undici/scripts/strip-comments.js +10 -0
- package/node_modules/undici/types/README.md +6 -0
- package/node_modules/undici/types/agent.d.ts +32 -0
- package/node_modules/undici/types/api.d.ts +43 -0
- package/node_modules/undici/types/balanced-pool.d.ts +30 -0
- package/node_modules/undici/types/cache-interceptor.d.ts +179 -0
- package/node_modules/undici/types/cache.d.ts +36 -0
- package/node_modules/undici/types/client-stats.d.ts +15 -0
- package/node_modules/undici/types/client.d.ts +139 -0
- package/node_modules/undici/types/connector.d.ts +36 -0
- package/node_modules/undici/types/content-type.d.ts +21 -0
- package/node_modules/undici/types/cookies.d.ts +30 -0
- package/node_modules/undici/types/diagnostics-channel.d.ts +74 -0
- package/node_modules/undici/types/dispatcher.d.ts +275 -0
- package/node_modules/undici/types/env-http-proxy-agent.d.ts +22 -0
- package/node_modules/undici/types/errors.d.ts +177 -0
- package/node_modules/undici/types/eventsource.d.ts +66 -0
- package/node_modules/undici/types/fetch.d.ts +231 -0
- package/node_modules/undici/types/formdata.d.ts +114 -0
- package/node_modules/undici/types/global-dispatcher.d.ts +9 -0
- package/node_modules/undici/types/global-origin.d.ts +7 -0
- package/node_modules/undici/types/h2c-client.d.ts +73 -0
- package/node_modules/undici/types/handlers.d.ts +14 -0
- package/node_modules/undici/types/header.d.ts +160 -0
- package/node_modules/undici/types/index.d.ts +91 -0
- package/node_modules/undici/types/interceptors.d.ts +82 -0
- package/node_modules/undici/types/mock-agent.d.ts +68 -0
- package/node_modules/undici/types/mock-call-history.d.ts +111 -0
- package/node_modules/undici/types/mock-client.d.ts +27 -0
- package/node_modules/undici/types/mock-errors.d.ts +12 -0
- package/node_modules/undici/types/mock-interceptor.d.ts +94 -0
- package/node_modules/undici/types/mock-pool.d.ts +27 -0
- package/node_modules/undici/types/patch.d.ts +29 -0
- package/node_modules/undici/types/pool-stats.d.ts +19 -0
- package/node_modules/undici/types/pool.d.ts +41 -0
- package/node_modules/undici/types/proxy-agent.d.ts +29 -0
- package/node_modules/undici/types/readable.d.ts +68 -0
- package/node_modules/undici/types/retry-agent.d.ts +8 -0
- package/node_modules/undici/types/retry-handler.d.ts +125 -0
- package/node_modules/undici/types/round-robin-pool.d.ts +41 -0
- package/node_modules/undici/types/snapshot-agent.d.ts +109 -0
- package/node_modules/undici/types/socks5-proxy-agent.d.ts +25 -0
- package/node_modules/undici/types/util.d.ts +18 -0
- package/node_modules/undici/types/utility.d.ts +7 -0
- package/node_modules/undici/types/webidl.d.ts +347 -0
- package/node_modules/undici/types/websocket.d.ts +188 -0
- package/package.json +4 -2
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -112,6 +112,262 @@ function clientVersionHeaders() {
|
|
|
112
112
|
};
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
// src/lib/as-pairing.ts
|
|
116
|
+
import { createHash } from "crypto";
|
|
117
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
|
|
118
|
+
import { dirname, join as join2 } from "path";
|
|
119
|
+
function pairingPath(dataDir2) {
|
|
120
|
+
return join2(dataDir2, "as-pairing.json");
|
|
121
|
+
}
|
|
122
|
+
function readPairing(dataDir2) {
|
|
123
|
+
const path = pairingPath(dataDir2);
|
|
124
|
+
if (!existsSync2(path)) return null;
|
|
125
|
+
try {
|
|
126
|
+
const data = JSON.parse(readFileSync2(path, "utf-8"));
|
|
127
|
+
if (typeof data.asUrl !== "string" || typeof data.publicKeyHex !== "string") return null;
|
|
128
|
+
return {
|
|
129
|
+
asUrl: data.asUrl,
|
|
130
|
+
publicKeyHex: data.publicKeyHex,
|
|
131
|
+
pairedAt: data.pairedAt ?? "",
|
|
132
|
+
...typeof data.tlsSpkiPinHex === "string" ? { tlsSpkiPinHex: data.tlsSpkiPinHex } : {}
|
|
133
|
+
};
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function fingerprintOf(publicKeyHex) {
|
|
139
|
+
const raw = Buffer.from(publicKeyHex, "hex");
|
|
140
|
+
const digest = createHash("sha256").update(raw).digest("hex").toUpperCase();
|
|
141
|
+
return digest.match(/.{1,4}/g)?.join(":") ?? digest;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/lib/as-config.ts
|
|
145
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
146
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
147
|
+
var DEFAULT_AS_URL = "https://www.suveren.ai";
|
|
148
|
+
function configPath(dataDir2) {
|
|
149
|
+
return join3(dataDir2, "config.json");
|
|
150
|
+
}
|
|
151
|
+
function readAsConfigStrict(dataDir2) {
|
|
152
|
+
const path = configPath(dataDir2);
|
|
153
|
+
if (!existsSync3(path)) return {};
|
|
154
|
+
let data;
|
|
155
|
+
try {
|
|
156
|
+
data = JSON.parse(readFileSync3(path, "utf-8"));
|
|
157
|
+
} catch (err) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`Could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
const out = {};
|
|
163
|
+
if (typeof data.asUrl === "string") out.asUrl = data.asUrl;
|
|
164
|
+
if (typeof data.caFile === "string") out.caFile = data.caFile;
|
|
165
|
+
if (typeof data.pinTls === "boolean") out.pinTls = data.pinTls;
|
|
166
|
+
if (typeof data.pinTlsExpectedFingerprint === "string") out.pinTlsExpectedFingerprint = data.pinTlsExpectedFingerprint;
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
function readAsConfig(dataDir2) {
|
|
170
|
+
const path = configPath(dataDir2);
|
|
171
|
+
if (!existsSync3(path)) return {};
|
|
172
|
+
try {
|
|
173
|
+
const data = JSON.parse(readFileSync3(path, "utf-8"));
|
|
174
|
+
const out = {};
|
|
175
|
+
if (typeof data.asUrl === "string") out.asUrl = data.asUrl;
|
|
176
|
+
if (typeof data.caFile === "string") out.caFile = data.caFile;
|
|
177
|
+
if (typeof data.pinTls === "boolean") out.pinTls = data.pinTls;
|
|
178
|
+
if (typeof data.pinTlsExpectedFingerprint === "string") out.pinTlsExpectedFingerprint = data.pinTlsExpectedFingerprint;
|
|
179
|
+
return out;
|
|
180
|
+
} catch {
|
|
181
|
+
return {};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function validateAsUrl(candidate) {
|
|
185
|
+
const trimmed = candidate.trim();
|
|
186
|
+
if (!trimmed) return { ok: false, error: "The Authority Server URL is empty." };
|
|
187
|
+
let parsed;
|
|
188
|
+
try {
|
|
189
|
+
parsed = new URL(trimmed);
|
|
190
|
+
} catch {
|
|
191
|
+
return { ok: false, error: `"${trimmed}" is not a valid URL.` };
|
|
192
|
+
}
|
|
193
|
+
const isLocalHttp = parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1");
|
|
194
|
+
if (parsed.protocol !== "https:" && !isLocalHttp) {
|
|
195
|
+
return {
|
|
196
|
+
ok: false,
|
|
197
|
+
error: `"${trimmed}" must use https:// (http:// is only allowed for http://localhost or http://127.0.0.1).`
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
let normalized = `${parsed.origin}${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
201
|
+
if (normalized.endsWith("/") && normalized !== `${parsed.origin}/`) {
|
|
202
|
+
normalized = normalized.slice(0, -1);
|
|
203
|
+
} else if (normalized === `${parsed.origin}/`) {
|
|
204
|
+
normalized = parsed.origin;
|
|
205
|
+
}
|
|
206
|
+
return { ok: true, url: normalized };
|
|
207
|
+
}
|
|
208
|
+
function resolveAsUrl(dataDir2, flag) {
|
|
209
|
+
if (flag) {
|
|
210
|
+
const v = validateAsUrl(flag);
|
|
211
|
+
if (!v.ok) throw new Error(`Invalid --as-url: ${v.error}`);
|
|
212
|
+
return v.url;
|
|
213
|
+
}
|
|
214
|
+
const envUrl = process.env.SUVEREN_AS_URL;
|
|
215
|
+
if (envUrl) {
|
|
216
|
+
const v = validateAsUrl(envUrl);
|
|
217
|
+
if (!v.ok) throw new Error(`Invalid SUVEREN_AS_URL: ${v.error}`);
|
|
218
|
+
return v.url;
|
|
219
|
+
}
|
|
220
|
+
const saved = readAsConfigStrict(dataDir2).asUrl;
|
|
221
|
+
if (saved) {
|
|
222
|
+
const v = validateAsUrl(saved);
|
|
223
|
+
if (!v.ok) throw new Error(`Invalid saved as-url "${saved}" in ${dataDir2}/config.json: ${v.error}`);
|
|
224
|
+
return v.url;
|
|
225
|
+
}
|
|
226
|
+
return DEFAULT_AS_URL;
|
|
227
|
+
}
|
|
228
|
+
function resolvePinTls(dataDir2) {
|
|
229
|
+
return readAsConfig(dataDir2).pinTls === true;
|
|
230
|
+
}
|
|
231
|
+
function validatePinTlsForUrl(pinTls, asUrl) {
|
|
232
|
+
if (!pinTls) return;
|
|
233
|
+
if (!asUrl.startsWith("https:")) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
`config.json sets "pinTls": true, but the Authority Server URL (${asUrl}) is not https:// \u2014 there is no TLS certificate to pin. Fix the saved as-url, or turn pin-tls off: \`suveren-gateway config set pin-tls off\`.`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/lib/as-tls-pin.ts
|
|
241
|
+
import { createHash as createHash2 } from "crypto";
|
|
242
|
+
import { checkServerIdentity as defaultCheckServerIdentity, rootCertificates } from "tls";
|
|
243
|
+
import { Agent as HttpsAgent } from "https";
|
|
244
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
245
|
+
import { Agent, fetch as undiciFetch } from "undici";
|
|
246
|
+
function effectiveCa() {
|
|
247
|
+
const path = process.env.NODE_EXTRA_CA_CERTS;
|
|
248
|
+
if (!path) return void 0;
|
|
249
|
+
try {
|
|
250
|
+
return [...rootCertificates, readFileSync4(path, "utf-8")];
|
|
251
|
+
} catch {
|
|
252
|
+
return void 0;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
var PIN_CHECK_ERROR_CODE = "EAS_TLS_PIN_CHECK";
|
|
256
|
+
function taggedCheckError(err) {
|
|
257
|
+
err.code = PIN_CHECK_ERROR_CODE;
|
|
258
|
+
return err;
|
|
259
|
+
}
|
|
260
|
+
function isPinCheckError(err) {
|
|
261
|
+
let cur = err;
|
|
262
|
+
for (let i = 0; i < 5 && cur; i++) {
|
|
263
|
+
if (cur instanceof Error && cur.code === PIN_CHECK_ERROR_CODE) return true;
|
|
264
|
+
cur = cur instanceof Error ? cur.cause : void 0;
|
|
265
|
+
}
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
var AsTlsMismatchError = class extends Error {
|
|
269
|
+
constructor(message) {
|
|
270
|
+
super(message);
|
|
271
|
+
this.name = "AsTlsMismatchError";
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
function spkiSha256Hex(cert) {
|
|
275
|
+
if (!cert.pubkey) {
|
|
276
|
+
throw new Error("peer certificate carries no pubkey (expected the detailed certificate object)");
|
|
277
|
+
}
|
|
278
|
+
return createHash2("sha256").update(cert.pubkey).digest("hex");
|
|
279
|
+
}
|
|
280
|
+
function formatPinFingerprint(hex) {
|
|
281
|
+
const upper = hex.toUpperCase();
|
|
282
|
+
return upper.match(/.{1,4}/g)?.join(":") ?? upper;
|
|
283
|
+
}
|
|
284
|
+
function pinnedCheckServerIdentity(pinnedSpkiHex) {
|
|
285
|
+
const wantHex = pinnedSpkiHex.toLowerCase();
|
|
286
|
+
return (hostname, cert) => {
|
|
287
|
+
const defaultErr = defaultCheckServerIdentity(hostname, cert);
|
|
288
|
+
if (defaultErr) return taggedCheckError(defaultErr);
|
|
289
|
+
let actualHex;
|
|
290
|
+
try {
|
|
291
|
+
actualHex = spkiSha256Hex(cert);
|
|
292
|
+
} catch (err) {
|
|
293
|
+
return taggedCheckError(new Error(
|
|
294
|
+
`could not read the server certificate's public key to check the TLS pin \u2014 ${err instanceof Error ? err.message : String(err)}`
|
|
295
|
+
));
|
|
296
|
+
}
|
|
297
|
+
if (actualHex !== wantHex) {
|
|
298
|
+
return taggedCheckError(new Error(
|
|
299
|
+
`TLS certificate pin mismatch for ${hostname}: expected SPKI sha256 ${formatPinFingerprint(wantHex)}, got ${formatPinFingerprint(actualHex)}.`
|
|
300
|
+
));
|
|
301
|
+
}
|
|
302
|
+
return void 0;
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function buildPinnedDispatcher(pinnedSpkiHex) {
|
|
306
|
+
return new Agent({
|
|
307
|
+
connect: {
|
|
308
|
+
ca: effectiveCa(),
|
|
309
|
+
checkServerIdentity: pinnedCheckServerIdentity(pinnedSpkiHex)
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
function buildCapturingDispatcher() {
|
|
314
|
+
let captured;
|
|
315
|
+
const dispatcher = new Agent({
|
|
316
|
+
connect: {
|
|
317
|
+
ca: effectiveCa(),
|
|
318
|
+
checkServerIdentity: (hostname, cert) => {
|
|
319
|
+
captured = cert;
|
|
320
|
+
const defaultErr = defaultCheckServerIdentity(hostname, cert);
|
|
321
|
+
return defaultErr ? taggedCheckError(defaultErr) : void 0;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
return { dispatcher, getCapturedCert: () => captured };
|
|
326
|
+
}
|
|
327
|
+
async function fetchAs(url, init, pinning) {
|
|
328
|
+
if (!pinning.enforce && !pinning.capture) {
|
|
329
|
+
const res2 = await fetch(url, init);
|
|
330
|
+
return { res: res2 };
|
|
331
|
+
}
|
|
332
|
+
if (pinning.enforce && !pinning.capture && !pinning.pinnedSpkiHex) {
|
|
333
|
+
throw new AsTlsMismatchError(
|
|
334
|
+
`pin-tls is on but no certificate is pinned yet for ${url} \u2014 sign in again to establish it (re-pairing required).`
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
if (pinning.pinnedSpkiHex && (pinning.enforce || !pinning.capture)) {
|
|
338
|
+
const dispatcher2 = buildPinnedDispatcher(pinning.pinnedSpkiHex);
|
|
339
|
+
try {
|
|
340
|
+
const res2 = await undiciFetch(url, { ...init, dispatcher: dispatcher2 });
|
|
341
|
+
return { res: res2 };
|
|
342
|
+
} catch (err) {
|
|
343
|
+
if (!isPinCheckError(err)) throw err;
|
|
344
|
+
throw new AsTlsMismatchError(
|
|
345
|
+
`TLS pin check failed for ${url} \u2014 ${describeFetchError(err)}. Refusing the connection; the API key / session cookie were never sent. If the Authority Server's certificate changed intentionally (a new key, not just renewal), an operator must clear the pairing and sign in again to re-pin it.`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const { dispatcher, getCapturedCert } = buildCapturingDispatcher();
|
|
350
|
+
let res;
|
|
351
|
+
try {
|
|
352
|
+
res = await undiciFetch(url, { ...init, dispatcher });
|
|
353
|
+
} catch (err) {
|
|
354
|
+
if (!isPinCheckError(err)) throw err;
|
|
355
|
+
throw new AsTlsMismatchError(
|
|
356
|
+
`could not verify the Authority Server at ${url} while establishing its TLS pin \u2014 ${describeFetchError(err)}.`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
const cert = getCapturedCert();
|
|
360
|
+
return { res, capturedSpkiHex: cert ? spkiSha256Hex(cert) : void 0 };
|
|
361
|
+
}
|
|
362
|
+
function describeFetchError(err) {
|
|
363
|
+
if (err instanceof Error) {
|
|
364
|
+
const cause = err.cause;
|
|
365
|
+
if (cause instanceof Error) return cause.message;
|
|
366
|
+
return err.message;
|
|
367
|
+
}
|
|
368
|
+
return String(err);
|
|
369
|
+
}
|
|
370
|
+
|
|
115
371
|
// src/lib/sp-client.ts
|
|
116
372
|
var SPReceiptError = class extends Error {
|
|
117
373
|
constructor(message, statusCode, body) {
|
|
@@ -127,8 +383,9 @@ var DEFAULT_RECEIPT_RETRY = {
|
|
|
127
383
|
};
|
|
128
384
|
var sleep = (ms) => ms > 0 ? new Promise((resolve3) => setTimeout(resolve3, ms)) : Promise.resolve();
|
|
129
385
|
var SPClient = class {
|
|
130
|
-
constructor(baseUrl, receiptRetry = {}) {
|
|
386
|
+
constructor(baseUrl, receiptRetry = {}, dataDir2) {
|
|
131
387
|
this.baseUrl = baseUrl;
|
|
388
|
+
this.dataDir = dataDir2;
|
|
132
389
|
this.receiptRetry = { ...DEFAULT_RECEIPT_RETRY, ...receiptRetry };
|
|
133
390
|
}
|
|
134
391
|
sessionCookie = "";
|
|
@@ -141,6 +398,22 @@ var SPClient = class {
|
|
|
141
398
|
* was active tells the control plane. Concurrent in-flight calls that were
|
|
142
399
|
* all sent under the same now-dead cookie would otherwise all report it. */
|
|
143
400
|
sessionExpiredNotified = false;
|
|
401
|
+
/** Same single-flight shape, for a TLS pin mismatch (as-tls-pin.ts). */
|
|
402
|
+
tlsMismatchNotified = false;
|
|
403
|
+
/** Opt-in TLS pinning (as-tls-pin.ts) for every call THIS client makes.
|
|
404
|
+
* Re-read live on each call rather than cached — same reasoning as
|
|
405
|
+
* AttestationCache.getPublicKey's pin check: a pin or setting change
|
|
406
|
+
* must take effect on the very next call, not the next restart. Never
|
|
407
|
+
* allowed to CAPTURE a new pin here — only the control plane's sign-in
|
|
408
|
+
* exchange (as-challenge.ts) does that; this client only enforces. */
|
|
409
|
+
tlsPinning() {
|
|
410
|
+
if (!this.dataDir) return { enforce: false };
|
|
411
|
+
const pin = readPairing(this.dataDir);
|
|
412
|
+
return {
|
|
413
|
+
enforce: resolvePinTls(this.dataDir),
|
|
414
|
+
pinnedSpkiHex: pin && pin.asUrl === this.baseUrl ? pin.tlsSpkiPinHex : void 0
|
|
415
|
+
};
|
|
416
|
+
}
|
|
144
417
|
/** The AS base URL this client talks to (for provenance records). */
|
|
145
418
|
get url() {
|
|
146
419
|
return this.baseUrl;
|
|
@@ -152,6 +425,7 @@ var SPClient = class {
|
|
|
152
425
|
this.sessionCookie = cookie;
|
|
153
426
|
this.lockReason = null;
|
|
154
427
|
this.sessionExpiredNotified = false;
|
|
428
|
+
this.tlsMismatchNotified = false;
|
|
155
429
|
}
|
|
156
430
|
/**
|
|
157
431
|
* Drop the session because the control plane learned (by whatever path —
|
|
@@ -205,10 +479,21 @@ var SPClient = class {
|
|
|
205
479
|
headers["X-API-Key"] = this.apiKey;
|
|
206
480
|
}
|
|
207
481
|
const hadSession = this.sessionCookie !== "";
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
headers
|
|
211
|
-
|
|
482
|
+
let res;
|
|
483
|
+
try {
|
|
484
|
+
const result = await fetchAs(url, { ...init, headers }, this.tlsPinning());
|
|
485
|
+
res = result.res;
|
|
486
|
+
} catch (err) {
|
|
487
|
+
if (err instanceof AsTlsMismatchError) {
|
|
488
|
+
this.sessionCookie = "";
|
|
489
|
+
this.lockReason = "as-tls-mismatch";
|
|
490
|
+
if (!this.tlsMismatchNotified) {
|
|
491
|
+
this.tlsMismatchNotified = true;
|
|
492
|
+
void notifyControlPlane("as-tls-mismatch");
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
throw err;
|
|
496
|
+
}
|
|
212
497
|
if (res.status === 401 && hadSession) {
|
|
213
498
|
this.sessionCookie = "";
|
|
214
499
|
this.lockReason = "expired";
|
|
@@ -371,32 +656,6 @@ var SPClient = class {
|
|
|
371
656
|
|
|
372
657
|
// src/lib/attestation-cache.ts
|
|
373
658
|
import { decodeAttestationBlob } from "@hap/core";
|
|
374
|
-
|
|
375
|
-
// src/lib/as-pairing.ts
|
|
376
|
-
import { createHash } from "crypto";
|
|
377
|
-
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
|
|
378
|
-
import { dirname, join as join2 } from "path";
|
|
379
|
-
function pairingPath(dataDir2) {
|
|
380
|
-
return join2(dataDir2, "as-pairing.json");
|
|
381
|
-
}
|
|
382
|
-
function readPairing(dataDir2) {
|
|
383
|
-
const path = pairingPath(dataDir2);
|
|
384
|
-
if (!existsSync2(path)) return null;
|
|
385
|
-
try {
|
|
386
|
-
const data = JSON.parse(readFileSync2(path, "utf-8"));
|
|
387
|
-
if (typeof data.asUrl !== "string" || typeof data.publicKeyHex !== "string") return null;
|
|
388
|
-
return { asUrl: data.asUrl, publicKeyHex: data.publicKeyHex, pairedAt: data.pairedAt ?? "" };
|
|
389
|
-
} catch {
|
|
390
|
-
return null;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
function fingerprintOf(publicKeyHex) {
|
|
394
|
-
const raw = Buffer.from(publicKeyHex, "hex");
|
|
395
|
-
const digest = createHash("sha256").update(raw).digest("hex").toUpperCase();
|
|
396
|
-
return digest.match(/.{1,4}/g)?.join(":") ?? digest;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// src/lib/attestation-cache.ts
|
|
400
659
|
var AsKeyMismatchError = class extends Error {
|
|
401
660
|
constructor(message) {
|
|
402
661
|
super(message);
|
|
@@ -558,18 +817,18 @@ var AttestationCache = class {
|
|
|
558
817
|
};
|
|
559
818
|
|
|
560
819
|
// src/lib/gate-store.ts
|
|
561
|
-
import { mkdirSync as
|
|
820
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
562
821
|
import { homedir } from "os";
|
|
563
|
-
import { dirname as
|
|
822
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
564
823
|
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
565
|
-
var DEFAULT_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
824
|
+
var DEFAULT_DIR = process.env.SUVEREN_DATA_DIR ?? join4(homedir(), ".suveren");
|
|
566
825
|
var GateStore = class {
|
|
567
826
|
entries = /* @__PURE__ */ new Map();
|
|
568
827
|
baseDir;
|
|
569
828
|
vaultKey = null;
|
|
570
829
|
constructor(filePath) {
|
|
571
830
|
if (filePath && filePath.endsWith(".json")) {
|
|
572
|
-
this.baseDir =
|
|
831
|
+
this.baseDir = dirname3(filePath);
|
|
573
832
|
} else {
|
|
574
833
|
this.baseDir = filePath ?? DEFAULT_DIR;
|
|
575
834
|
}
|
|
@@ -580,11 +839,11 @@ var GateStore = class {
|
|
|
580
839
|
*/
|
|
581
840
|
setVaultKey(key) {
|
|
582
841
|
this.vaultKey = key;
|
|
583
|
-
if (
|
|
842
|
+
if (existsSync4(this.encryptedFilePath)) {
|
|
584
843
|
this.loadEncrypted();
|
|
585
844
|
} else if (this.entries.size > 0) {
|
|
586
845
|
this.persistEncrypted();
|
|
587
|
-
if (
|
|
846
|
+
if (existsSync4(this.plaintextFilePath)) {
|
|
588
847
|
try {
|
|
589
848
|
unlinkSync2(this.plaintextFilePath);
|
|
590
849
|
} catch {
|
|
@@ -622,11 +881,11 @@ var GateStore = class {
|
|
|
622
881
|
clearAll() {
|
|
623
882
|
this.entries = /* @__PURE__ */ new Map();
|
|
624
883
|
try {
|
|
625
|
-
if (
|
|
884
|
+
if (existsSync4(this.plaintextFilePath)) unlinkSync2(this.plaintextFilePath);
|
|
626
885
|
} catch {
|
|
627
886
|
}
|
|
628
887
|
try {
|
|
629
|
-
if (
|
|
888
|
+
if (existsSync4(this.encryptedFilePath)) unlinkSync2(this.encryptedFilePath);
|
|
630
889
|
} catch {
|
|
631
890
|
}
|
|
632
891
|
}
|
|
@@ -659,19 +918,19 @@ var GateStore = class {
|
|
|
659
918
|
}
|
|
660
919
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
661
920
|
get plaintextFilePath() {
|
|
662
|
-
return
|
|
921
|
+
return join4(this.baseDir, "gates.json");
|
|
663
922
|
}
|
|
664
923
|
get encryptedFilePath() {
|
|
665
|
-
return
|
|
924
|
+
return join4(this.baseDir, "gates.enc.json");
|
|
666
925
|
}
|
|
667
926
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
668
927
|
loadPlaintext() {
|
|
669
|
-
if (!
|
|
670
|
-
|
|
928
|
+
if (!existsSync4(this.plaintextFilePath)) {
|
|
929
|
+
mkdirSync3(this.baseDir, { recursive: true });
|
|
671
930
|
return;
|
|
672
931
|
}
|
|
673
932
|
try {
|
|
674
|
-
const raw =
|
|
933
|
+
const raw = readFileSync5(this.plaintextFilePath, "utf-8");
|
|
675
934
|
const data = JSON.parse(raw);
|
|
676
935
|
this.entries = new Map(Object.entries(data.entries));
|
|
677
936
|
} catch {
|
|
@@ -680,9 +939,9 @@ var GateStore = class {
|
|
|
680
939
|
}
|
|
681
940
|
}
|
|
682
941
|
loadEncrypted() {
|
|
683
|
-
if (!
|
|
942
|
+
if (!existsSync4(this.encryptedFilePath)) return;
|
|
684
943
|
try {
|
|
685
|
-
const raw =
|
|
944
|
+
const raw = readFileSync5(this.encryptedFilePath, "utf-8");
|
|
686
945
|
const data = JSON.parse(raw);
|
|
687
946
|
this.entries = /* @__PURE__ */ new Map();
|
|
688
947
|
for (const [key, blob] of Object.entries(data.entries)) {
|
|
@@ -706,8 +965,8 @@ var GateStore = class {
|
|
|
706
965
|
version: 1,
|
|
707
966
|
entries: Object.fromEntries(this.entries)
|
|
708
967
|
};
|
|
709
|
-
|
|
710
|
-
|
|
968
|
+
mkdirSync3(this.baseDir, { recursive: true, mode: 448 });
|
|
969
|
+
writeFileSync3(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
711
970
|
}
|
|
712
971
|
persistEncrypted() {
|
|
713
972
|
const data = {
|
|
@@ -717,17 +976,17 @@ var GateStore = class {
|
|
|
717
976
|
for (const [key, entry] of this.entries) {
|
|
718
977
|
data.entries[key] = this.encrypt(JSON.stringify(entry));
|
|
719
978
|
}
|
|
720
|
-
|
|
721
|
-
|
|
979
|
+
mkdirSync3(this.baseDir, { recursive: true, mode: 448 });
|
|
980
|
+
writeFileSync3(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
722
981
|
}
|
|
723
982
|
};
|
|
724
983
|
|
|
725
984
|
// src/lib/execution-log.ts
|
|
726
|
-
import { mkdirSync as
|
|
985
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync5, unlinkSync as unlinkSync3 } from "fs";
|
|
727
986
|
import { homedir as homedir2 } from "os";
|
|
728
|
-
import { dirname as
|
|
987
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
729
988
|
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes2 } from "crypto";
|
|
730
|
-
var DEFAULT_DIR2 = process.env.SUVEREN_DATA_DIR ??
|
|
989
|
+
var DEFAULT_DIR2 = process.env.SUVEREN_DATA_DIR ?? join5(homedir2(), ".suveren");
|
|
731
990
|
var MAX_AGE_SECONDS = 31 * 24 * 60 * 60;
|
|
732
991
|
var ExecutionLog = class {
|
|
733
992
|
entries = [];
|
|
@@ -735,7 +994,7 @@ var ExecutionLog = class {
|
|
|
735
994
|
vaultKey = null;
|
|
736
995
|
constructor(filePath) {
|
|
737
996
|
if (filePath && filePath.endsWith(".json")) {
|
|
738
|
-
this.baseDir =
|
|
997
|
+
this.baseDir = dirname4(filePath);
|
|
739
998
|
} else {
|
|
740
999
|
this.baseDir = filePath ?? DEFAULT_DIR2;
|
|
741
1000
|
}
|
|
@@ -746,11 +1005,11 @@ var ExecutionLog = class {
|
|
|
746
1005
|
*/
|
|
747
1006
|
setVaultKey(key) {
|
|
748
1007
|
this.vaultKey = key;
|
|
749
|
-
if (
|
|
1008
|
+
if (existsSync5(this.encryptedFilePath)) {
|
|
750
1009
|
this.loadEncrypted();
|
|
751
1010
|
} else if (this.entries.length > 0) {
|
|
752
1011
|
this.persistEncrypted();
|
|
753
|
-
if (
|
|
1012
|
+
if (existsSync5(this.plaintextFilePath)) {
|
|
754
1013
|
try {
|
|
755
1014
|
unlinkSync3(this.plaintextFilePath);
|
|
756
1015
|
} catch {
|
|
@@ -854,19 +1113,19 @@ var ExecutionLog = class {
|
|
|
854
1113
|
}
|
|
855
1114
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
856
1115
|
get plaintextFilePath() {
|
|
857
|
-
return
|
|
1116
|
+
return join5(this.baseDir, "execution-log.json");
|
|
858
1117
|
}
|
|
859
1118
|
get encryptedFilePath() {
|
|
860
|
-
return
|
|
1119
|
+
return join5(this.baseDir, "execution-log.enc.json");
|
|
861
1120
|
}
|
|
862
1121
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
863
1122
|
loadPlaintext() {
|
|
864
|
-
if (!
|
|
865
|
-
|
|
1123
|
+
if (!existsSync5(this.plaintextFilePath)) {
|
|
1124
|
+
mkdirSync4(this.baseDir, { recursive: true });
|
|
866
1125
|
return;
|
|
867
1126
|
}
|
|
868
1127
|
try {
|
|
869
|
-
const raw =
|
|
1128
|
+
const raw = readFileSync6(this.plaintextFilePath, "utf-8");
|
|
870
1129
|
const data = JSON.parse(raw);
|
|
871
1130
|
this.entries = data.entries;
|
|
872
1131
|
this.prune();
|
|
@@ -876,9 +1135,9 @@ var ExecutionLog = class {
|
|
|
876
1135
|
}
|
|
877
1136
|
}
|
|
878
1137
|
loadEncrypted() {
|
|
879
|
-
if (!
|
|
1138
|
+
if (!existsSync5(this.encryptedFilePath)) return;
|
|
880
1139
|
try {
|
|
881
|
-
const raw =
|
|
1140
|
+
const raw = readFileSync6(this.encryptedFilePath, "utf-8");
|
|
882
1141
|
const data = JSON.parse(raw);
|
|
883
1142
|
const decrypted = this.decrypt(data.blob);
|
|
884
1143
|
const logFile = JSON.parse(decrypted);
|
|
@@ -898,8 +1157,8 @@ var ExecutionLog = class {
|
|
|
898
1157
|
}
|
|
899
1158
|
persistPlaintext() {
|
|
900
1159
|
const data = { version: 1, entries: this.entries };
|
|
901
|
-
|
|
902
|
-
|
|
1160
|
+
mkdirSync4(this.baseDir, { recursive: true, mode: 448 });
|
|
1161
|
+
writeFileSync4(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
903
1162
|
}
|
|
904
1163
|
persistEncrypted() {
|
|
905
1164
|
const logFile = { version: 1, entries: this.entries };
|
|
@@ -907,8 +1166,8 @@ var ExecutionLog = class {
|
|
|
907
1166
|
version: 1,
|
|
908
1167
|
blob: this.encrypt(JSON.stringify(logFile))
|
|
909
1168
|
};
|
|
910
|
-
|
|
911
|
-
|
|
1169
|
+
mkdirSync4(this.baseDir, { recursive: true, mode: 448 });
|
|
1170
|
+
writeFileSync4(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
912
1171
|
}
|
|
913
1172
|
};
|
|
914
1173
|
function windowCutoff(window, now) {
|
|
@@ -925,11 +1184,11 @@ function windowCutoff(window, now) {
|
|
|
925
1184
|
}
|
|
926
1185
|
|
|
927
1186
|
// src/lib/denial-log.ts
|
|
928
|
-
import { mkdirSync as
|
|
1187
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync6, unlinkSync as unlinkSync4 } from "fs";
|
|
929
1188
|
import { homedir as homedir3 } from "os";
|
|
930
|
-
import { dirname as
|
|
1189
|
+
import { dirname as dirname5, join as join6 } from "path";
|
|
931
1190
|
import { createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3, randomBytes as randomBytes3 } from "crypto";
|
|
932
|
-
var DEFAULT_DIR3 = process.env.SUVEREN_DATA_DIR ??
|
|
1191
|
+
var DEFAULT_DIR3 = process.env.SUVEREN_DATA_DIR ?? join6(homedir3(), ".suveren");
|
|
933
1192
|
var MAX_RECORDS = 200;
|
|
934
1193
|
var MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
935
1194
|
var DenialLog = class {
|
|
@@ -937,16 +1196,16 @@ var DenialLog = class {
|
|
|
937
1196
|
baseDir;
|
|
938
1197
|
vaultKey = null;
|
|
939
1198
|
constructor(filePath) {
|
|
940
|
-
this.baseDir = filePath && filePath.endsWith(".json") ?
|
|
1199
|
+
this.baseDir = filePath && filePath.endsWith(".json") ? dirname5(filePath) : filePath ?? DEFAULT_DIR3;
|
|
941
1200
|
this.loadPlaintext();
|
|
942
1201
|
}
|
|
943
1202
|
setVaultKey(key) {
|
|
944
1203
|
this.vaultKey = key;
|
|
945
|
-
if (
|
|
1204
|
+
if (existsSync6(this.encryptedFilePath)) {
|
|
946
1205
|
this.loadEncrypted();
|
|
947
1206
|
} else if (this.records.length > 0) {
|
|
948
1207
|
this.persistEncrypted();
|
|
949
|
-
if (
|
|
1208
|
+
if (existsSync6(this.plaintextFilePath)) {
|
|
950
1209
|
try {
|
|
951
1210
|
unlinkSync4(this.plaintextFilePath);
|
|
952
1211
|
} catch {
|
|
@@ -996,18 +1255,18 @@ var DenialLog = class {
|
|
|
996
1255
|
return Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "hex")), decipher.final()]).toString("utf8");
|
|
997
1256
|
}
|
|
998
1257
|
get plaintextFilePath() {
|
|
999
|
-
return
|
|
1258
|
+
return join6(this.baseDir, "denials.json");
|
|
1000
1259
|
}
|
|
1001
1260
|
get encryptedFilePath() {
|
|
1002
|
-
return
|
|
1261
|
+
return join6(this.baseDir, "denials.enc.json");
|
|
1003
1262
|
}
|
|
1004
1263
|
loadPlaintext() {
|
|
1005
|
-
if (!
|
|
1006
|
-
|
|
1264
|
+
if (!existsSync6(this.plaintextFilePath)) {
|
|
1265
|
+
mkdirSync5(this.baseDir, { recursive: true });
|
|
1007
1266
|
return;
|
|
1008
1267
|
}
|
|
1009
1268
|
try {
|
|
1010
|
-
const data = JSON.parse(
|
|
1269
|
+
const data = JSON.parse(readFileSync7(this.plaintextFilePath, "utf-8"));
|
|
1011
1270
|
this.records = data.records ?? [];
|
|
1012
1271
|
} catch {
|
|
1013
1272
|
console.error(`[DenialLog] Could not parse ${this.plaintextFilePath}, starting fresh`);
|
|
@@ -1015,9 +1274,9 @@ var DenialLog = class {
|
|
|
1015
1274
|
}
|
|
1016
1275
|
}
|
|
1017
1276
|
loadEncrypted() {
|
|
1018
|
-
if (!
|
|
1277
|
+
if (!existsSync6(this.encryptedFilePath)) return;
|
|
1019
1278
|
try {
|
|
1020
|
-
const data = JSON.parse(
|
|
1279
|
+
const data = JSON.parse(readFileSync7(this.encryptedFilePath, "utf-8"));
|
|
1021
1280
|
const logFile = JSON.parse(this.decrypt(data.blob));
|
|
1022
1281
|
this.records = logFile.records ?? [];
|
|
1023
1282
|
} catch (err) {
|
|
@@ -1031,23 +1290,23 @@ var DenialLog = class {
|
|
|
1031
1290
|
}
|
|
1032
1291
|
persistPlaintext() {
|
|
1033
1292
|
const data = { version: 1, records: this.records };
|
|
1034
|
-
|
|
1035
|
-
|
|
1293
|
+
mkdirSync5(this.baseDir, { recursive: true, mode: 448 });
|
|
1294
|
+
writeFileSync5(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1036
1295
|
}
|
|
1037
1296
|
persistEncrypted() {
|
|
1038
1297
|
const logFile = { version: 1, records: this.records };
|
|
1039
1298
|
const data = { version: 1, blob: this.encrypt(JSON.stringify(logFile)) };
|
|
1040
|
-
|
|
1041
|
-
|
|
1299
|
+
mkdirSync5(this.baseDir, { recursive: true, mode: 448 });
|
|
1300
|
+
writeFileSync5(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1042
1301
|
}
|
|
1043
1302
|
};
|
|
1044
1303
|
|
|
1045
1304
|
// src/lib/receipt-archive.ts
|
|
1046
|
-
import { mkdirSync as
|
|
1305
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync7, unlinkSync as unlinkSync5 } from "fs";
|
|
1047
1306
|
import { homedir as homedir4 } from "os";
|
|
1048
|
-
import { dirname as
|
|
1307
|
+
import { dirname as dirname6, join as join7 } from "path";
|
|
1049
1308
|
import { createCipheriv as createCipheriv4, createDecipheriv as createDecipheriv4, randomBytes as randomBytes4 } from "crypto";
|
|
1050
|
-
var DEFAULT_DIR4 = process.env.SUVEREN_DATA_DIR ??
|
|
1309
|
+
var DEFAULT_DIR4 = process.env.SUVEREN_DATA_DIR ?? join7(homedir4(), ".suveren");
|
|
1051
1310
|
function stripPreviews(proposal) {
|
|
1052
1311
|
const args = proposal.toolArgs;
|
|
1053
1312
|
if (!args || typeof args !== "object") return proposal;
|
|
@@ -1061,7 +1320,7 @@ var ReceiptArchive = class {
|
|
|
1061
1320
|
vaultKey = null;
|
|
1062
1321
|
constructor(filePath) {
|
|
1063
1322
|
if (filePath && filePath.endsWith(".json")) {
|
|
1064
|
-
this.baseDir =
|
|
1323
|
+
this.baseDir = dirname6(filePath);
|
|
1065
1324
|
} else {
|
|
1066
1325
|
this.baseDir = filePath ?? DEFAULT_DIR4;
|
|
1067
1326
|
}
|
|
@@ -1072,11 +1331,11 @@ var ReceiptArchive = class {
|
|
|
1072
1331
|
*/
|
|
1073
1332
|
setVaultKey(key) {
|
|
1074
1333
|
this.vaultKey = key;
|
|
1075
|
-
if (
|
|
1334
|
+
if (existsSync7(this.encryptedFilePath)) {
|
|
1076
1335
|
this.loadEncrypted();
|
|
1077
1336
|
} else if (this.data.receipts.length > 0 || Object.keys(this.data.authorizations).length > 0) {
|
|
1078
1337
|
this.persistEncrypted();
|
|
1079
|
-
if (
|
|
1338
|
+
if (existsSync7(this.plaintextFilePath)) {
|
|
1080
1339
|
try {
|
|
1081
1340
|
unlinkSync5(this.plaintextFilePath);
|
|
1082
1341
|
} catch {
|
|
@@ -1090,7 +1349,7 @@ var ReceiptArchive = class {
|
|
|
1090
1349
|
* this state — that would report a hollow success over unreadable evidence.
|
|
1091
1350
|
*/
|
|
1092
1351
|
isLocked() {
|
|
1093
|
-
return this.vaultKey === null &&
|
|
1352
|
+
return this.vaultKey === null && existsSync7(this.encryptedFilePath);
|
|
1094
1353
|
}
|
|
1095
1354
|
/**
|
|
1096
1355
|
* Archive a signed receipt (and the grant it was issued under).
|
|
@@ -1191,19 +1450,19 @@ var ReceiptArchive = class {
|
|
|
1191
1450
|
}
|
|
1192
1451
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
1193
1452
|
get plaintextFilePath() {
|
|
1194
|
-
return
|
|
1453
|
+
return join7(this.baseDir, "receipt-archive.json");
|
|
1195
1454
|
}
|
|
1196
1455
|
get encryptedFilePath() {
|
|
1197
|
-
return
|
|
1456
|
+
return join7(this.baseDir, "receipt-archive.enc.json");
|
|
1198
1457
|
}
|
|
1199
1458
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
1200
1459
|
loadPlaintext() {
|
|
1201
|
-
if (!
|
|
1202
|
-
|
|
1460
|
+
if (!existsSync7(this.plaintextFilePath)) {
|
|
1461
|
+
mkdirSync6(this.baseDir, { recursive: true });
|
|
1203
1462
|
return;
|
|
1204
1463
|
}
|
|
1205
1464
|
try {
|
|
1206
|
-
const raw =
|
|
1465
|
+
const raw = readFileSync8(this.plaintextFilePath, "utf-8");
|
|
1207
1466
|
const parsed = JSON.parse(raw);
|
|
1208
1467
|
this.data = {
|
|
1209
1468
|
version: 1,
|
|
@@ -1213,18 +1472,18 @@ var ReceiptArchive = class {
|
|
|
1213
1472
|
} catch {
|
|
1214
1473
|
console.error(`[ReceiptArchive] Could not parse ${this.plaintextFilePath} \u2014 preserving as .corrupt`);
|
|
1215
1474
|
try {
|
|
1216
|
-
|
|
1475
|
+
writeFileSync6(
|
|
1217
1476
|
`${this.plaintextFilePath}.corrupt-${Date.now()}`,
|
|
1218
|
-
|
|
1477
|
+
readFileSync8(this.plaintextFilePath)
|
|
1219
1478
|
);
|
|
1220
1479
|
} catch {
|
|
1221
1480
|
}
|
|
1222
1481
|
}
|
|
1223
1482
|
}
|
|
1224
1483
|
loadEncrypted() {
|
|
1225
|
-
if (!
|
|
1484
|
+
if (!existsSync7(this.encryptedFilePath)) return;
|
|
1226
1485
|
try {
|
|
1227
|
-
const raw =
|
|
1486
|
+
const raw = readFileSync8(this.encryptedFilePath, "utf-8");
|
|
1228
1487
|
const data = JSON.parse(raw);
|
|
1229
1488
|
const decrypted = this.decrypt(data.blob);
|
|
1230
1489
|
const parsed = JSON.parse(decrypted);
|
|
@@ -1236,9 +1495,9 @@ var ReceiptArchive = class {
|
|
|
1236
1495
|
} catch (err) {
|
|
1237
1496
|
console.error(`[ReceiptArchive] Could not decrypt ${this.encryptedFilePath} \u2014 preserving as .corrupt:`, err);
|
|
1238
1497
|
try {
|
|
1239
|
-
|
|
1498
|
+
writeFileSync6(
|
|
1240
1499
|
`${this.encryptedFilePath}.corrupt-${Date.now()}`,
|
|
1241
|
-
|
|
1500
|
+
readFileSync8(this.encryptedFilePath)
|
|
1242
1501
|
);
|
|
1243
1502
|
} catch {
|
|
1244
1503
|
}
|
|
@@ -1252,29 +1511,29 @@ var ReceiptArchive = class {
|
|
|
1252
1511
|
}
|
|
1253
1512
|
}
|
|
1254
1513
|
persistPlaintext() {
|
|
1255
|
-
|
|
1256
|
-
|
|
1514
|
+
mkdirSync6(this.baseDir, { recursive: true, mode: 448 });
|
|
1515
|
+
writeFileSync6(this.plaintextFilePath, JSON.stringify(this.data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1257
1516
|
}
|
|
1258
1517
|
persistEncrypted() {
|
|
1259
1518
|
const data = {
|
|
1260
1519
|
version: 1,
|
|
1261
1520
|
blob: this.encrypt(JSON.stringify(this.data))
|
|
1262
1521
|
};
|
|
1263
|
-
|
|
1264
|
-
|
|
1522
|
+
mkdirSync6(this.baseDir, { recursive: true, mode: 448 });
|
|
1523
|
+
writeFileSync6(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1265
1524
|
}
|
|
1266
1525
|
};
|
|
1267
1526
|
|
|
1268
1527
|
// src/lib/execution-journal.ts
|
|
1269
|
-
import { mkdirSync as
|
|
1528
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync8, renameSync } from "fs";
|
|
1270
1529
|
import { homedir as homedir5 } from "os";
|
|
1271
|
-
import { dirname as
|
|
1272
|
-
import { createHash as
|
|
1273
|
-
var DEFAULT_DIR5 = process.env.SUVEREN_DATA_DIR ??
|
|
1530
|
+
import { dirname as dirname7, join as join8 } from "path";
|
|
1531
|
+
import { createHash as createHash3 } from "crypto";
|
|
1532
|
+
var DEFAULT_DIR5 = process.env.SUVEREN_DATA_DIR ?? join8(homedir5(), ".suveren");
|
|
1274
1533
|
var FILE_NAME = "execution-journal.json";
|
|
1275
1534
|
var MAX_AGE_SECONDS2 = 90 * 24 * 60 * 60;
|
|
1276
1535
|
function hashToolArgs(args) {
|
|
1277
|
-
return "sha256:" +
|
|
1536
|
+
return "sha256:" + createHash3("sha256").update(stableStringify(args)).digest("hex");
|
|
1278
1537
|
}
|
|
1279
1538
|
function stableStringify(value) {
|
|
1280
1539
|
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "undefined";
|
|
@@ -1286,8 +1545,8 @@ function stableStringify(value) {
|
|
|
1286
1545
|
var ExecutionJournal = class {
|
|
1287
1546
|
filePath;
|
|
1288
1547
|
constructor(filePath) {
|
|
1289
|
-
const baseDir = filePath && filePath.endsWith(".json") ?
|
|
1290
|
-
this.filePath =
|
|
1548
|
+
const baseDir = filePath && filePath.endsWith(".json") ? dirname7(filePath) : filePath ?? DEFAULT_DIR5;
|
|
1549
|
+
this.filePath = join8(baseDir, FILE_NAME);
|
|
1291
1550
|
}
|
|
1292
1551
|
/** Look a ticket up. Always reads from disk — another process may have written. */
|
|
1293
1552
|
get(ticketId) {
|
|
@@ -1326,9 +1585,9 @@ var ExecutionJournal = class {
|
|
|
1326
1585
|
}
|
|
1327
1586
|
// ─── Persistence ────────────────────────────────────────────────────────
|
|
1328
1587
|
load() {
|
|
1329
|
-
if (!
|
|
1588
|
+
if (!existsSync8(this.filePath)) return { version: 1, entries: [] };
|
|
1330
1589
|
try {
|
|
1331
|
-
const parsed = JSON.parse(
|
|
1590
|
+
const parsed = JSON.parse(readFileSync9(this.filePath, "utf8"));
|
|
1332
1591
|
if (parsed?.version !== 1 || !Array.isArray(parsed.entries)) {
|
|
1333
1592
|
throw new Error("unexpected shape");
|
|
1334
1593
|
}
|
|
@@ -1342,9 +1601,9 @@ var ExecutionJournal = class {
|
|
|
1342
1601
|
persist(file) {
|
|
1343
1602
|
const cutoff = nowSec() - MAX_AGE_SECONDS2;
|
|
1344
1603
|
file.entries = file.entries.filter((e) => e.startedAt >= cutoff);
|
|
1345
|
-
|
|
1604
|
+
mkdirSync7(dirname7(this.filePath), { recursive: true, mode: 448 });
|
|
1346
1605
|
const tmp = this.filePath + ".tmp";
|
|
1347
|
-
|
|
1606
|
+
writeFileSync7(tmp, JSON.stringify(file, null, 2), { mode: 384 });
|
|
1348
1607
|
renameSync(tmp, this.filePath);
|
|
1349
1608
|
}
|
|
1350
1609
|
};
|
|
@@ -1353,10 +1612,10 @@ function nowSec() {
|
|
|
1353
1612
|
}
|
|
1354
1613
|
|
|
1355
1614
|
// src/lib/proposal-submission-store.ts
|
|
1356
|
-
import { mkdirSync as
|
|
1615
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync8, existsSync as existsSync9, renameSync as renameSync2 } from "fs";
|
|
1357
1616
|
import { homedir as homedir6 } from "os";
|
|
1358
|
-
import { dirname as
|
|
1359
|
-
var DEFAULT_DIR6 = process.env.SUVEREN_DATA_DIR ??
|
|
1617
|
+
import { dirname as dirname8, join as join9 } from "path";
|
|
1618
|
+
var DEFAULT_DIR6 = process.env.SUVEREN_DATA_DIR ?? join9(homedir6(), ".suveren");
|
|
1360
1619
|
var FILE_NAME2 = "proposal-submissions.json";
|
|
1361
1620
|
var MAX_AGE_SECONDS3 = 90 * 24 * 60 * 60;
|
|
1362
1621
|
function nowSec2() {
|
|
@@ -1365,8 +1624,8 @@ function nowSec2() {
|
|
|
1365
1624
|
var ProposalSubmissionStore = class {
|
|
1366
1625
|
filePath;
|
|
1367
1626
|
constructor(filePath) {
|
|
1368
|
-
const baseDir = filePath && filePath.endsWith(".json") ?
|
|
1369
|
-
this.filePath =
|
|
1627
|
+
const baseDir = filePath && filePath.endsWith(".json") ? dirname8(filePath) : filePath ?? DEFAULT_DIR6;
|
|
1628
|
+
this.filePath = join9(baseDir, FILE_NAME2);
|
|
1370
1629
|
}
|
|
1371
1630
|
/** Record what was submitted, right after a successful submitProposal(). */
|
|
1372
1631
|
record(entry) {
|
|
@@ -1389,9 +1648,9 @@ var ProposalSubmissionStore = class {
|
|
|
1389
1648
|
return this.load().entries.find((e) => e.proposalId === proposalId);
|
|
1390
1649
|
}
|
|
1391
1650
|
load() {
|
|
1392
|
-
if (!
|
|
1651
|
+
if (!existsSync9(this.filePath)) return { version: 1, entries: [] };
|
|
1393
1652
|
try {
|
|
1394
|
-
const parsed = JSON.parse(
|
|
1653
|
+
const parsed = JSON.parse(readFileSync10(this.filePath, "utf-8"));
|
|
1395
1654
|
const cutoff = nowSec2() - MAX_AGE_SECONDS3;
|
|
1396
1655
|
parsed.entries = (parsed.entries ?? []).filter((e) => e.submittedAt >= cutoff);
|
|
1397
1656
|
return parsed;
|
|
@@ -1400,9 +1659,9 @@ var ProposalSubmissionStore = class {
|
|
|
1400
1659
|
}
|
|
1401
1660
|
}
|
|
1402
1661
|
persist(file) {
|
|
1403
|
-
|
|
1662
|
+
mkdirSync8(dirname8(this.filePath), { recursive: true, mode: 448 });
|
|
1404
1663
|
const tmpPath = `${this.filePath}.tmp`;
|
|
1405
|
-
|
|
1664
|
+
writeFileSync8(tmpPath, JSON.stringify(file, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1406
1665
|
renameSync2(tmpPath, this.filePath);
|
|
1407
1666
|
}
|
|
1408
1667
|
};
|
|
@@ -1514,12 +1773,13 @@ var SharedState = class {
|
|
|
1514
1773
|
gatekeeper;
|
|
1515
1774
|
/**
|
|
1516
1775
|
* @param dataDir Passed to the AttestationCache so it can enforce AS key
|
|
1517
|
-
* pinning (as-pairing.ts)
|
|
1518
|
-
*
|
|
1519
|
-
*
|
|
1776
|
+
* pinning (as-pairing.ts), and to the SPClient so it can enforce opt-in
|
|
1777
|
+
* TLS pinning (as-tls-pin.ts). Optional for backward compat with
|
|
1778
|
+
* existing constructions/tests that pass only a URL — those get
|
|
1779
|
+
* unpinned trust-on-first-use behavior, same as before pinning existed.
|
|
1520
1780
|
*/
|
|
1521
1781
|
constructor(spUrl2, gateStorePath, dataDir2) {
|
|
1522
|
-
this.spClient = new SPClient(spUrl2);
|
|
1782
|
+
this.spClient = new SPClient(spUrl2, void 0, dataDir2);
|
|
1523
1783
|
this.cache = new AttestationCache(this.spClient, dataDir2);
|
|
1524
1784
|
this.gateStore = new GateStore(gateStorePath);
|
|
1525
1785
|
this.executionLog = new ExecutionLog(gateStorePath);
|
|
@@ -1679,17 +1939,17 @@ function formatConsumptionFull(entries) {
|
|
|
1679
1939
|
}
|
|
1680
1940
|
|
|
1681
1941
|
// src/lib/context-loader.ts
|
|
1682
|
-
import { readFileSync as
|
|
1942
|
+
import { readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
|
|
1683
1943
|
import { homedir as homedir7 } from "os";
|
|
1684
|
-
import { join as
|
|
1685
|
-
var DEFAULT_DIR7 = process.env.SUVEREN_DATA_DIR ??
|
|
1944
|
+
import { join as join10 } from "path";
|
|
1945
|
+
var DEFAULT_DIR7 = process.env.SUVEREN_DATA_DIR ?? join10(homedir7(), ".suveren");
|
|
1686
1946
|
var BRIEF_MAX_CHARS = 16 * 1024;
|
|
1687
1947
|
function readContextFile(dataDir2) {
|
|
1688
1948
|
const dir = dataDir2 ?? DEFAULT_DIR7;
|
|
1689
|
-
const filePath =
|
|
1690
|
-
if (!
|
|
1949
|
+
const filePath = join10(dir, "context.md");
|
|
1950
|
+
if (!existsSync10(filePath)) return null;
|
|
1691
1951
|
try {
|
|
1692
|
-
const content =
|
|
1952
|
+
const content = readFileSync11(filePath, "utf-8").trim();
|
|
1693
1953
|
return content || null;
|
|
1694
1954
|
} catch {
|
|
1695
1955
|
return null;
|
|
@@ -1809,6 +2069,11 @@ function asKeyMismatchNotice(what) {
|
|
|
1809
2069
|
|
|
1810
2070
|
TELL THE USER: this needs an operator to check whether the Authority Server's key changed intentionally (e.g. a reinstall) or something else is answering at that address, at ${uiUrl()}. Nothing ran under the unrecognized key.`;
|
|
1811
2071
|
}
|
|
2072
|
+
function asTlsMismatchNotice(what) {
|
|
2073
|
+
return `${what} Suveren gateway is LOCKED because a connection to the Authority Server presented a TLS certificate that does not match the one pinned when this gateway last signed in (opt-in pin-tls). This is NOT an ended sign-in \u2014 signing in again will be refused until the mismatch is resolved.
|
|
2074
|
+
|
|
2075
|
+
TELL THE USER: this needs an operator to check whether the Authority Server's certificate changed intentionally (e.g. a new TLS key, not just a renewal) or something else is answering at that address, at ${uiUrl()}. Nothing was sent under the unrecognized certificate.`;
|
|
2076
|
+
}
|
|
1812
2077
|
function asUrlChangedNotice(what) {
|
|
1813
2078
|
return `${what} Suveren gateway is LOCKED because the Authority Server it points at has changed since it last signed in. Cached mandates from the old server were cleared.
|
|
1814
2079
|
|
|
@@ -1821,6 +2086,8 @@ function lockedNotice(action, reason = "restart") {
|
|
|
1821
2086
|
return expiredSessionNotice(what);
|
|
1822
2087
|
case "as-key-mismatch":
|
|
1823
2088
|
return asKeyMismatchNotice(what);
|
|
2089
|
+
case "as-tls-mismatch":
|
|
2090
|
+
return asTlsMismatchNotice(what);
|
|
1824
2091
|
case "as-url-changed":
|
|
1825
2092
|
return asUrlChangedNotice(what);
|
|
1826
2093
|
default:
|
|
@@ -1989,6 +2256,11 @@ async function verifyTicket(cache, receipt, expected) {
|
|
|
1989
2256
|
`Ticket proposalId "${String(receipt.proposalId)}" does not match the proposal being executed "${expected.proposalId}" \u2014 refusing to execute.`
|
|
1990
2257
|
);
|
|
1991
2258
|
}
|
|
2259
|
+
if (expected.idempotencyKey !== void 0 && receipt.idempotencyKey !== expected.idempotencyKey) {
|
|
2260
|
+
throw new TicketBindingMismatchError(
|
|
2261
|
+
`Ticket idempotencyKey "${String(receipt.idempotencyKey)}" does not match the one this call sent "${expected.idempotencyKey}" \u2014 refusing to execute.`
|
|
2262
|
+
);
|
|
2263
|
+
}
|
|
1992
2264
|
if (expected.contentHash !== void 0) {
|
|
1993
2265
|
if (receipt.contentHash !== expected.contentHash) {
|
|
1994
2266
|
throw new TicketBindingMismatchError(
|
|
@@ -2766,6 +3038,7 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
2766
3038
|
try {
|
|
2767
3039
|
const actionType = declaredActionType;
|
|
2768
3040
|
const binding = computeContentBinding(auth.profileId, tool, args, actionType);
|
|
3041
|
+
const idempotencyKey = randomUUID();
|
|
2769
3042
|
const { receipt } = await state2.spClient.postReceipt({
|
|
2770
3043
|
authorizationId: authzId,
|
|
2771
3044
|
// Optional cross-check — the AS fails closed on a mismatch.
|
|
@@ -2775,7 +3048,7 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
2775
3048
|
actionType,
|
|
2776
3049
|
executionContext: { ...execution },
|
|
2777
3050
|
amount: typeof execution.amount === "number" ? execution.amount : void 0,
|
|
2778
|
-
idempotencyKey
|
|
3051
|
+
idempotencyKey,
|
|
2779
3052
|
// Privacy: send the hash and how to reproduce it — never the
|
|
2780
3053
|
// preimage. `binding` also carries `boundContent`, the plaintext
|
|
2781
3054
|
// that was hashed (an email's to/cc/subject/body); it stays on
|
|
@@ -2789,6 +3062,7 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
2789
3062
|
executionContext: { ...execution },
|
|
2790
3063
|
authorizationId: authzId,
|
|
2791
3064
|
profileId: auth.profileId,
|
|
3065
|
+
idempotencyKey,
|
|
2792
3066
|
contentHash: binding?.contentHash,
|
|
2793
3067
|
contentBinding: binding?.contentBinding
|
|
2794
3068
|
});
|
|
@@ -3750,16 +4024,16 @@ function verifyGateContentHashes(content, auth) {
|
|
|
3750
4024
|
}
|
|
3751
4025
|
|
|
3752
4026
|
// src/lib/integration-registry.ts
|
|
3753
|
-
import { mkdirSync as
|
|
4027
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync11 } from "fs";
|
|
3754
4028
|
import { homedir as homedir8 } from "os";
|
|
3755
|
-
import { dirname as
|
|
3756
|
-
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
4029
|
+
import { dirname as dirname9, join as join11 } from "path";
|
|
4030
|
+
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join11(homedir8(), ".suveren");
|
|
3757
4031
|
var IntegrationRegistry = class {
|
|
3758
4032
|
integrations = /* @__PURE__ */ new Map();
|
|
3759
4033
|
filePath;
|
|
3760
4034
|
constructor(dataDir2) {
|
|
3761
4035
|
const dir = dataDir2 ?? DEFAULT_DATA_DIR;
|
|
3762
|
-
this.filePath =
|
|
4036
|
+
this.filePath = join11(dir, "integrations.json");
|
|
3763
4037
|
this.load();
|
|
3764
4038
|
}
|
|
3765
4039
|
// ─── CRUD ───────────────────────────────────────────────────────────────
|
|
@@ -3790,12 +4064,12 @@ var IntegrationRegistry = class {
|
|
|
3790
4064
|
}
|
|
3791
4065
|
// ─── Load / Save ────────────────────────────────────────────────────────
|
|
3792
4066
|
load() {
|
|
3793
|
-
if (!
|
|
3794
|
-
|
|
4067
|
+
if (!existsSync11(this.filePath)) {
|
|
4068
|
+
mkdirSync9(dirname9(this.filePath), { recursive: true });
|
|
3795
4069
|
return;
|
|
3796
4070
|
}
|
|
3797
4071
|
try {
|
|
3798
|
-
const raw =
|
|
4072
|
+
const raw = readFileSync12(this.filePath, "utf-8");
|
|
3799
4073
|
const data = JSON.parse(raw);
|
|
3800
4074
|
const migrated = data.integrations.map((i) => {
|
|
3801
4075
|
if ("toolGating" in i && !("profile" in i)) {
|
|
@@ -3824,45 +4098,45 @@ var IntegrationRegistry = class {
|
|
|
3824
4098
|
version: 1,
|
|
3825
4099
|
integrations: Array.from(this.integrations.values())
|
|
3826
4100
|
};
|
|
3827
|
-
|
|
3828
|
-
|
|
4101
|
+
mkdirSync9(dirname9(this.filePath), { recursive: true });
|
|
4102
|
+
writeFileSync9(this.filePath, JSON.stringify(data, null, 2), { encoding: "utf-8" });
|
|
3829
4103
|
}
|
|
3830
4104
|
};
|
|
3831
4105
|
|
|
3832
4106
|
// src/lib/integration-manager.ts
|
|
3833
4107
|
import { homedir as homedir9 } from "os";
|
|
3834
|
-
import { join as
|
|
3835
|
-
import { existsSync as
|
|
4108
|
+
import { join as join13, delimiter } from "path";
|
|
4109
|
+
import { existsSync as existsSync13, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync14, rmSync } from "fs";
|
|
3836
4110
|
import { execFile } from "child_process";
|
|
3837
4111
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3838
4112
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3839
4113
|
import { getProfile as getProfile5 } from "@hap/core";
|
|
3840
4114
|
|
|
3841
4115
|
// src/lib/manifest-loader.ts
|
|
3842
|
-
import { readFileSync as
|
|
3843
|
-
import { join as
|
|
4116
|
+
import { readFileSync as readFileSync13, existsSync as existsSync12 } from "fs";
|
|
4117
|
+
import { join as join12, resolve } from "path";
|
|
3844
4118
|
var manifests = /* @__PURE__ */ new Map();
|
|
3845
4119
|
function loadManifests(integrationsDir) {
|
|
3846
4120
|
const dir = resolve(
|
|
3847
|
-
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ??
|
|
4121
|
+
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join12(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
|
|
3848
4122
|
);
|
|
3849
|
-
const indexPath =
|
|
3850
|
-
if (!
|
|
4123
|
+
const indexPath = join12(dir, "index.json");
|
|
4124
|
+
if (!existsSync12(indexPath)) {
|
|
3851
4125
|
console.error(`[ManifestLoader] No index.json found at ${indexPath}, skipping manifest loading`);
|
|
3852
4126
|
return 0;
|
|
3853
4127
|
}
|
|
3854
4128
|
let index;
|
|
3855
4129
|
try {
|
|
3856
|
-
index = JSON.parse(
|
|
4130
|
+
index = JSON.parse(readFileSync13(indexPath, "utf-8"));
|
|
3857
4131
|
} catch (err) {
|
|
3858
4132
|
console.error(`[ManifestLoader] Failed to parse ${indexPath}:`, err);
|
|
3859
4133
|
return 0;
|
|
3860
4134
|
}
|
|
3861
4135
|
let loaded = 0;
|
|
3862
4136
|
for (const [id, relativePath] of Object.entries(index.integrations)) {
|
|
3863
|
-
const manifestPath =
|
|
4137
|
+
const manifestPath = join12(dir, relativePath);
|
|
3864
4138
|
try {
|
|
3865
|
-
const manifest = JSON.parse(
|
|
4139
|
+
const manifest = JSON.parse(readFileSync13(manifestPath, "utf-8"));
|
|
3866
4140
|
manifests.set(id, manifest);
|
|
3867
4141
|
loaded++;
|
|
3868
4142
|
} catch (err) {
|
|
@@ -3950,26 +4224,26 @@ function safeHost(url) {
|
|
|
3950
4224
|
}
|
|
3951
4225
|
|
|
3952
4226
|
// src/lib/integration-manager.ts
|
|
3953
|
-
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ??
|
|
3954
|
-
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ??
|
|
4227
|
+
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join13(homedir9(), ".suveren");
|
|
4228
|
+
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ?? join13(DEFAULT_DATA_DIR2, "integrations");
|
|
3955
4229
|
var installLock = Promise.resolve();
|
|
3956
4230
|
function withInstallLock(task) {
|
|
3957
4231
|
const run = installLock.then(task, task);
|
|
3958
4232
|
installLock = run.then(() => void 0, () => void 0);
|
|
3959
4233
|
return run;
|
|
3960
4234
|
}
|
|
3961
|
-
var INTEGRATIONS_BIN =
|
|
4235
|
+
var INTEGRATIONS_BIN = join13(INTEGRATIONS_DIR, "node_modules", ".bin");
|
|
3962
4236
|
function buildPath() {
|
|
3963
4237
|
const base = process.env.PATH ?? "";
|
|
3964
4238
|
return [INTEGRATIONS_BIN, base].join(delimiter);
|
|
3965
4239
|
}
|
|
3966
4240
|
function ensureIntegrationsDir() {
|
|
3967
|
-
if (!
|
|
3968
|
-
|
|
4241
|
+
if (!existsSync13(INTEGRATIONS_DIR)) {
|
|
4242
|
+
mkdirSync10(INTEGRATIONS_DIR, { recursive: true });
|
|
3969
4243
|
}
|
|
3970
|
-
const pkgPath =
|
|
3971
|
-
if (!
|
|
3972
|
-
|
|
4244
|
+
const pkgPath = join13(INTEGRATIONS_DIR, "package.json");
|
|
4245
|
+
if (!existsSync13(pkgPath)) {
|
|
4246
|
+
writeFileSync10(pkgPath, JSON.stringify({ name: "suveren-integrations", version: "1.0.0", private: true }, null, 2));
|
|
3973
4247
|
}
|
|
3974
4248
|
}
|
|
3975
4249
|
function readAgeOf(config) {
|
|
@@ -4035,19 +4309,19 @@ var IntegrationManager = class {
|
|
|
4035
4309
|
* exist, and treat anything else as not-installed (and reinstallable).
|
|
4036
4310
|
*/
|
|
4037
4311
|
isUsableInstall(npmPackage) {
|
|
4038
|
-
const pkgDir =
|
|
4039
|
-
const pkgJson =
|
|
4040
|
-
if (!
|
|
4312
|
+
const pkgDir = join13(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
4313
|
+
const pkgJson = join13(pkgDir, "package.json");
|
|
4314
|
+
if (!existsSync13(pkgDir) || !existsSync13(pkgJson)) return false;
|
|
4041
4315
|
try {
|
|
4042
|
-
const pkg = JSON.parse(
|
|
4316
|
+
const pkg = JSON.parse(readFileSync14(pkgJson, "utf8"));
|
|
4043
4317
|
const defaultBinName = (pkg.name ?? npmPackage).split("/").pop() ?? "";
|
|
4044
4318
|
const binMap = typeof pkg.bin === "string" ? { [defaultBinName]: pkg.bin } : pkg.bin ?? {};
|
|
4045
4319
|
const binEntries = Object.entries(binMap);
|
|
4046
4320
|
for (const [binName, target] of binEntries) {
|
|
4047
|
-
if (!
|
|
4321
|
+
if (!existsSync13(join13(pkgDir, target))) return false;
|
|
4048
4322
|
if (!this.binShimExists(binName)) return false;
|
|
4049
4323
|
}
|
|
4050
|
-
if (binEntries.length === 0 && pkg.main && !
|
|
4324
|
+
if (binEntries.length === 0 && pkg.main && !existsSync13(join13(pkgDir, pkg.main))) return false;
|
|
4051
4325
|
return true;
|
|
4052
4326
|
} catch {
|
|
4053
4327
|
return false;
|
|
@@ -4061,7 +4335,7 @@ var IntegrationManager = class {
|
|
|
4061
4335
|
binShimExists(binName) {
|
|
4062
4336
|
if (!binName) return false;
|
|
4063
4337
|
const candidates = process.platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.CMD`, `${binName}.ps1`] : [binName];
|
|
4064
|
-
return candidates.some((c) =>
|
|
4338
|
+
return candidates.some((c) => existsSync13(join13(INTEGRATIONS_BIN, c)));
|
|
4065
4339
|
}
|
|
4066
4340
|
/**
|
|
4067
4341
|
* Install an npm package into the managed integrations directory if not
|
|
@@ -4085,8 +4359,8 @@ var IntegrationManager = class {
|
|
|
4085
4359
|
}
|
|
4086
4360
|
/** The actual install. Callers MUST hold the install lock. */
|
|
4087
4361
|
async installNow(npmPackage) {
|
|
4088
|
-
const pkgDir =
|
|
4089
|
-
if (
|
|
4362
|
+
const pkgDir = join13(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
4363
|
+
if (existsSync13(pkgDir)) {
|
|
4090
4364
|
console.error(`[IntegrationManager] Removing unusable install of ${npmPackage}`);
|
|
4091
4365
|
try {
|
|
4092
4366
|
rmSync(pkgDir, { recursive: true, force: true });
|
|
@@ -4521,28 +4795,28 @@ var IntegrationManager = class {
|
|
|
4521
4795
|
};
|
|
4522
4796
|
|
|
4523
4797
|
// src/lib/profile-loader.ts
|
|
4524
|
-
import { readFileSync as
|
|
4525
|
-
import { join as
|
|
4798
|
+
import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
|
|
4799
|
+
import { join as join14, resolve as resolve2 } from "path";
|
|
4526
4800
|
import { registerProfile } from "@hap/core";
|
|
4527
4801
|
function loadProfiles(profilesDir) {
|
|
4528
|
-
const dir = resolve2(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ??
|
|
4529
|
-
const indexPath =
|
|
4530
|
-
if (!
|
|
4802
|
+
const dir = resolve2(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ?? join14(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "..", "hap-profiles"));
|
|
4803
|
+
const indexPath = join14(dir, "index.json");
|
|
4804
|
+
if (!existsSync14(indexPath)) {
|
|
4531
4805
|
warnNoProfiles(`No index.json at ${indexPath}`);
|
|
4532
4806
|
return 0;
|
|
4533
4807
|
}
|
|
4534
4808
|
let index;
|
|
4535
4809
|
try {
|
|
4536
|
-
index = JSON.parse(
|
|
4810
|
+
index = JSON.parse(readFileSync15(indexPath, "utf-8"));
|
|
4537
4811
|
} catch (err) {
|
|
4538
4812
|
console.error(`[ProfileLoader] Failed to parse ${indexPath}:`, err);
|
|
4539
4813
|
return 0;
|
|
4540
4814
|
}
|
|
4541
4815
|
let loaded = 0;
|
|
4542
4816
|
for (const [profileId, relativePath] of Object.entries(index.profiles)) {
|
|
4543
|
-
const profilePath =
|
|
4817
|
+
const profilePath = join14(dir, relativePath);
|
|
4544
4818
|
try {
|
|
4545
|
-
const profile = JSON.parse(
|
|
4819
|
+
const profile = JSON.parse(readFileSync15(profilePath, "utf-8"));
|
|
4546
4820
|
registerProfile(profileId, profile);
|
|
4547
4821
|
const shortName = profileId.split("/").pop()?.replace(/@.*$/, "");
|
|
4548
4822
|
if (shortName && shortName !== profileId) {
|
|
@@ -4578,8 +4852,8 @@ function warnNoProfiles(reason) {
|
|
|
4578
4852
|
import { decodeAttestationBlob as decodeAttestationBlob3 } from "@hap/core";
|
|
4579
4853
|
|
|
4580
4854
|
// src/lib/committed-executor.ts
|
|
4581
|
-
import { existsSync as
|
|
4582
|
-
import { dirname as
|
|
4855
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync16, unlinkSync as unlinkSync6, writeFileSync as writeFileSync11 } from "fs";
|
|
4856
|
+
import { dirname as dirname10, join as join15 } from "path";
|
|
4583
4857
|
var CommittedExecutor = class {
|
|
4584
4858
|
constructor(run) {
|
|
4585
4859
|
this.run = run;
|
|
@@ -4607,7 +4881,7 @@ var ExecutorLock = class {
|
|
|
4607
4881
|
path;
|
|
4608
4882
|
held = false;
|
|
4609
4883
|
constructor(dataDir2) {
|
|
4610
|
-
this.path =
|
|
4884
|
+
this.path = join15(dataDir2, "executor.lock");
|
|
4611
4885
|
}
|
|
4612
4886
|
/** Try to take the lock. Returns the holder's pid when someone else has it. */
|
|
4613
4887
|
acquire() {
|
|
@@ -4615,9 +4889,9 @@ var ExecutorLock = class {
|
|
|
4615
4889
|
if (current && current.pid !== process.pid && isAlive(current.pid)) {
|
|
4616
4890
|
return { held: false, holderPid: current.pid };
|
|
4617
4891
|
}
|
|
4618
|
-
|
|
4892
|
+
mkdirSync11(dirname10(this.path), { recursive: true, mode: 448 });
|
|
4619
4893
|
const lock = { pid: process.pid, startedAt: Math.floor(Date.now() / 1e3) };
|
|
4620
|
-
|
|
4894
|
+
writeFileSync11(this.path, JSON.stringify(lock), { mode: 384 });
|
|
4621
4895
|
this.held = true;
|
|
4622
4896
|
return { held: true };
|
|
4623
4897
|
}
|
|
@@ -4639,9 +4913,9 @@ var ExecutorLock = class {
|
|
|
4639
4913
|
}
|
|
4640
4914
|
}
|
|
4641
4915
|
read() {
|
|
4642
|
-
if (!
|
|
4916
|
+
if (!existsSync15(this.path)) return null;
|
|
4643
4917
|
try {
|
|
4644
|
-
const parsed = JSON.parse(
|
|
4918
|
+
const parsed = JSON.parse(readFileSync16(this.path, "utf8"));
|
|
4645
4919
|
return typeof parsed?.pid === "number" ? parsed : null;
|
|
4646
4920
|
} catch {
|
|
4647
4921
|
return null;
|
|
@@ -4661,76 +4935,8 @@ function isAlive(pid) {
|
|
|
4661
4935
|
import { homedir as homedir10 } from "os";
|
|
4662
4936
|
import { join as join17 } from "path";
|
|
4663
4937
|
|
|
4664
|
-
// src/lib/as-config.ts
|
|
4665
|
-
import { existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync15, writeFileSync as writeFileSync11 } from "fs";
|
|
4666
|
-
import { dirname as dirname10, join as join15 } from "path";
|
|
4667
|
-
var DEFAULT_AS_URL = "https://www.suveren.ai";
|
|
4668
|
-
function configPath(dataDir2) {
|
|
4669
|
-
return join15(dataDir2, "config.json");
|
|
4670
|
-
}
|
|
4671
|
-
function readAsConfigStrict(dataDir2) {
|
|
4672
|
-
const path = configPath(dataDir2);
|
|
4673
|
-
if (!existsSync15(path)) return {};
|
|
4674
|
-
let data;
|
|
4675
|
-
try {
|
|
4676
|
-
data = JSON.parse(readFileSync15(path, "utf-8"));
|
|
4677
|
-
} catch (err) {
|
|
4678
|
-
throw new Error(
|
|
4679
|
-
`Could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
4680
|
-
);
|
|
4681
|
-
}
|
|
4682
|
-
const out = {};
|
|
4683
|
-
if (typeof data.asUrl === "string") out.asUrl = data.asUrl;
|
|
4684
|
-
if (typeof data.caFile === "string") out.caFile = data.caFile;
|
|
4685
|
-
return out;
|
|
4686
|
-
}
|
|
4687
|
-
function validateAsUrl(candidate) {
|
|
4688
|
-
const trimmed = candidate.trim();
|
|
4689
|
-
if (!trimmed) return { ok: false, error: "The Authority Server URL is empty." };
|
|
4690
|
-
let parsed;
|
|
4691
|
-
try {
|
|
4692
|
-
parsed = new URL(trimmed);
|
|
4693
|
-
} catch {
|
|
4694
|
-
return { ok: false, error: `"${trimmed}" is not a valid URL.` };
|
|
4695
|
-
}
|
|
4696
|
-
const isLocalHttp = parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1");
|
|
4697
|
-
if (parsed.protocol !== "https:" && !isLocalHttp) {
|
|
4698
|
-
return {
|
|
4699
|
-
ok: false,
|
|
4700
|
-
error: `"${trimmed}" must use https:// (http:// is only allowed for http://localhost or http://127.0.0.1).`
|
|
4701
|
-
};
|
|
4702
|
-
}
|
|
4703
|
-
let normalized = `${parsed.origin}${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
4704
|
-
if (normalized.endsWith("/") && normalized !== `${parsed.origin}/`) {
|
|
4705
|
-
normalized = normalized.slice(0, -1);
|
|
4706
|
-
} else if (normalized === `${parsed.origin}/`) {
|
|
4707
|
-
normalized = parsed.origin;
|
|
4708
|
-
}
|
|
4709
|
-
return { ok: true, url: normalized };
|
|
4710
|
-
}
|
|
4711
|
-
function resolveAsUrl(dataDir2, flag) {
|
|
4712
|
-
if (flag) {
|
|
4713
|
-
const v = validateAsUrl(flag);
|
|
4714
|
-
if (!v.ok) throw new Error(`Invalid --as-url: ${v.error}`);
|
|
4715
|
-
return v.url;
|
|
4716
|
-
}
|
|
4717
|
-
const envUrl = process.env.SUVEREN_AS_URL;
|
|
4718
|
-
if (envUrl) {
|
|
4719
|
-
const v = validateAsUrl(envUrl);
|
|
4720
|
-
if (!v.ok) throw new Error(`Invalid SUVEREN_AS_URL: ${v.error}`);
|
|
4721
|
-
return v.url;
|
|
4722
|
-
}
|
|
4723
|
-
const saved = readAsConfigStrict(dataDir2).asUrl;
|
|
4724
|
-
if (saved) {
|
|
4725
|
-
const v = validateAsUrl(saved);
|
|
4726
|
-
if (!v.ok) throw new Error(`Invalid saved as-url "${saved}" in ${dataDir2}/config.json: ${v.error}`);
|
|
4727
|
-
return v.url;
|
|
4728
|
-
}
|
|
4729
|
-
return DEFAULT_AS_URL;
|
|
4730
|
-
}
|
|
4731
|
-
|
|
4732
4938
|
// src/lib/mcp-as-tracker.ts
|
|
4733
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync12, readFileSync as
|
|
4939
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync12, readFileSync as readFileSync17, writeFileSync as writeFileSync12 } from "fs";
|
|
4734
4940
|
import { dirname as dirname11, join as join16 } from "path";
|
|
4735
4941
|
function trackerPath(dataDir2) {
|
|
4736
4942
|
return join16(dataDir2, "mcp-as-pairing.json");
|
|
@@ -4739,7 +4945,7 @@ function readMcpPairedAsUrl(dataDir2) {
|
|
|
4739
4945
|
const path = trackerPath(dataDir2);
|
|
4740
4946
|
if (!existsSync16(path)) return null;
|
|
4741
4947
|
try {
|
|
4742
|
-
const data = JSON.parse(
|
|
4948
|
+
const data = JSON.parse(readFileSync17(path, "utf-8"));
|
|
4743
4949
|
return typeof data.asUrl === "string" ? data.asUrl : null;
|
|
4744
4950
|
} catch {
|
|
4745
4951
|
return null;
|
|
@@ -4754,6 +4960,7 @@ function writeMcpPairedAsUrl(dataDir2, asUrl) {
|
|
|
4754
4960
|
// bin/http.ts
|
|
4755
4961
|
var dataDir = process.env.SUVEREN_DATA_DIR ?? join17(homedir10(), ".suveren");
|
|
4756
4962
|
var spUrl = resolveAsUrl(dataDir);
|
|
4963
|
+
validatePinTlsForUrl(resolvePinTls(dataDir), spUrl);
|
|
4757
4964
|
var port = parseInt(process.env.SUVEREN_MCP_PORT ?? "3430", 10);
|
|
4758
4965
|
setAsBaseUrl(spUrl);
|
|
4759
4966
|
var state = new SharedState(spUrl, void 0, dataDir);
|
|
@@ -4838,7 +5045,7 @@ app.post("/internal/configure", internalOnly, (req, res) => {
|
|
|
4838
5045
|
});
|
|
4839
5046
|
app.post("/internal/clear-session", internalOnly, (req, res) => {
|
|
4840
5047
|
const reason = req.body?.reason;
|
|
4841
|
-
const validReason = reason === "as-key-mismatch" || reason === "as-url-changed" ? reason : void 0;
|
|
5048
|
+
const validReason = reason === "as-key-mismatch" || reason === "as-tls-mismatch" || reason === "as-url-changed" ? reason : void 0;
|
|
4842
5049
|
state.spClient.clearSession(validReason);
|
|
4843
5050
|
console.error(`[Suveren MCP] Session cleared by control-plane (${validReason ?? "expired"})`);
|
|
4844
5051
|
res.json({ ok: true });
|