@suveren/gateway 0.12.0 → 0.13.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 +34 -0
- package/bin/suveren-gateway.js +128 -12
- package/dist/control-plane/index.mjs +555 -122
- package/dist/mcp-server/http.mjs +487 -157
- package/dist/ui/assets/{index-BzcgeHlq.js → index-CmLqzgqA.js} +21 -21
- package/dist/ui/assets/{index-BFOprnFZ.css → index-y1OFeYnS.css} +1 -1
- package/dist/ui/index.html +2 -2
- package/lib/autostart-templates.d.mts +6 -0
- package/lib/autostart-templates.mjs +21 -4
- package/lib/config.d.mts +10 -0
- package/lib/config.mjs +79 -18
- package/lib/policy.d.mts +34 -0
- package/lib/policy.mjs +369 -0
- package/package.json +1 -1
- package/server.js +51 -8
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -142,18 +142,237 @@ function fingerprintOf(publicKeyHex) {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
// src/lib/as-config.ts
|
|
145
|
-
import { existsSync as
|
|
146
|
-
import { dirname as dirname2, join as
|
|
145
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
146
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
147
|
+
|
|
148
|
+
// src/lib/policy.ts
|
|
149
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
150
|
+
import { join as join3 } from "path";
|
|
151
|
+
import { spawnSync } from "child_process";
|
|
152
|
+
var POLICY_KEYS = ["AsUrl", "CaFile", "PinTls", "Proxy", "NoProxy", "Simulation", "InstallMethod"];
|
|
153
|
+
var DEFAULT_REGISTRY_BASE_KEY = "SOFTWARE\\Policies\\Suveren\\Gateway";
|
|
154
|
+
function registryKeyPath(hive) {
|
|
155
|
+
const baseKey = process.env.SUVEREN_POLICY_REGISTRY_KEY || DEFAULT_REGISTRY_BASE_KEY;
|
|
156
|
+
return `${hive}\\${baseKey}`;
|
|
157
|
+
}
|
|
158
|
+
function validateAsUrl(candidate) {
|
|
159
|
+
const trimmed = candidate.trim();
|
|
160
|
+
if (!trimmed) return { ok: false, error: "The Authority Server URL is empty." };
|
|
161
|
+
let parsed;
|
|
162
|
+
try {
|
|
163
|
+
parsed = new URL(trimmed);
|
|
164
|
+
} catch {
|
|
165
|
+
return { ok: false, error: `"${trimmed}" is not a valid URL.` };
|
|
166
|
+
}
|
|
167
|
+
const isLocalHttp = parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1");
|
|
168
|
+
if (parsed.protocol !== "https:" && !isLocalHttp) {
|
|
169
|
+
return {
|
|
170
|
+
ok: false,
|
|
171
|
+
error: `"${trimmed}" must use https:// (http:// is only allowed for http://localhost or http://127.0.0.1).`
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
let normalized = `${parsed.origin}${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
175
|
+
if (normalized.endsWith("/") && normalized !== `${parsed.origin}/`) {
|
|
176
|
+
normalized = normalized.slice(0, -1);
|
|
177
|
+
} else if (normalized === `${parsed.origin}/`) {
|
|
178
|
+
normalized = parsed.origin;
|
|
179
|
+
}
|
|
180
|
+
return { ok: true, url: normalized };
|
|
181
|
+
}
|
|
182
|
+
function validateCaFile(candidate) {
|
|
183
|
+
const trimmed = candidate.trim();
|
|
184
|
+
if (!trimmed) return { ok: false, error: "The CA file path is empty." };
|
|
185
|
+
if (!existsSync3(trimmed)) return { ok: false, error: `No file at "${trimmed}".` };
|
|
186
|
+
return { ok: true, path: trimmed };
|
|
187
|
+
}
|
|
188
|
+
function validateProxyUrl(candidate) {
|
|
189
|
+
const trimmed = candidate.trim();
|
|
190
|
+
if (!trimmed) return { ok: false, error: "The proxy URL is empty." };
|
|
191
|
+
let parsed;
|
|
192
|
+
try {
|
|
193
|
+
parsed = new URL(trimmed);
|
|
194
|
+
} catch {
|
|
195
|
+
return { ok: false, error: `"${trimmed}" is not a valid URL.` };
|
|
196
|
+
}
|
|
197
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
198
|
+
return { ok: false, error: `"${trimmed}" must use http:// or https://.` };
|
|
199
|
+
}
|
|
200
|
+
return { ok: true, url: trimmed };
|
|
201
|
+
}
|
|
202
|
+
function coerceBoolean(value) {
|
|
203
|
+
if (typeof value === "boolean") return value;
|
|
204
|
+
if (typeof value === "number") {
|
|
205
|
+
if (value === 0) return false;
|
|
206
|
+
if (value === 1) return true;
|
|
207
|
+
return void 0;
|
|
208
|
+
}
|
|
209
|
+
if (typeof value === "string") {
|
|
210
|
+
const v = value.trim().toLowerCase();
|
|
211
|
+
if (v === "true" || v === "1") return true;
|
|
212
|
+
if (v === "false" || v === "0") return false;
|
|
213
|
+
}
|
|
214
|
+
return void 0;
|
|
215
|
+
}
|
|
216
|
+
function parseRegQueryOutput(stdout) {
|
|
217
|
+
const out = {};
|
|
218
|
+
const lineRe = /^ {4}(\S+)\s+(REG_SZ|REG_EXPAND_SZ|REG_DWORD|REG_QWORD|REG_BINARY|REG_MULTI_SZ)\s+(.*)$/;
|
|
219
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
220
|
+
const m = lineRe.exec(line);
|
|
221
|
+
if (!m) continue;
|
|
222
|
+
const [, name, type, rawValue] = m;
|
|
223
|
+
if (!POLICY_KEYS.includes(name)) continue;
|
|
224
|
+
const key = name;
|
|
225
|
+
if (type === "REG_DWORD" || type === "REG_QWORD") {
|
|
226
|
+
out[key] = parseInt(rawValue, 16);
|
|
227
|
+
} else {
|
|
228
|
+
out[key] = rawValue;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
233
|
+
function readRegistryHive(hive) {
|
|
234
|
+
const path = registryKeyPath(hive);
|
|
235
|
+
const source = `the registry policy (${path})`;
|
|
236
|
+
if (process.env.SUVEREN_POLICY_REGISTRY === "off") return { values: {}, source };
|
|
237
|
+
if (process.platform !== "win32") return { values: {}, source };
|
|
238
|
+
let result;
|
|
239
|
+
try {
|
|
240
|
+
result = spawnSync("reg", ["query", path], { encoding: "utf8" });
|
|
241
|
+
} catch {
|
|
242
|
+
return { values: {}, source };
|
|
243
|
+
}
|
|
244
|
+
if (!result || result.status !== 0 || !result.stdout) return { values: {}, source };
|
|
245
|
+
return { values: parseRegQueryOutput(result.stdout), source };
|
|
246
|
+
}
|
|
247
|
+
function policyFilePath() {
|
|
248
|
+
if (process.env.SUVEREN_POLICY_FILE) return process.env.SUVEREN_POLICY_FILE;
|
|
249
|
+
if (process.platform === "win32") {
|
|
250
|
+
const programData = process.env.ProgramData || process.env.PROGRAMDATA || "C:\\ProgramData";
|
|
251
|
+
return join3(programData, "Suveren", "gateway-policy.json");
|
|
252
|
+
}
|
|
253
|
+
if (process.platform === "darwin") {
|
|
254
|
+
return "/Library/Application Support/Suveren/gateway-policy.json";
|
|
255
|
+
}
|
|
256
|
+
return "/etc/suveren/gateway-policy.json";
|
|
257
|
+
}
|
|
258
|
+
function readPolicyFile() {
|
|
259
|
+
const path = policyFilePath();
|
|
260
|
+
const source = `the policy file (${path})`;
|
|
261
|
+
if (!existsSync3(path)) return { values: {}, source };
|
|
262
|
+
let data;
|
|
263
|
+
try {
|
|
264
|
+
data = JSON.parse(readFileSync3(path, "utf8"));
|
|
265
|
+
} catch (err) {
|
|
266
|
+
throw new Error(`Could not parse policy file ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
267
|
+
}
|
|
268
|
+
const values = {};
|
|
269
|
+
for (const key of POLICY_KEYS) {
|
|
270
|
+
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
271
|
+
values[key] = data[key];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return { values, source };
|
|
275
|
+
}
|
|
276
|
+
function mergeBySource(entries) {
|
|
277
|
+
const merged = {};
|
|
278
|
+
const sourceOf = {};
|
|
279
|
+
for (const key of POLICY_KEYS) {
|
|
280
|
+
for (const entry of entries) {
|
|
281
|
+
if (Object.prototype.hasOwnProperty.call(entry.values, key) && entry.values[key] !== void 0) {
|
|
282
|
+
merged[key] = entry.values[key];
|
|
283
|
+
sourceOf[key] = entry.source;
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return { merged, sourceOf };
|
|
289
|
+
}
|
|
290
|
+
var cached = null;
|
|
291
|
+
function readPolicy() {
|
|
292
|
+
if (cached) return cached;
|
|
293
|
+
const hklm = readRegistryHive("HKLM");
|
|
294
|
+
const hkcu = readRegistryHive("HKCU");
|
|
295
|
+
const file = readPolicyFile();
|
|
296
|
+
const { merged, sourceOf } = mergeBySource([hklm, hkcu, file]);
|
|
297
|
+
const policy = {};
|
|
298
|
+
const locked = /* @__PURE__ */ new Set();
|
|
299
|
+
if (merged.AsUrl !== void 0) {
|
|
300
|
+
const v = validateAsUrl(String(merged.AsUrl));
|
|
301
|
+
if (!v.ok) {
|
|
302
|
+
throw new Error(`Invalid policy AsUrl ("${merged.AsUrl}") from ${sourceOf.AsUrl}: ${v.error}`);
|
|
303
|
+
}
|
|
304
|
+
policy.asUrl = v.url;
|
|
305
|
+
locked.add("asUrl");
|
|
306
|
+
}
|
|
307
|
+
if (merged.CaFile !== void 0) {
|
|
308
|
+
const v = validateCaFile(String(merged.CaFile));
|
|
309
|
+
if (!v.ok) {
|
|
310
|
+
throw new Error(`Invalid policy CaFile ("${merged.CaFile}") from ${sourceOf.CaFile}: ${v.error}`);
|
|
311
|
+
}
|
|
312
|
+
policy.caFile = v.path;
|
|
313
|
+
locked.add("caFile");
|
|
314
|
+
}
|
|
315
|
+
if (merged.PinTls !== void 0) {
|
|
316
|
+
const b = coerceBoolean(merged.PinTls);
|
|
317
|
+
if (b === void 0) {
|
|
318
|
+
throw new Error(
|
|
319
|
+
`Invalid policy PinTls ("${String(merged.PinTls)}") from ${sourceOf.PinTls}: must be a boolean or 0/1.`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
policy.pinTls = b;
|
|
323
|
+
locked.add("pinTls");
|
|
324
|
+
}
|
|
325
|
+
if (merged.Proxy !== void 0) {
|
|
326
|
+
const v = validateProxyUrl(String(merged.Proxy));
|
|
327
|
+
if (!v.ok) {
|
|
328
|
+
throw new Error(`Invalid policy Proxy ("${String(merged.Proxy)}") from ${sourceOf.Proxy}: ${v.error}`);
|
|
329
|
+
}
|
|
330
|
+
policy.proxy = v.url;
|
|
331
|
+
locked.add("proxy");
|
|
332
|
+
}
|
|
333
|
+
if (merged.NoProxy !== void 0) {
|
|
334
|
+
const raw = String(merged.NoProxy).trim();
|
|
335
|
+
if (!raw) {
|
|
336
|
+
throw new Error(`Invalid policy NoProxy ("${String(merged.NoProxy)}") from ${sourceOf.NoProxy}: must not be empty.`);
|
|
337
|
+
}
|
|
338
|
+
policy.noProxy = raw;
|
|
339
|
+
locked.add("noProxy");
|
|
340
|
+
}
|
|
341
|
+
if (merged.Simulation !== void 0) {
|
|
342
|
+
const b = coerceBoolean(merged.Simulation);
|
|
343
|
+
if (b === void 0) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
`Invalid policy Simulation ("${String(merged.Simulation)}") from ${sourceOf.Simulation}: must be a boolean or 0/1.`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
policy.simulation = b;
|
|
349
|
+
locked.add("simulation");
|
|
350
|
+
}
|
|
351
|
+
if (merged.InstallMethod !== void 0) {
|
|
352
|
+
const raw = String(merged.InstallMethod).trim().toLowerCase();
|
|
353
|
+
if (raw !== "managed") {
|
|
354
|
+
throw new Error(
|
|
355
|
+
`Invalid policy InstallMethod ("${String(merged.InstallMethod)}") from ${sourceOf.InstallMethod}: only "managed" is supported.`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
policy.installMethod = "managed";
|
|
359
|
+
locked.add("installMethod");
|
|
360
|
+
}
|
|
361
|
+
cached = { policy, locked };
|
|
362
|
+
return cached;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/lib/as-config.ts
|
|
147
366
|
var DEFAULT_AS_URL = "https://www.suveren.ai";
|
|
148
367
|
function configPath(dataDir2) {
|
|
149
|
-
return
|
|
368
|
+
return join4(dataDir2, "config.json");
|
|
150
369
|
}
|
|
151
370
|
function readAsConfigStrict(dataDir2) {
|
|
152
371
|
const path = configPath(dataDir2);
|
|
153
|
-
if (!
|
|
372
|
+
if (!existsSync4(path)) return {};
|
|
154
373
|
let data;
|
|
155
374
|
try {
|
|
156
|
-
data = JSON.parse(
|
|
375
|
+
data = JSON.parse(readFileSync4(path, "utf-8"));
|
|
157
376
|
} catch (err) {
|
|
158
377
|
throw new Error(
|
|
159
378
|
`Could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -168,9 +387,9 @@ function readAsConfigStrict(dataDir2) {
|
|
|
168
387
|
}
|
|
169
388
|
function readAsConfig(dataDir2) {
|
|
170
389
|
const path = configPath(dataDir2);
|
|
171
|
-
if (!
|
|
390
|
+
if (!existsSync4(path)) return {};
|
|
172
391
|
try {
|
|
173
|
-
const data = JSON.parse(
|
|
392
|
+
const data = JSON.parse(readFileSync4(path, "utf-8"));
|
|
174
393
|
const out = {};
|
|
175
394
|
if (typeof data.asUrl === "string") out.asUrl = data.asUrl;
|
|
176
395
|
if (typeof data.caFile === "string") out.caFile = data.caFile;
|
|
@@ -181,7 +400,7 @@ function readAsConfig(dataDir2) {
|
|
|
181
400
|
return {};
|
|
182
401
|
}
|
|
183
402
|
}
|
|
184
|
-
function
|
|
403
|
+
function validateAsUrl2(candidate) {
|
|
185
404
|
const trimmed = candidate.trim();
|
|
186
405
|
if (!trimmed) return { ok: false, error: "The Authority Server URL is empty." };
|
|
187
406
|
let parsed;
|
|
@@ -206,26 +425,30 @@ function validateAsUrl(candidate) {
|
|
|
206
425
|
return { ok: true, url: normalized };
|
|
207
426
|
}
|
|
208
427
|
function resolveAsUrl(dataDir2, flag) {
|
|
428
|
+
const policyUrl = readPolicy().policy.asUrl;
|
|
429
|
+
if (policyUrl) return policyUrl;
|
|
209
430
|
if (flag) {
|
|
210
|
-
const v =
|
|
431
|
+
const v = validateAsUrl2(flag);
|
|
211
432
|
if (!v.ok) throw new Error(`Invalid --as-url: ${v.error}`);
|
|
212
433
|
return v.url;
|
|
213
434
|
}
|
|
214
435
|
const envUrl = process.env.SUVEREN_AS_URL;
|
|
215
436
|
if (envUrl) {
|
|
216
|
-
const v =
|
|
437
|
+
const v = validateAsUrl2(envUrl);
|
|
217
438
|
if (!v.ok) throw new Error(`Invalid SUVEREN_AS_URL: ${v.error}`);
|
|
218
439
|
return v.url;
|
|
219
440
|
}
|
|
220
441
|
const saved = readAsConfigStrict(dataDir2).asUrl;
|
|
221
442
|
if (saved) {
|
|
222
|
-
const v =
|
|
443
|
+
const v = validateAsUrl2(saved);
|
|
223
444
|
if (!v.ok) throw new Error(`Invalid saved as-url "${saved}" in ${dataDir2}/config.json: ${v.error}`);
|
|
224
445
|
return v.url;
|
|
225
446
|
}
|
|
226
447
|
return DEFAULT_AS_URL;
|
|
227
448
|
}
|
|
228
449
|
function resolvePinTls(dataDir2) {
|
|
450
|
+
const policyPinTls = readPolicy().policy.pinTls;
|
|
451
|
+
if (policyPinTls !== void 0) return policyPinTls;
|
|
229
452
|
return readAsConfig(dataDir2).pinTls === true;
|
|
230
453
|
}
|
|
231
454
|
function validatePinTlsForUrl(pinTls, asUrl) {
|
|
@@ -240,14 +463,58 @@ function validatePinTlsForUrl(pinTls, asUrl) {
|
|
|
240
463
|
// src/lib/as-tls-pin.ts
|
|
241
464
|
import { createHash as createHash2 } from "crypto";
|
|
242
465
|
import { checkServerIdentity as defaultCheckServerIdentity, rootCertificates } from "tls";
|
|
466
|
+
import { Agent as HttpsAgent2 } from "https";
|
|
467
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
468
|
+
import { Agent, ProxyAgent, fetch as undiciFetch } from "undici";
|
|
469
|
+
|
|
470
|
+
// src/lib/proxy-env.ts
|
|
471
|
+
function isLoopbackHostname(hostname) {
|
|
472
|
+
const h = hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
473
|
+
if (h === "localhost") return true;
|
|
474
|
+
if (h === "::1" || h === "0:0:0:0:0:0:0:1") return true;
|
|
475
|
+
return /^127\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.test(h);
|
|
476
|
+
}
|
|
477
|
+
function readProxyEnv() {
|
|
478
|
+
const http = process.env.http_proxy || process.env.HTTP_PROXY || void 0;
|
|
479
|
+
const https = process.env.https_proxy || process.env.HTTPS_PROXY || void 0;
|
|
480
|
+
const noProxy = process.env.no_proxy ?? process.env.NO_PROXY ?? "";
|
|
481
|
+
return { http, https, noProxy };
|
|
482
|
+
}
|
|
483
|
+
function matchesNoProxy(hostname, port2, noProxy) {
|
|
484
|
+
const entries = noProxy.split(/[,\s]+/).filter(Boolean);
|
|
485
|
+
if (entries.length === 0) return false;
|
|
486
|
+
if (entries.includes("*")) return true;
|
|
487
|
+
const h = hostname.toLowerCase();
|
|
488
|
+
for (const raw of entries) {
|
|
489
|
+
const m = raw.match(/^(.+):(\d+)$/);
|
|
490
|
+
const entryHost = (m ? m[1] : raw).replace(/^\*?\./, "").toLowerCase();
|
|
491
|
+
const entryPort = m ? Number(m[2]) : 0;
|
|
492
|
+
if (entryPort && entryPort !== port2) continue;
|
|
493
|
+
if (h === entryHost || h.endsWith(`.${entryHost}`)) return true;
|
|
494
|
+
}
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
function selectProxyUrl(targetUrl) {
|
|
498
|
+
const url = typeof targetUrl === "string" ? new URL(targetUrl) : targetUrl;
|
|
499
|
+
if (isLoopbackHostname(url.hostname)) return void 0;
|
|
500
|
+
const { http, https, noProxy } = readProxyEnv();
|
|
501
|
+
const port2 = url.port ? Number(url.port) : url.protocol === "https:" ? 443 : 80;
|
|
502
|
+
if (matchesNoProxy(url.hostname, port2, noProxy)) return void 0;
|
|
503
|
+
return url.protocol === "https:" ? https ?? http : http;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// src/lib/proxy-https-agent.ts
|
|
243
507
|
import { Agent as HttpsAgent } from "https";
|
|
244
|
-
import {
|
|
245
|
-
import {
|
|
508
|
+
import { connect as netConnect } from "net";
|
|
509
|
+
import { connect as tlsConnect } from "tls";
|
|
510
|
+
var MAX_CONNECT_RESPONSE_BYTES = 64 * 1024;
|
|
511
|
+
|
|
512
|
+
// src/lib/as-tls-pin.ts
|
|
246
513
|
function effectiveCa() {
|
|
247
514
|
const path = process.env.NODE_EXTRA_CA_CERTS;
|
|
248
515
|
if (!path) return void 0;
|
|
249
516
|
try {
|
|
250
|
-
return [...rootCertificates,
|
|
517
|
+
return [...rootCertificates, readFileSync5(path, "utf-8")];
|
|
251
518
|
} catch {
|
|
252
519
|
return void 0;
|
|
253
520
|
}
|
|
@@ -302,24 +569,27 @@ function pinnedCheckServerIdentity(pinnedSpkiHex) {
|
|
|
302
569
|
return void 0;
|
|
303
570
|
};
|
|
304
571
|
}
|
|
305
|
-
function
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
572
|
+
function buildTlsDispatcher(url, connectOpts) {
|
|
573
|
+
const proxyUrl = selectProxyUrl(url);
|
|
574
|
+
if (proxyUrl) {
|
|
575
|
+
return new ProxyAgent({ uri: proxyUrl, requestTls: connectOpts });
|
|
576
|
+
}
|
|
577
|
+
return new Agent({ connect: connectOpts });
|
|
578
|
+
}
|
|
579
|
+
function buildPinnedDispatcher(pinnedSpkiHex, url) {
|
|
580
|
+
return buildTlsDispatcher(url, {
|
|
581
|
+
ca: effectiveCa(),
|
|
582
|
+
checkServerIdentity: pinnedCheckServerIdentity(pinnedSpkiHex)
|
|
311
583
|
});
|
|
312
584
|
}
|
|
313
|
-
function buildCapturingDispatcher() {
|
|
585
|
+
function buildCapturingDispatcher(url) {
|
|
314
586
|
let captured;
|
|
315
|
-
const dispatcher =
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
return defaultErr ? taggedCheckError(defaultErr) : void 0;
|
|
322
|
-
}
|
|
587
|
+
const dispatcher = buildTlsDispatcher(url, {
|
|
588
|
+
ca: effectiveCa(),
|
|
589
|
+
checkServerIdentity: (hostname, cert) => {
|
|
590
|
+
captured = cert;
|
|
591
|
+
const defaultErr = defaultCheckServerIdentity(hostname, cert);
|
|
592
|
+
return defaultErr ? taggedCheckError(defaultErr) : void 0;
|
|
323
593
|
}
|
|
324
594
|
});
|
|
325
595
|
return { dispatcher, getCapturedCert: () => captured };
|
|
@@ -330,6 +600,34 @@ function withBoundedSignal(init) {
|
|
|
330
600
|
const signal = init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout;
|
|
331
601
|
return { ...init, signal };
|
|
332
602
|
}
|
|
603
|
+
var UNKNOWN_ISSUER_CODES = /* @__PURE__ */ new Set([
|
|
604
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
605
|
+
"UNABLE_TO_GET_ISSUER_CERT",
|
|
606
|
+
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
|
607
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
608
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
609
|
+
"CERT_UNTRUSTED"
|
|
610
|
+
]);
|
|
611
|
+
function tlsErrorCode(err) {
|
|
612
|
+
if (!(err instanceof Error)) return void 0;
|
|
613
|
+
const cause = err.cause;
|
|
614
|
+
const withCode = cause instanceof Error ? cause : err;
|
|
615
|
+
return withCode.code;
|
|
616
|
+
}
|
|
617
|
+
function isUnknownIssuerError(err) {
|
|
618
|
+
const code = tlsErrorCode(err);
|
|
619
|
+
return !!code && UNKNOWN_ISSUER_CODES.has(code);
|
|
620
|
+
}
|
|
621
|
+
function rethrowWithIssuerHint(err, url) {
|
|
622
|
+
if (isUnknownIssuerError(err)) {
|
|
623
|
+
const hinted = new Error(
|
|
624
|
+
`Could not verify the certificate presented for ${url} \u2014 its issuer is not trusted (${tlsErrorCode(err)}). This is the typical signature of a company network that inspects HTTPS traffic with its own root certificate: trust it with \`suveren-gateway config set ca-file <path-to-company-root.pem>\` (or --ca-file / NODE_EXTRA_CA_CERTS), then try again. Original error: ${describeFetchError(err)}`
|
|
625
|
+
);
|
|
626
|
+
hinted.cause = err;
|
|
627
|
+
throw hinted;
|
|
628
|
+
}
|
|
629
|
+
throw err;
|
|
630
|
+
}
|
|
333
631
|
function closeAfter(dispatcher, pending) {
|
|
334
632
|
pending.catch(() => {
|
|
335
633
|
}).finally(() => {
|
|
@@ -339,8 +637,24 @@ function closeAfter(dispatcher, pending) {
|
|
|
339
637
|
}
|
|
340
638
|
async function fetchAs(url, init, pinning) {
|
|
341
639
|
if (!pinning.enforce && !pinning.capture) {
|
|
342
|
-
const
|
|
343
|
-
|
|
640
|
+
const proxyUrl = selectProxyUrl(url);
|
|
641
|
+
if (!proxyUrl) {
|
|
642
|
+
try {
|
|
643
|
+
const res2 = await fetch(url, withBoundedSignal(init));
|
|
644
|
+
return { res: res2 };
|
|
645
|
+
} catch (err) {
|
|
646
|
+
rethrowWithIssuerHint(err, url);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
const dispatcher2 = new ProxyAgent({ uri: proxyUrl, requestTls: { ca: effectiveCa() } });
|
|
650
|
+
const pending2 = undiciFetch(url, { ...withBoundedSignal(init), dispatcher: dispatcher2 });
|
|
651
|
+
closeAfter(dispatcher2, pending2);
|
|
652
|
+
try {
|
|
653
|
+
const res2 = await pending2;
|
|
654
|
+
return { res: res2 };
|
|
655
|
+
} catch (err) {
|
|
656
|
+
rethrowWithIssuerHint(err, url);
|
|
657
|
+
}
|
|
344
658
|
}
|
|
345
659
|
if (pinning.enforce && !pinning.capture && !pinning.pinnedSpkiHex) {
|
|
346
660
|
throw new AsTlsMismatchError(
|
|
@@ -348,27 +662,27 @@ async function fetchAs(url, init, pinning) {
|
|
|
348
662
|
);
|
|
349
663
|
}
|
|
350
664
|
if (pinning.pinnedSpkiHex && (pinning.enforce || !pinning.capture)) {
|
|
351
|
-
const dispatcher2 = buildPinnedDispatcher(pinning.pinnedSpkiHex);
|
|
665
|
+
const dispatcher2 = buildPinnedDispatcher(pinning.pinnedSpkiHex, url);
|
|
352
666
|
const pending2 = undiciFetch(url, { ...withBoundedSignal(init), dispatcher: dispatcher2 });
|
|
353
667
|
closeAfter(dispatcher2, pending2);
|
|
354
668
|
try {
|
|
355
669
|
const res2 = await pending2;
|
|
356
670
|
return { res: res2 };
|
|
357
671
|
} catch (err) {
|
|
358
|
-
if (!isPinCheckError(err))
|
|
672
|
+
if (!isPinCheckError(err)) rethrowWithIssuerHint(err, url);
|
|
359
673
|
throw new AsTlsMismatchError(
|
|
360
|
-
`TLS pin check failed for ${url} \u2014 ${describeFetchError(err)}. Refusing the connection; the API key / session cookie were never sent.
|
|
674
|
+
`TLS pin check failed for ${url} \u2014 ${describeFetchError(err)}. Refusing the connection; the API key / session cookie were never sent. Two things produce this: (1) the Authority Server's TLS certificate genuinely changed (a new signing key, not just a renewal) \u2014 clear the pairing and sign in again to re-pin it; or (2) a TLS-inspecting proxy on this network is re-signing traffic with its own certificate \u2014 ask network/IT to exempt the Authority Server's host from TLS inspection (pin-tls refusing this, even with a trusted --ca-file, is working as intended).`
|
|
361
675
|
);
|
|
362
676
|
}
|
|
363
677
|
}
|
|
364
|
-
const { dispatcher, getCapturedCert } = buildCapturingDispatcher();
|
|
678
|
+
const { dispatcher, getCapturedCert } = buildCapturingDispatcher(url);
|
|
365
679
|
const pending = undiciFetch(url, { ...withBoundedSignal(init), dispatcher });
|
|
366
680
|
closeAfter(dispatcher, pending);
|
|
367
681
|
let res;
|
|
368
682
|
try {
|
|
369
683
|
res = await pending;
|
|
370
684
|
} catch (err) {
|
|
371
|
-
if (!isPinCheckError(err))
|
|
685
|
+
if (!isPinCheckError(err)) rethrowWithIssuerHint(err, url);
|
|
372
686
|
throw new AsTlsMismatchError(
|
|
373
687
|
`could not verify the Authority Server at ${url} while establishing its TLS pin \u2014 ${describeFetchError(err)}.`
|
|
374
688
|
);
|
|
@@ -834,11 +1148,11 @@ var AttestationCache = class {
|
|
|
834
1148
|
};
|
|
835
1149
|
|
|
836
1150
|
// src/lib/gate-store.ts
|
|
837
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
1151
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
838
1152
|
import { homedir } from "os";
|
|
839
|
-
import { dirname as dirname3, join as
|
|
1153
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
840
1154
|
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
841
|
-
var DEFAULT_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
1155
|
+
var DEFAULT_DIR = process.env.SUVEREN_DATA_DIR ?? join5(homedir(), ".suveren");
|
|
842
1156
|
var GateStore = class {
|
|
843
1157
|
entries = /* @__PURE__ */ new Map();
|
|
844
1158
|
baseDir;
|
|
@@ -856,11 +1170,11 @@ var GateStore = class {
|
|
|
856
1170
|
*/
|
|
857
1171
|
setVaultKey(key) {
|
|
858
1172
|
this.vaultKey = key;
|
|
859
|
-
if (
|
|
1173
|
+
if (existsSync5(this.encryptedFilePath)) {
|
|
860
1174
|
this.loadEncrypted();
|
|
861
1175
|
} else if (this.entries.size > 0) {
|
|
862
1176
|
this.persistEncrypted();
|
|
863
|
-
if (
|
|
1177
|
+
if (existsSync5(this.plaintextFilePath)) {
|
|
864
1178
|
try {
|
|
865
1179
|
unlinkSync2(this.plaintextFilePath);
|
|
866
1180
|
} catch {
|
|
@@ -898,11 +1212,11 @@ var GateStore = class {
|
|
|
898
1212
|
clearAll() {
|
|
899
1213
|
this.entries = /* @__PURE__ */ new Map();
|
|
900
1214
|
try {
|
|
901
|
-
if (
|
|
1215
|
+
if (existsSync5(this.plaintextFilePath)) unlinkSync2(this.plaintextFilePath);
|
|
902
1216
|
} catch {
|
|
903
1217
|
}
|
|
904
1218
|
try {
|
|
905
|
-
if (
|
|
1219
|
+
if (existsSync5(this.encryptedFilePath)) unlinkSync2(this.encryptedFilePath);
|
|
906
1220
|
} catch {
|
|
907
1221
|
}
|
|
908
1222
|
}
|
|
@@ -935,19 +1249,19 @@ var GateStore = class {
|
|
|
935
1249
|
}
|
|
936
1250
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
937
1251
|
get plaintextFilePath() {
|
|
938
|
-
return
|
|
1252
|
+
return join5(this.baseDir, "gates.json");
|
|
939
1253
|
}
|
|
940
1254
|
get encryptedFilePath() {
|
|
941
|
-
return
|
|
1255
|
+
return join5(this.baseDir, "gates.enc.json");
|
|
942
1256
|
}
|
|
943
1257
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
944
1258
|
loadPlaintext() {
|
|
945
|
-
if (!
|
|
1259
|
+
if (!existsSync5(this.plaintextFilePath)) {
|
|
946
1260
|
mkdirSync3(this.baseDir, { recursive: true });
|
|
947
1261
|
return;
|
|
948
1262
|
}
|
|
949
1263
|
try {
|
|
950
|
-
const raw =
|
|
1264
|
+
const raw = readFileSync6(this.plaintextFilePath, "utf-8");
|
|
951
1265
|
const data = JSON.parse(raw);
|
|
952
1266
|
this.entries = new Map(Object.entries(data.entries));
|
|
953
1267
|
} catch {
|
|
@@ -956,9 +1270,9 @@ var GateStore = class {
|
|
|
956
1270
|
}
|
|
957
1271
|
}
|
|
958
1272
|
loadEncrypted() {
|
|
959
|
-
if (!
|
|
1273
|
+
if (!existsSync5(this.encryptedFilePath)) return;
|
|
960
1274
|
try {
|
|
961
|
-
const raw =
|
|
1275
|
+
const raw = readFileSync6(this.encryptedFilePath, "utf-8");
|
|
962
1276
|
const data = JSON.parse(raw);
|
|
963
1277
|
this.entries = /* @__PURE__ */ new Map();
|
|
964
1278
|
for (const [key, blob] of Object.entries(data.entries)) {
|
|
@@ -999,11 +1313,11 @@ var GateStore = class {
|
|
|
999
1313
|
};
|
|
1000
1314
|
|
|
1001
1315
|
// src/lib/execution-log.ts
|
|
1002
|
-
import { mkdirSync as mkdirSync4, readFileSync as
|
|
1316
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync6, unlinkSync as unlinkSync3 } from "fs";
|
|
1003
1317
|
import { homedir as homedir2 } from "os";
|
|
1004
|
-
import { dirname as dirname4, join as
|
|
1318
|
+
import { dirname as dirname4, join as join6 } from "path";
|
|
1005
1319
|
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes2 } from "crypto";
|
|
1006
|
-
var DEFAULT_DIR2 = process.env.SUVEREN_DATA_DIR ??
|
|
1320
|
+
var DEFAULT_DIR2 = process.env.SUVEREN_DATA_DIR ?? join6(homedir2(), ".suveren");
|
|
1007
1321
|
var MAX_AGE_SECONDS = 31 * 24 * 60 * 60;
|
|
1008
1322
|
var ExecutionLog = class {
|
|
1009
1323
|
entries = [];
|
|
@@ -1022,11 +1336,11 @@ var ExecutionLog = class {
|
|
|
1022
1336
|
*/
|
|
1023
1337
|
setVaultKey(key) {
|
|
1024
1338
|
this.vaultKey = key;
|
|
1025
|
-
if (
|
|
1339
|
+
if (existsSync6(this.encryptedFilePath)) {
|
|
1026
1340
|
this.loadEncrypted();
|
|
1027
1341
|
} else if (this.entries.length > 0) {
|
|
1028
1342
|
this.persistEncrypted();
|
|
1029
|
-
if (
|
|
1343
|
+
if (existsSync6(this.plaintextFilePath)) {
|
|
1030
1344
|
try {
|
|
1031
1345
|
unlinkSync3(this.plaintextFilePath);
|
|
1032
1346
|
} catch {
|
|
@@ -1130,19 +1444,19 @@ var ExecutionLog = class {
|
|
|
1130
1444
|
}
|
|
1131
1445
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
1132
1446
|
get plaintextFilePath() {
|
|
1133
|
-
return
|
|
1447
|
+
return join6(this.baseDir, "execution-log.json");
|
|
1134
1448
|
}
|
|
1135
1449
|
get encryptedFilePath() {
|
|
1136
|
-
return
|
|
1450
|
+
return join6(this.baseDir, "execution-log.enc.json");
|
|
1137
1451
|
}
|
|
1138
1452
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
1139
1453
|
loadPlaintext() {
|
|
1140
|
-
if (!
|
|
1454
|
+
if (!existsSync6(this.plaintextFilePath)) {
|
|
1141
1455
|
mkdirSync4(this.baseDir, { recursive: true });
|
|
1142
1456
|
return;
|
|
1143
1457
|
}
|
|
1144
1458
|
try {
|
|
1145
|
-
const raw =
|
|
1459
|
+
const raw = readFileSync7(this.plaintextFilePath, "utf-8");
|
|
1146
1460
|
const data = JSON.parse(raw);
|
|
1147
1461
|
this.entries = data.entries;
|
|
1148
1462
|
this.prune();
|
|
@@ -1152,9 +1466,9 @@ var ExecutionLog = class {
|
|
|
1152
1466
|
}
|
|
1153
1467
|
}
|
|
1154
1468
|
loadEncrypted() {
|
|
1155
|
-
if (!
|
|
1469
|
+
if (!existsSync6(this.encryptedFilePath)) return;
|
|
1156
1470
|
try {
|
|
1157
|
-
const raw =
|
|
1471
|
+
const raw = readFileSync7(this.encryptedFilePath, "utf-8");
|
|
1158
1472
|
const data = JSON.parse(raw);
|
|
1159
1473
|
const decrypted = this.decrypt(data.blob);
|
|
1160
1474
|
const logFile = JSON.parse(decrypted);
|
|
@@ -1201,11 +1515,11 @@ function windowCutoff(window, now) {
|
|
|
1201
1515
|
}
|
|
1202
1516
|
|
|
1203
1517
|
// src/lib/denial-log.ts
|
|
1204
|
-
import { mkdirSync as mkdirSync5, readFileSync as
|
|
1518
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync7, unlinkSync as unlinkSync4 } from "fs";
|
|
1205
1519
|
import { homedir as homedir3 } from "os";
|
|
1206
|
-
import { dirname as dirname5, join as
|
|
1520
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
1207
1521
|
import { createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3, randomBytes as randomBytes3 } from "crypto";
|
|
1208
|
-
var DEFAULT_DIR3 = process.env.SUVEREN_DATA_DIR ??
|
|
1522
|
+
var DEFAULT_DIR3 = process.env.SUVEREN_DATA_DIR ?? join7(homedir3(), ".suveren");
|
|
1209
1523
|
var MAX_RECORDS = 200;
|
|
1210
1524
|
var MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1211
1525
|
var DenialLog = class {
|
|
@@ -1218,11 +1532,11 @@ var DenialLog = class {
|
|
|
1218
1532
|
}
|
|
1219
1533
|
setVaultKey(key) {
|
|
1220
1534
|
this.vaultKey = key;
|
|
1221
|
-
if (
|
|
1535
|
+
if (existsSync7(this.encryptedFilePath)) {
|
|
1222
1536
|
this.loadEncrypted();
|
|
1223
1537
|
} else if (this.records.length > 0) {
|
|
1224
1538
|
this.persistEncrypted();
|
|
1225
|
-
if (
|
|
1539
|
+
if (existsSync7(this.plaintextFilePath)) {
|
|
1226
1540
|
try {
|
|
1227
1541
|
unlinkSync4(this.plaintextFilePath);
|
|
1228
1542
|
} catch {
|
|
@@ -1272,18 +1586,18 @@ var DenialLog = class {
|
|
|
1272
1586
|
return Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "hex")), decipher.final()]).toString("utf8");
|
|
1273
1587
|
}
|
|
1274
1588
|
get plaintextFilePath() {
|
|
1275
|
-
return
|
|
1589
|
+
return join7(this.baseDir, "denials.json");
|
|
1276
1590
|
}
|
|
1277
1591
|
get encryptedFilePath() {
|
|
1278
|
-
return
|
|
1592
|
+
return join7(this.baseDir, "denials.enc.json");
|
|
1279
1593
|
}
|
|
1280
1594
|
loadPlaintext() {
|
|
1281
|
-
if (!
|
|
1595
|
+
if (!existsSync7(this.plaintextFilePath)) {
|
|
1282
1596
|
mkdirSync5(this.baseDir, { recursive: true });
|
|
1283
1597
|
return;
|
|
1284
1598
|
}
|
|
1285
1599
|
try {
|
|
1286
|
-
const data = JSON.parse(
|
|
1600
|
+
const data = JSON.parse(readFileSync8(this.plaintextFilePath, "utf-8"));
|
|
1287
1601
|
this.records = data.records ?? [];
|
|
1288
1602
|
} catch {
|
|
1289
1603
|
console.error(`[DenialLog] Could not parse ${this.plaintextFilePath}, starting fresh`);
|
|
@@ -1291,9 +1605,9 @@ var DenialLog = class {
|
|
|
1291
1605
|
}
|
|
1292
1606
|
}
|
|
1293
1607
|
loadEncrypted() {
|
|
1294
|
-
if (!
|
|
1608
|
+
if (!existsSync7(this.encryptedFilePath)) return;
|
|
1295
1609
|
try {
|
|
1296
|
-
const data = JSON.parse(
|
|
1610
|
+
const data = JSON.parse(readFileSync8(this.encryptedFilePath, "utf-8"));
|
|
1297
1611
|
const logFile = JSON.parse(this.decrypt(data.blob));
|
|
1298
1612
|
this.records = logFile.records ?? [];
|
|
1299
1613
|
} catch (err) {
|
|
@@ -1319,11 +1633,11 @@ var DenialLog = class {
|
|
|
1319
1633
|
};
|
|
1320
1634
|
|
|
1321
1635
|
// src/lib/receipt-archive.ts
|
|
1322
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
1636
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync8, unlinkSync as unlinkSync5 } from "fs";
|
|
1323
1637
|
import { homedir as homedir4 } from "os";
|
|
1324
|
-
import { dirname as dirname6, join as
|
|
1638
|
+
import { dirname as dirname6, join as join8 } from "path";
|
|
1325
1639
|
import { createCipheriv as createCipheriv4, createDecipheriv as createDecipheriv4, randomBytes as randomBytes4 } from "crypto";
|
|
1326
|
-
var DEFAULT_DIR4 = process.env.SUVEREN_DATA_DIR ??
|
|
1640
|
+
var DEFAULT_DIR4 = process.env.SUVEREN_DATA_DIR ?? join8(homedir4(), ".suveren");
|
|
1327
1641
|
function stripPreviews(proposal) {
|
|
1328
1642
|
const args = proposal.toolArgs;
|
|
1329
1643
|
if (!args || typeof args !== "object") return proposal;
|
|
@@ -1348,11 +1662,11 @@ var ReceiptArchive = class {
|
|
|
1348
1662
|
*/
|
|
1349
1663
|
setVaultKey(key) {
|
|
1350
1664
|
this.vaultKey = key;
|
|
1351
|
-
if (
|
|
1665
|
+
if (existsSync8(this.encryptedFilePath)) {
|
|
1352
1666
|
this.loadEncrypted();
|
|
1353
1667
|
} else if (this.data.receipts.length > 0 || Object.keys(this.data.authorizations).length > 0) {
|
|
1354
1668
|
this.persistEncrypted();
|
|
1355
|
-
if (
|
|
1669
|
+
if (existsSync8(this.plaintextFilePath)) {
|
|
1356
1670
|
try {
|
|
1357
1671
|
unlinkSync5(this.plaintextFilePath);
|
|
1358
1672
|
} catch {
|
|
@@ -1366,7 +1680,7 @@ var ReceiptArchive = class {
|
|
|
1366
1680
|
* this state — that would report a hollow success over unreadable evidence.
|
|
1367
1681
|
*/
|
|
1368
1682
|
isLocked() {
|
|
1369
|
-
return this.vaultKey === null &&
|
|
1683
|
+
return this.vaultKey === null && existsSync8(this.encryptedFilePath);
|
|
1370
1684
|
}
|
|
1371
1685
|
/**
|
|
1372
1686
|
* Archive a signed receipt (and the grant it was issued under).
|
|
@@ -1467,19 +1781,19 @@ var ReceiptArchive = class {
|
|
|
1467
1781
|
}
|
|
1468
1782
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
1469
1783
|
get plaintextFilePath() {
|
|
1470
|
-
return
|
|
1784
|
+
return join8(this.baseDir, "receipt-archive.json");
|
|
1471
1785
|
}
|
|
1472
1786
|
get encryptedFilePath() {
|
|
1473
|
-
return
|
|
1787
|
+
return join8(this.baseDir, "receipt-archive.enc.json");
|
|
1474
1788
|
}
|
|
1475
1789
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
1476
1790
|
loadPlaintext() {
|
|
1477
|
-
if (!
|
|
1791
|
+
if (!existsSync8(this.plaintextFilePath)) {
|
|
1478
1792
|
mkdirSync6(this.baseDir, { recursive: true });
|
|
1479
1793
|
return;
|
|
1480
1794
|
}
|
|
1481
1795
|
try {
|
|
1482
|
-
const raw =
|
|
1796
|
+
const raw = readFileSync9(this.plaintextFilePath, "utf-8");
|
|
1483
1797
|
const parsed = JSON.parse(raw);
|
|
1484
1798
|
this.data = {
|
|
1485
1799
|
version: 1,
|
|
@@ -1491,16 +1805,16 @@ var ReceiptArchive = class {
|
|
|
1491
1805
|
try {
|
|
1492
1806
|
writeFileSync6(
|
|
1493
1807
|
`${this.plaintextFilePath}.corrupt-${Date.now()}`,
|
|
1494
|
-
|
|
1808
|
+
readFileSync9(this.plaintextFilePath)
|
|
1495
1809
|
);
|
|
1496
1810
|
} catch {
|
|
1497
1811
|
}
|
|
1498
1812
|
}
|
|
1499
1813
|
}
|
|
1500
1814
|
loadEncrypted() {
|
|
1501
|
-
if (!
|
|
1815
|
+
if (!existsSync8(this.encryptedFilePath)) return;
|
|
1502
1816
|
try {
|
|
1503
|
-
const raw =
|
|
1817
|
+
const raw = readFileSync9(this.encryptedFilePath, "utf-8");
|
|
1504
1818
|
const data = JSON.parse(raw);
|
|
1505
1819
|
const decrypted = this.decrypt(data.blob);
|
|
1506
1820
|
const parsed = JSON.parse(decrypted);
|
|
@@ -1514,7 +1828,7 @@ var ReceiptArchive = class {
|
|
|
1514
1828
|
try {
|
|
1515
1829
|
writeFileSync6(
|
|
1516
1830
|
`${this.encryptedFilePath}.corrupt-${Date.now()}`,
|
|
1517
|
-
|
|
1831
|
+
readFileSync9(this.encryptedFilePath)
|
|
1518
1832
|
);
|
|
1519
1833
|
} catch {
|
|
1520
1834
|
}
|
|
@@ -1542,11 +1856,11 @@ var ReceiptArchive = class {
|
|
|
1542
1856
|
};
|
|
1543
1857
|
|
|
1544
1858
|
// src/lib/execution-journal.ts
|
|
1545
|
-
import { mkdirSync as mkdirSync7, readFileSync as
|
|
1859
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync9, renameSync } from "fs";
|
|
1546
1860
|
import { homedir as homedir5 } from "os";
|
|
1547
|
-
import { dirname as dirname7, join as
|
|
1861
|
+
import { dirname as dirname7, join as join9 } from "path";
|
|
1548
1862
|
import { createHash as createHash3 } from "crypto";
|
|
1549
|
-
var DEFAULT_DIR5 = process.env.SUVEREN_DATA_DIR ??
|
|
1863
|
+
var DEFAULT_DIR5 = process.env.SUVEREN_DATA_DIR ?? join9(homedir5(), ".suveren");
|
|
1550
1864
|
var FILE_NAME = "execution-journal.json";
|
|
1551
1865
|
var MAX_AGE_SECONDS2 = 90 * 24 * 60 * 60;
|
|
1552
1866
|
function hashToolArgs(args) {
|
|
@@ -1563,7 +1877,7 @@ var ExecutionJournal = class {
|
|
|
1563
1877
|
filePath;
|
|
1564
1878
|
constructor(filePath) {
|
|
1565
1879
|
const baseDir = filePath && filePath.endsWith(".json") ? dirname7(filePath) : filePath ?? DEFAULT_DIR5;
|
|
1566
|
-
this.filePath =
|
|
1880
|
+
this.filePath = join9(baseDir, FILE_NAME);
|
|
1567
1881
|
}
|
|
1568
1882
|
/** Look a ticket up. Always reads from disk — another process may have written. */
|
|
1569
1883
|
get(ticketId) {
|
|
@@ -1602,9 +1916,9 @@ var ExecutionJournal = class {
|
|
|
1602
1916
|
}
|
|
1603
1917
|
// ─── Persistence ────────────────────────────────────────────────────────
|
|
1604
1918
|
load() {
|
|
1605
|
-
if (!
|
|
1919
|
+
if (!existsSync9(this.filePath)) return { version: 1, entries: [] };
|
|
1606
1920
|
try {
|
|
1607
|
-
const parsed = JSON.parse(
|
|
1921
|
+
const parsed = JSON.parse(readFileSync10(this.filePath, "utf8"));
|
|
1608
1922
|
if (parsed?.version !== 1 || !Array.isArray(parsed.entries)) {
|
|
1609
1923
|
throw new Error("unexpected shape");
|
|
1610
1924
|
}
|
|
@@ -1629,10 +1943,10 @@ function nowSec() {
|
|
|
1629
1943
|
}
|
|
1630
1944
|
|
|
1631
1945
|
// src/lib/proposal-submission-store.ts
|
|
1632
|
-
import { mkdirSync as mkdirSync8, readFileSync as
|
|
1946
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync10, renameSync as renameSync2 } from "fs";
|
|
1633
1947
|
import { homedir as homedir6 } from "os";
|
|
1634
|
-
import { dirname as dirname8, join as
|
|
1635
|
-
var DEFAULT_DIR6 = process.env.SUVEREN_DATA_DIR ??
|
|
1948
|
+
import { dirname as dirname8, join as join10 } from "path";
|
|
1949
|
+
var DEFAULT_DIR6 = process.env.SUVEREN_DATA_DIR ?? join10(homedir6(), ".suveren");
|
|
1636
1950
|
var FILE_NAME2 = "proposal-submissions.json";
|
|
1637
1951
|
var MAX_AGE_SECONDS3 = 90 * 24 * 60 * 60;
|
|
1638
1952
|
function nowSec2() {
|
|
@@ -1642,7 +1956,7 @@ var ProposalSubmissionStore = class {
|
|
|
1642
1956
|
filePath;
|
|
1643
1957
|
constructor(filePath) {
|
|
1644
1958
|
const baseDir = filePath && filePath.endsWith(".json") ? dirname8(filePath) : filePath ?? DEFAULT_DIR6;
|
|
1645
|
-
this.filePath =
|
|
1959
|
+
this.filePath = join10(baseDir, FILE_NAME2);
|
|
1646
1960
|
}
|
|
1647
1961
|
/** Record what was submitted, right after a successful submitProposal(). */
|
|
1648
1962
|
record(entry) {
|
|
@@ -1665,9 +1979,9 @@ var ProposalSubmissionStore = class {
|
|
|
1665
1979
|
return this.load().entries.find((e) => e.proposalId === proposalId);
|
|
1666
1980
|
}
|
|
1667
1981
|
load() {
|
|
1668
|
-
if (!
|
|
1982
|
+
if (!existsSync10(this.filePath)) return { version: 1, entries: [] };
|
|
1669
1983
|
try {
|
|
1670
|
-
const parsed = JSON.parse(
|
|
1984
|
+
const parsed = JSON.parse(readFileSync11(this.filePath, "utf-8"));
|
|
1671
1985
|
const cutoff = nowSec2() - MAX_AGE_SECONDS3;
|
|
1672
1986
|
parsed.entries = (parsed.entries ?? []).filter((e) => e.submittedAt >= cutoff);
|
|
1673
1987
|
return parsed;
|
|
@@ -1956,17 +2270,17 @@ function formatConsumptionFull(entries) {
|
|
|
1956
2270
|
}
|
|
1957
2271
|
|
|
1958
2272
|
// src/lib/context-loader.ts
|
|
1959
|
-
import { readFileSync as
|
|
2273
|
+
import { readFileSync as readFileSync12, existsSync as existsSync11 } from "fs";
|
|
1960
2274
|
import { homedir as homedir7 } from "os";
|
|
1961
|
-
import { join as
|
|
1962
|
-
var DEFAULT_DIR7 = process.env.SUVEREN_DATA_DIR ??
|
|
2275
|
+
import { join as join11 } from "path";
|
|
2276
|
+
var DEFAULT_DIR7 = process.env.SUVEREN_DATA_DIR ?? join11(homedir7(), ".suveren");
|
|
1963
2277
|
var BRIEF_MAX_CHARS = 16 * 1024;
|
|
1964
2278
|
function readContextFile(dataDir2) {
|
|
1965
2279
|
const dir = dataDir2 ?? DEFAULT_DIR7;
|
|
1966
|
-
const filePath =
|
|
1967
|
-
if (!
|
|
2280
|
+
const filePath = join11(dir, "context.md");
|
|
2281
|
+
if (!existsSync11(filePath)) return null;
|
|
1968
2282
|
try {
|
|
1969
|
-
const content =
|
|
2283
|
+
const content = readFileSync12(filePath, "utf-8").trim();
|
|
1970
2284
|
return content || null;
|
|
1971
2285
|
} catch {
|
|
1972
2286
|
return null;
|
|
@@ -2605,8 +2919,8 @@ import { extname } from "path";
|
|
|
2605
2919
|
import { randomUUID } from "crypto";
|
|
2606
2920
|
|
|
2607
2921
|
// src/lib/manifest-loader.ts
|
|
2608
|
-
import { readFileSync as
|
|
2609
|
-
import { join as
|
|
2922
|
+
import { readFileSync as readFileSync13, existsSync as existsSync12 } from "fs";
|
|
2923
|
+
import { join as join12, resolve } from "path";
|
|
2610
2924
|
var EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
2611
2925
|
function isExactSemver(value) {
|
|
2612
2926
|
return typeof value === "string" && EXACT_SEMVER_RE.test(value);
|
|
@@ -2624,25 +2938,25 @@ function invalidNpmPinReason(manifest) {
|
|
|
2624
2938
|
var manifests = /* @__PURE__ */ new Map();
|
|
2625
2939
|
function loadManifests(integrationsDir) {
|
|
2626
2940
|
const dir = resolve(
|
|
2627
|
-
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ??
|
|
2941
|
+
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join12(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
|
|
2628
2942
|
);
|
|
2629
|
-
const indexPath =
|
|
2630
|
-
if (!
|
|
2943
|
+
const indexPath = join12(dir, "index.json");
|
|
2944
|
+
if (!existsSync12(indexPath)) {
|
|
2631
2945
|
console.error(`[ManifestLoader] No index.json found at ${indexPath}, skipping manifest loading`);
|
|
2632
2946
|
return 0;
|
|
2633
2947
|
}
|
|
2634
2948
|
let index;
|
|
2635
2949
|
try {
|
|
2636
|
-
index = JSON.parse(
|
|
2950
|
+
index = JSON.parse(readFileSync13(indexPath, "utf-8"));
|
|
2637
2951
|
} catch (err) {
|
|
2638
2952
|
console.error(`[ManifestLoader] Failed to parse ${indexPath}:`, err);
|
|
2639
2953
|
return 0;
|
|
2640
2954
|
}
|
|
2641
2955
|
let loaded = 0;
|
|
2642
2956
|
for (const [id, relativePath] of Object.entries(index.integrations)) {
|
|
2643
|
-
const manifestPath =
|
|
2957
|
+
const manifestPath = join12(dir, relativePath);
|
|
2644
2958
|
try {
|
|
2645
|
-
const manifest = JSON.parse(
|
|
2959
|
+
const manifest = JSON.parse(readFileSync13(manifestPath, "utf-8"));
|
|
2646
2960
|
const pinReason = invalidNpmPinReason(manifest);
|
|
2647
2961
|
if (pinReason) {
|
|
2648
2962
|
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${pinReason}`);
|
|
@@ -2666,6 +2980,8 @@ function getAllManifests() {
|
|
|
2666
2980
|
|
|
2667
2981
|
// src/lib/simulation-mode.ts
|
|
2668
2982
|
function isSimulationMode() {
|
|
2983
|
+
const policySimulation = readPolicy().policy.simulation;
|
|
2984
|
+
if (policySimulation !== void 0) return policySimulation;
|
|
2669
2985
|
return process.env.SUVEREN_SIMULATION === "1";
|
|
2670
2986
|
}
|
|
2671
2987
|
function manifestIsSimulated(manifest) {
|
|
@@ -4195,16 +4511,16 @@ function verifyGateContentHashes(content, auth) {
|
|
|
4195
4511
|
}
|
|
4196
4512
|
|
|
4197
4513
|
// src/lib/integration-registry.ts
|
|
4198
|
-
import { mkdirSync as mkdirSync9, readFileSync as
|
|
4514
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync13 } from "fs";
|
|
4199
4515
|
import { homedir as homedir8 } from "os";
|
|
4200
|
-
import { dirname as dirname9, join as
|
|
4201
|
-
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
4516
|
+
import { dirname as dirname9, join as join13 } from "path";
|
|
4517
|
+
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join13(homedir8(), ".suveren");
|
|
4202
4518
|
var IntegrationRegistry = class {
|
|
4203
4519
|
integrations = /* @__PURE__ */ new Map();
|
|
4204
4520
|
filePath;
|
|
4205
4521
|
constructor(dataDir2) {
|
|
4206
4522
|
const dir = dataDir2 ?? DEFAULT_DATA_DIR;
|
|
4207
|
-
this.filePath =
|
|
4523
|
+
this.filePath = join13(dir, "integrations.json");
|
|
4208
4524
|
this.load();
|
|
4209
4525
|
}
|
|
4210
4526
|
// ─── CRUD ───────────────────────────────────────────────────────────────
|
|
@@ -4235,12 +4551,12 @@ var IntegrationRegistry = class {
|
|
|
4235
4551
|
}
|
|
4236
4552
|
// ─── Load / Save ────────────────────────────────────────────────────────
|
|
4237
4553
|
load() {
|
|
4238
|
-
if (!
|
|
4554
|
+
if (!existsSync13(this.filePath)) {
|
|
4239
4555
|
mkdirSync9(dirname9(this.filePath), { recursive: true });
|
|
4240
4556
|
return;
|
|
4241
4557
|
}
|
|
4242
4558
|
try {
|
|
4243
|
-
const raw =
|
|
4559
|
+
const raw = readFileSync14(this.filePath, "utf-8");
|
|
4244
4560
|
const data = JSON.parse(raw);
|
|
4245
4561
|
const migrated = data.integrations.map((i) => {
|
|
4246
4562
|
if ("toolGating" in i && !("profile" in i)) {
|
|
@@ -4276,8 +4592,8 @@ var IntegrationRegistry = class {
|
|
|
4276
4592
|
|
|
4277
4593
|
// src/lib/integration-manager.ts
|
|
4278
4594
|
import { homedir as homedir9 } from "os";
|
|
4279
|
-
import { join as
|
|
4280
|
-
import { existsSync as
|
|
4595
|
+
import { join as join14, delimiter } from "path";
|
|
4596
|
+
import { existsSync as existsSync14, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync15, rmSync } from "fs";
|
|
4281
4597
|
import { execFile } from "child_process";
|
|
4282
4598
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
4283
4599
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
@@ -4354,25 +4670,26 @@ function safeHost(url) {
|
|
|
4354
4670
|
}
|
|
4355
4671
|
|
|
4356
4672
|
// src/lib/integration-manager.ts
|
|
4357
|
-
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ??
|
|
4358
|
-
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ??
|
|
4673
|
+
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join14(homedir9(), ".suveren");
|
|
4674
|
+
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ?? join14(DEFAULT_DATA_DIR2, "integrations");
|
|
4675
|
+
var OFFLINE_MODE = process.env.SUVEREN_OFFLINE === "1";
|
|
4359
4676
|
var installLock = Promise.resolve();
|
|
4360
4677
|
function withInstallLock(task) {
|
|
4361
4678
|
const run = installLock.then(task, task);
|
|
4362
4679
|
installLock = run.then(() => void 0, () => void 0);
|
|
4363
4680
|
return run;
|
|
4364
4681
|
}
|
|
4365
|
-
var INTEGRATIONS_BIN =
|
|
4682
|
+
var INTEGRATIONS_BIN = join14(INTEGRATIONS_DIR, "node_modules", ".bin");
|
|
4366
4683
|
function buildPath() {
|
|
4367
4684
|
const base = process.env.PATH ?? "";
|
|
4368
4685
|
return [INTEGRATIONS_BIN, base].join(delimiter);
|
|
4369
4686
|
}
|
|
4370
4687
|
function ensureIntegrationsDir() {
|
|
4371
|
-
if (!
|
|
4688
|
+
if (!existsSync14(INTEGRATIONS_DIR)) {
|
|
4372
4689
|
mkdirSync10(INTEGRATIONS_DIR, { recursive: true });
|
|
4373
4690
|
}
|
|
4374
|
-
const pkgPath =
|
|
4375
|
-
if (!
|
|
4691
|
+
const pkgPath = join14(INTEGRATIONS_DIR, "package.json");
|
|
4692
|
+
if (!existsSync14(pkgPath)) {
|
|
4376
4693
|
writeFileSync10(pkgPath, JSON.stringify({ name: "suveren-integrations", version: "1.0.0", private: true }, null, 2));
|
|
4377
4694
|
}
|
|
4378
4695
|
}
|
|
@@ -4460,19 +4777,19 @@ var IntegrationManager = class {
|
|
|
4460
4777
|
* exist, and treat anything else as not-installed (and reinstallable).
|
|
4461
4778
|
*/
|
|
4462
4779
|
isUsableInstall(npmPackage) {
|
|
4463
|
-
const pkgDir =
|
|
4464
|
-
const pkgJson =
|
|
4465
|
-
if (!
|
|
4780
|
+
const pkgDir = join14(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
4781
|
+
const pkgJson = join14(pkgDir, "package.json");
|
|
4782
|
+
if (!existsSync14(pkgDir) || !existsSync14(pkgJson)) return false;
|
|
4466
4783
|
try {
|
|
4467
|
-
const pkg = JSON.parse(
|
|
4784
|
+
const pkg = JSON.parse(readFileSync15(pkgJson, "utf8"));
|
|
4468
4785
|
const defaultBinName = (pkg.name ?? npmPackage).split("/").pop() ?? "";
|
|
4469
4786
|
const binMap = typeof pkg.bin === "string" ? { [defaultBinName]: pkg.bin } : pkg.bin ?? {};
|
|
4470
4787
|
const binEntries = Object.entries(binMap);
|
|
4471
4788
|
for (const [binName, target] of binEntries) {
|
|
4472
|
-
if (!
|
|
4789
|
+
if (!existsSync14(join14(pkgDir, target))) return false;
|
|
4473
4790
|
if (!this.binShimExists(binName)) return false;
|
|
4474
4791
|
}
|
|
4475
|
-
if (binEntries.length === 0 && pkg.main && !
|
|
4792
|
+
if (binEntries.length === 0 && pkg.main && !existsSync14(join14(pkgDir, pkg.main))) return false;
|
|
4476
4793
|
return true;
|
|
4477
4794
|
} catch {
|
|
4478
4795
|
return false;
|
|
@@ -4487,9 +4804,9 @@ var IntegrationManager = class {
|
|
|
4487
4804
|
*/
|
|
4488
4805
|
readInstalledVersion(npmPackage) {
|
|
4489
4806
|
if (!this.isUsableInstall(npmPackage)) return null;
|
|
4490
|
-
const pkgJson =
|
|
4807
|
+
const pkgJson = join14(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"), "package.json");
|
|
4491
4808
|
try {
|
|
4492
|
-
const pkg = JSON.parse(
|
|
4809
|
+
const pkg = JSON.parse(readFileSync15(pkgJson, "utf8"));
|
|
4493
4810
|
return typeof pkg.version === "string" ? pkg.version : null;
|
|
4494
4811
|
} catch {
|
|
4495
4812
|
return null;
|
|
@@ -4503,7 +4820,7 @@ var IntegrationManager = class {
|
|
|
4503
4820
|
binShimExists(binName) {
|
|
4504
4821
|
if (!binName) return false;
|
|
4505
4822
|
const candidates = process.platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.CMD`, `${binName}.ps1`] : [binName];
|
|
4506
|
-
return candidates.some((c) =>
|
|
4823
|
+
return candidates.some((c) => existsSync14(join14(INTEGRATIONS_BIN, c)));
|
|
4507
4824
|
}
|
|
4508
4825
|
/**
|
|
4509
4826
|
* The exact version THIS gateway's CURRENTLY LOADED manifest pins
|
|
@@ -4545,6 +4862,19 @@ var IntegrationManager = class {
|
|
|
4545
4862
|
if (!npmPackage) return;
|
|
4546
4863
|
ensureIntegrationsDir();
|
|
4547
4864
|
const pinnedVersion = this.pinnedVersionFor(config);
|
|
4865
|
+
if (OFFLINE_MODE) {
|
|
4866
|
+
const current = this.readInstalledVersion(npmPackage);
|
|
4867
|
+
if (pinnedVersion) {
|
|
4868
|
+
if (current === pinnedVersion) return;
|
|
4869
|
+
throw new Error(
|
|
4870
|
+
`${config.id}: connector ${npmPackage} version ${pinnedVersion} is not part of this installation (found ${current ?? "nothing installed"} at ${INTEGRATIONS_DIR}; SUVEREN_OFFLINE=1 so no npm install was attempted).`
|
|
4871
|
+
);
|
|
4872
|
+
}
|
|
4873
|
+
if (current !== null) return;
|
|
4874
|
+
throw new Error(
|
|
4875
|
+
`${config.id}: connector ${npmPackage} is not part of this installation (nothing installed at ${INTEGRATIONS_DIR}; SUVEREN_OFFLINE=1 so no npm install was attempted).`
|
|
4876
|
+
);
|
|
4877
|
+
}
|
|
4548
4878
|
if (pinnedVersion) {
|
|
4549
4879
|
if (this.readInstalledVersion(npmPackage) === pinnedVersion) return;
|
|
4550
4880
|
return withInstallLock(async () => {
|
|
@@ -4580,8 +4910,8 @@ var IntegrationManager = class {
|
|
|
4580
4910
|
* nothing at all.
|
|
4581
4911
|
*/
|
|
4582
4912
|
async installNow(npmPackage, exactVersion, cleanFirst = true) {
|
|
4583
|
-
const pkgDir =
|
|
4584
|
-
if (cleanFirst &&
|
|
4913
|
+
const pkgDir = join14(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
4914
|
+
if (cleanFirst && existsSync14(pkgDir)) {
|
|
4585
4915
|
console.error(`[IntegrationManager] Removing ${exactVersion ? "outdated" : "unusable"} install of ${npmPackage}`);
|
|
4586
4916
|
try {
|
|
4587
4917
|
rmSync(pkgDir, { recursive: true, force: true });
|
|
@@ -5070,28 +5400,28 @@ var IntegrationManager = class {
|
|
|
5070
5400
|
};
|
|
5071
5401
|
|
|
5072
5402
|
// src/lib/profile-loader.ts
|
|
5073
|
-
import { readFileSync as
|
|
5074
|
-
import { join as
|
|
5403
|
+
import { readFileSync as readFileSync16, existsSync as existsSync15 } from "fs";
|
|
5404
|
+
import { join as join15, resolve as resolve2 } from "path";
|
|
5075
5405
|
import { registerProfile } from "@hap/core";
|
|
5076
5406
|
function loadProfiles(profilesDir) {
|
|
5077
|
-
const dir = resolve2(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ??
|
|
5078
|
-
const indexPath =
|
|
5079
|
-
if (!
|
|
5407
|
+
const dir = resolve2(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ?? join15(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "..", "hap-profiles"));
|
|
5408
|
+
const indexPath = join15(dir, "index.json");
|
|
5409
|
+
if (!existsSync15(indexPath)) {
|
|
5080
5410
|
warnNoProfiles(`No index.json at ${indexPath}`);
|
|
5081
5411
|
return 0;
|
|
5082
5412
|
}
|
|
5083
5413
|
let index;
|
|
5084
5414
|
try {
|
|
5085
|
-
index = JSON.parse(
|
|
5415
|
+
index = JSON.parse(readFileSync16(indexPath, "utf-8"));
|
|
5086
5416
|
} catch (err) {
|
|
5087
5417
|
console.error(`[ProfileLoader] Failed to parse ${indexPath}:`, err);
|
|
5088
5418
|
return 0;
|
|
5089
5419
|
}
|
|
5090
5420
|
let loaded = 0;
|
|
5091
5421
|
for (const [profileId, relativePath] of Object.entries(index.profiles)) {
|
|
5092
|
-
const profilePath =
|
|
5422
|
+
const profilePath = join15(dir, relativePath);
|
|
5093
5423
|
try {
|
|
5094
|
-
const profile = JSON.parse(
|
|
5424
|
+
const profile = JSON.parse(readFileSync16(profilePath, "utf-8"));
|
|
5095
5425
|
registerProfile(profileId, profile);
|
|
5096
5426
|
const shortName = profileId.split("/").pop()?.replace(/@.*$/, "");
|
|
5097
5427
|
if (shortName && shortName !== profileId) {
|
|
@@ -5127,8 +5457,8 @@ function warnNoProfiles(reason) {
|
|
|
5127
5457
|
import { decodeAttestationBlob as decodeAttestationBlob3 } from "@hap/core";
|
|
5128
5458
|
|
|
5129
5459
|
// src/lib/committed-executor.ts
|
|
5130
|
-
import { existsSync as
|
|
5131
|
-
import { dirname as dirname10, join as
|
|
5460
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync17, unlinkSync as unlinkSync6, writeFileSync as writeFileSync11 } from "fs";
|
|
5461
|
+
import { dirname as dirname10, join as join16 } from "path";
|
|
5132
5462
|
var CommittedExecutor = class {
|
|
5133
5463
|
constructor(run) {
|
|
5134
5464
|
this.run = run;
|
|
@@ -5156,7 +5486,7 @@ var ExecutorLock = class {
|
|
|
5156
5486
|
path;
|
|
5157
5487
|
held = false;
|
|
5158
5488
|
constructor(dataDir2) {
|
|
5159
|
-
this.path =
|
|
5489
|
+
this.path = join16(dataDir2, "executor.lock");
|
|
5160
5490
|
}
|
|
5161
5491
|
/** Try to take the lock. Returns the holder's pid when someone else has it. */
|
|
5162
5492
|
acquire() {
|
|
@@ -5188,9 +5518,9 @@ var ExecutorLock = class {
|
|
|
5188
5518
|
}
|
|
5189
5519
|
}
|
|
5190
5520
|
read() {
|
|
5191
|
-
if (!
|
|
5521
|
+
if (!existsSync16(this.path)) return null;
|
|
5192
5522
|
try {
|
|
5193
|
-
const parsed = JSON.parse(
|
|
5523
|
+
const parsed = JSON.parse(readFileSync17(this.path, "utf8"));
|
|
5194
5524
|
return typeof parsed?.pid === "number" ? parsed : null;
|
|
5195
5525
|
} catch {
|
|
5196
5526
|
return null;
|
|
@@ -5208,19 +5538,19 @@ function isAlive(pid) {
|
|
|
5208
5538
|
|
|
5209
5539
|
// bin/http.ts
|
|
5210
5540
|
import { homedir as homedir10 } from "os";
|
|
5211
|
-
import { join as
|
|
5541
|
+
import { join as join18 } from "path";
|
|
5212
5542
|
|
|
5213
5543
|
// src/lib/mcp-as-tracker.ts
|
|
5214
|
-
import { existsSync as
|
|
5215
|
-
import { dirname as dirname11, join as
|
|
5544
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync12, readFileSync as readFileSync18, writeFileSync as writeFileSync12 } from "fs";
|
|
5545
|
+
import { dirname as dirname11, join as join17 } from "path";
|
|
5216
5546
|
function trackerPath(dataDir2) {
|
|
5217
|
-
return
|
|
5547
|
+
return join17(dataDir2, "mcp-as-pairing.json");
|
|
5218
5548
|
}
|
|
5219
5549
|
function readMcpPairedAsUrl(dataDir2) {
|
|
5220
5550
|
const path = trackerPath(dataDir2);
|
|
5221
|
-
if (!
|
|
5551
|
+
if (!existsSync17(path)) return null;
|
|
5222
5552
|
try {
|
|
5223
|
-
const data = JSON.parse(
|
|
5553
|
+
const data = JSON.parse(readFileSync18(path, "utf-8"));
|
|
5224
5554
|
return typeof data.asUrl === "string" ? data.asUrl : null;
|
|
5225
5555
|
} catch {
|
|
5226
5556
|
return null;
|
|
@@ -5233,7 +5563,7 @@ function writeMcpPairedAsUrl(dataDir2, asUrl) {
|
|
|
5233
5563
|
}
|
|
5234
5564
|
|
|
5235
5565
|
// bin/http.ts
|
|
5236
|
-
var dataDir = process.env.SUVEREN_DATA_DIR ??
|
|
5566
|
+
var dataDir = process.env.SUVEREN_DATA_DIR ?? join18(homedir10(), ".suveren");
|
|
5237
5567
|
var spUrl = resolveAsUrl(dataDir);
|
|
5238
5568
|
validatePinTlsForUrl(resolvePinTls(dataDir), spUrl);
|
|
5239
5569
|
var port = parseInt(process.env.SUVEREN_MCP_PORT ?? "3430", 10);
|
|
@@ -5760,7 +6090,7 @@ app.listen(port, "0.0.0.0", () => {
|
|
|
5760
6090
|
(proposal) => executeCommitted(proposal, state, integrationManager)
|
|
5761
6091
|
);
|
|
5762
6092
|
installCommittedExecutor(committedExecutor);
|
|
5763
|
-
const dataDir2 = process.env.SUVEREN_DATA_DIR ??
|
|
6093
|
+
const dataDir2 = process.env.SUVEREN_DATA_DIR ?? join18(homedir10(), ".suveren");
|
|
5764
6094
|
const executorLock = new ExecutorLock(dataDir2);
|
|
5765
6095
|
const lock = executorLock.acquire();
|
|
5766
6096
|
if (!lock.held) {
|