@suveren/gateway 0.28.1 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/content/integrations/crm.json +33 -1
- package/content/integrations/erp.json +16 -0
- package/content/integrations/gmail.json +14 -0
- package/content/integrations/mail.json +16 -1
- package/dist/control-plane/index.mjs +363 -195
- package/dist/mcp-server/http.mjs +926 -221
- package/dist/ui/assets/index-C3vTF2Ye.js +123 -0
- package/dist/ui/assets/{index-RX5juZkm.css → index-DUIlMVlP.css} +1 -1
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-I9WCdLMY.js +0 -120
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
7
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
8
|
-
import { existsSync as
|
|
9
|
-
import { dirname as
|
|
10
|
-
import { homedir as
|
|
8
|
+
import { existsSync as existsSync13, readFileSync as readFileSync15, renameSync as renameSync3 } from "fs";
|
|
9
|
+
import { dirname as dirname10, join as join15 } from "path";
|
|
10
|
+
import { homedir as homedir8 } from "os";
|
|
11
11
|
import express from "express";
|
|
12
12
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
13
13
|
|
|
@@ -309,15 +309,109 @@ var Vault = class {
|
|
|
309
309
|
}
|
|
310
310
|
};
|
|
311
311
|
|
|
312
|
-
// src/lib/
|
|
313
|
-
import {
|
|
312
|
+
// src/lib/shown-snapshot.ts
|
|
313
|
+
import { createHash as createHash2 } from "crypto";
|
|
314
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, renameSync } from "fs";
|
|
315
|
+
import { homedir as homedir2 } from "os";
|
|
314
316
|
import { dirname as dirname2, join as join2 } from "path";
|
|
317
|
+
import { canonicalize } from "@hap/core";
|
|
318
|
+
function computeShownHash(snapshot) {
|
|
319
|
+
return `sha256:${createHash2("sha256").update(canonicalize(snapshot)).digest("hex")}`;
|
|
320
|
+
}
|
|
321
|
+
var DEFAULT_DIR = process.env.SUVEREN_DATA_DIR ?? join2(homedir2(), ".suveren");
|
|
322
|
+
var FILE_NAME = "shown-snapshots.enc.json";
|
|
323
|
+
var SHOWN_SNAPSHOT_MAX_AGE_SECONDS = 90 * 24 * 60 * 60;
|
|
324
|
+
var ShownSnapshotStore = class {
|
|
325
|
+
filePath;
|
|
326
|
+
constructor(dataDir) {
|
|
327
|
+
this.filePath = join2(dataDir ?? DEFAULT_DIR, FILE_NAME);
|
|
328
|
+
}
|
|
329
|
+
/** Hashes + stores `snapshot` for `proposalId`, returning the hash to send
|
|
330
|
+
* with the decision. Requires an unlocked vault — callers already require
|
|
331
|
+
* an AS session (and therefore an unlocked vault) to reach this path.
|
|
332
|
+
* Opportunistically prunes expired entries first (doc §8.4's retention —
|
|
333
|
+
* this store has no in-memory cache to prune on every write the way
|
|
334
|
+
* `ExecutionLog` does, so a decision call is the one guaranteed moment
|
|
335
|
+
* the vault key is in hand). */
|
|
336
|
+
save(proposalId, snapshot, vault2) {
|
|
337
|
+
this.pruneWithVault(vault2);
|
|
338
|
+
const shownHash = computeShownHash(snapshot);
|
|
339
|
+
const entry = { shownHash, snapshot, storedAt: Math.floor(Date.now() / 1e3) };
|
|
340
|
+
const file = this.load();
|
|
341
|
+
file.entries[proposalId] = vault2.encrypt(JSON.stringify(entry));
|
|
342
|
+
this.persist(file);
|
|
343
|
+
return shownHash;
|
|
344
|
+
}
|
|
345
|
+
/** Returns the stored entry, or null if none exists (off-device / never
|
|
346
|
+
* approved on this gateway — doc §8.4's "only on the approving gateway"). */
|
|
347
|
+
get(proposalId, vault2) {
|
|
348
|
+
const file = this.load();
|
|
349
|
+
const blob = file.entries[proposalId];
|
|
350
|
+
if (!blob) return null;
|
|
351
|
+
try {
|
|
352
|
+
return JSON.parse(vault2.decrypt(blob));
|
|
353
|
+
} catch {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
remove(proposalId) {
|
|
358
|
+
const file = this.load();
|
|
359
|
+
if (!(proposalId in file.entries)) return;
|
|
360
|
+
delete file.entries[proposalId];
|
|
361
|
+
this.persist(file);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Drops entries older than {@link SHOWN_SNAPSHOT_MAX_AGE_SECONDS}. `storedAt`
|
|
365
|
+
* lives INSIDE each encrypted blob, so this needs the vault key — called
|
|
366
|
+
* once per unlock (same trigger as `ExecutionLog`'s own migration-on-
|
|
367
|
+
* unlock), not on a timer while locked.
|
|
368
|
+
*/
|
|
369
|
+
pruneWithVault(vault2, now = Math.floor(Date.now() / 1e3)) {
|
|
370
|
+
const file = this.load();
|
|
371
|
+
const cutoff = now - SHOWN_SNAPSHOT_MAX_AGE_SECONDS;
|
|
372
|
+
let changed = false;
|
|
373
|
+
for (const [proposalId, blob] of Object.entries(file.entries)) {
|
|
374
|
+
try {
|
|
375
|
+
const entry = JSON.parse(vault2.decrypt(blob));
|
|
376
|
+
if (entry.storedAt < cutoff) {
|
|
377
|
+
delete file.entries[proposalId];
|
|
378
|
+
changed = true;
|
|
379
|
+
}
|
|
380
|
+
} catch {
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (changed) this.persist(file);
|
|
384
|
+
}
|
|
385
|
+
// ─── Persistence ────────────────────────────────────────────────────────
|
|
386
|
+
load() {
|
|
387
|
+
if (!existsSync2(this.filePath)) return { version: 1, entries: {} };
|
|
388
|
+
try {
|
|
389
|
+
const parsed = JSON.parse(readFileSync2(this.filePath, "utf8"));
|
|
390
|
+
if (parsed?.version !== 1 || typeof parsed.entries !== "object" || parsed.entries === null) {
|
|
391
|
+
return { version: 1, entries: {} };
|
|
392
|
+
}
|
|
393
|
+
return parsed;
|
|
394
|
+
} catch {
|
|
395
|
+
return { version: 1, entries: {} };
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
persist(file) {
|
|
399
|
+
mkdirSync2(dirname2(this.filePath), { recursive: true, mode: 448 });
|
|
400
|
+
const tmp = `${this.filePath}.tmp`;
|
|
401
|
+
writeFileSync2(tmp, JSON.stringify(file, null, 2), { mode: 384 });
|
|
402
|
+
renameSync(tmp, this.filePath);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// src/lib/install-method.ts
|
|
407
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
408
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
315
409
|
var INSTALL_MARKER = "install-method.json";
|
|
316
410
|
function readInstallMarker(bundleRoot) {
|
|
317
|
-
const file =
|
|
318
|
-
if (!
|
|
411
|
+
const file = join3(bundleRoot, INSTALL_MARKER);
|
|
412
|
+
if (!existsSync3(file)) return null;
|
|
319
413
|
try {
|
|
320
|
-
const parsed = JSON.parse(
|
|
414
|
+
const parsed = JSON.parse(readFileSync3(file, "utf8"));
|
|
321
415
|
return typeof parsed.method === "string" ? parsed.method : null;
|
|
322
416
|
} catch {
|
|
323
417
|
return null;
|
|
@@ -325,13 +419,13 @@ function readInstallMarker(bundleRoot) {
|
|
|
325
419
|
}
|
|
326
420
|
function detectInstallMethod({ dir, dockerEnv, managed }) {
|
|
327
421
|
if (managed) return "managed";
|
|
328
|
-
if (readInstallMarker(
|
|
422
|
+
if (readInstallMarker(dirname3(dirname3(dir))) === "msi") return "msi";
|
|
329
423
|
if (dockerEnv) return "docker";
|
|
330
424
|
if (dir.includes("/node_modules/@suveren/gateway/")) return "npm";
|
|
331
425
|
let cursor = dir;
|
|
332
426
|
for (let i = 0; i < 8; i++) {
|
|
333
|
-
if (
|
|
334
|
-
const parent =
|
|
427
|
+
if (existsSync3(join3(cursor, ".git"))) return "dev";
|
|
428
|
+
const parent = dirname3(cursor);
|
|
335
429
|
if (parent === cursor) break;
|
|
336
430
|
cursor = parent;
|
|
337
431
|
}
|
|
@@ -625,6 +719,25 @@ async function getToolPreview(data) {
|
|
|
625
719
|
clearTimeout(timeout);
|
|
626
720
|
}
|
|
627
721
|
}
|
|
722
|
+
async function getToolRequestContext(data) {
|
|
723
|
+
const controller = new AbortController();
|
|
724
|
+
const timeout = setTimeout(() => controller.abort(), 12e3);
|
|
725
|
+
try {
|
|
726
|
+
const res = await fetch(`${MCP_BASE}/internal/request-context`, {
|
|
727
|
+
method: "POST",
|
|
728
|
+
headers: internalHeaders(),
|
|
729
|
+
body: JSON.stringify(data),
|
|
730
|
+
signal: controller.signal
|
|
731
|
+
});
|
|
732
|
+
if (!res.ok) {
|
|
733
|
+
const err = await res.json().catch(() => ({ error: "Unknown error" }));
|
|
734
|
+
throw new Error(`MCP request-context failed: ${err.error ?? res.status}`);
|
|
735
|
+
}
|
|
736
|
+
return res.json();
|
|
737
|
+
} finally {
|
|
738
|
+
clearTimeout(timeout);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
628
741
|
async function getAgentContact() {
|
|
629
742
|
const res = await fetch(`${MCP_BASE}/internal/agent-contact`, {
|
|
630
743
|
headers: internalHeaders()
|
|
@@ -759,12 +872,12 @@ async function loadOrGenerateKeyPair(vault2) {
|
|
|
759
872
|
}
|
|
760
873
|
|
|
761
874
|
// src/lib/client-version.ts
|
|
762
|
-
import { readFileSync as
|
|
763
|
-
import { join as
|
|
875
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
876
|
+
import { join as join4 } from "path";
|
|
764
877
|
var UNKNOWN = "unknown";
|
|
765
878
|
function readJson(path) {
|
|
766
879
|
try {
|
|
767
|
-
return JSON.parse(
|
|
880
|
+
return JSON.parse(readFileSync4(path, "utf8"));
|
|
768
881
|
} catch {
|
|
769
882
|
return null;
|
|
770
883
|
}
|
|
@@ -775,13 +888,13 @@ function detectGatewayVersion() {
|
|
|
775
888
|
const start = import.meta.dirname ?? process.cwd();
|
|
776
889
|
let dir = start;
|
|
777
890
|
for (let up = 0; up < 6; up++) {
|
|
778
|
-
const pkg = readJson(
|
|
891
|
+
const pkg = readJson(join4(dir, "package.json"));
|
|
779
892
|
const name = pkg?.name;
|
|
780
893
|
const version = pkg?.version;
|
|
781
894
|
if (typeof version === "string" && (name === "@suveren/gateway" || name === "suveren-gateway")) {
|
|
782
895
|
return version;
|
|
783
896
|
}
|
|
784
|
-
const parent =
|
|
897
|
+
const parent = join4(dir, "..");
|
|
785
898
|
if (parent === dir) break;
|
|
786
899
|
dir = parent;
|
|
787
900
|
}
|
|
@@ -795,17 +908,17 @@ function clientVersionHeaders() {
|
|
|
795
908
|
}
|
|
796
909
|
|
|
797
910
|
// src/lib/as-pairing.ts
|
|
798
|
-
import { createHash as
|
|
799
|
-
import { existsSync as
|
|
800
|
-
import { dirname as
|
|
911
|
+
import { createHash as createHash3 } from "crypto";
|
|
912
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
913
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
801
914
|
function pairingPath(dataDir) {
|
|
802
|
-
return
|
|
915
|
+
return join5(dataDir, "as-pairing.json");
|
|
803
916
|
}
|
|
804
917
|
function readPairing(dataDir) {
|
|
805
918
|
const path = pairingPath(dataDir);
|
|
806
|
-
if (!
|
|
919
|
+
if (!existsSync4(path)) return null;
|
|
807
920
|
try {
|
|
808
|
-
const data = JSON.parse(
|
|
921
|
+
const data = JSON.parse(readFileSync5(path, "utf-8"));
|
|
809
922
|
if (typeof data.asUrl !== "string" || typeof data.publicKeyHex !== "string") return null;
|
|
810
923
|
return {
|
|
811
924
|
asUrl: data.asUrl,
|
|
@@ -825,15 +938,15 @@ function writePairing(dataDir, asUrl, publicKeyHex, opts = {}) {
|
|
|
825
938
|
...opts.tlsSpkiPinHex ? { tlsSpkiPinHex: opts.tlsSpkiPinHex } : {}
|
|
826
939
|
};
|
|
827
940
|
const path = pairingPath(dataDir);
|
|
828
|
-
|
|
829
|
-
|
|
941
|
+
mkdirSync3(dirname4(path), { recursive: true, mode: 448 });
|
|
942
|
+
writeFileSync3(path, JSON.stringify(pairing, null, 2), { encoding: "utf-8", mode: 384 });
|
|
830
943
|
return pairing;
|
|
831
944
|
}
|
|
832
945
|
function recordTlsPin(dataDir, asUrl, tlsSpkiPinHex) {
|
|
833
946
|
const existing = readPairing(dataDir);
|
|
834
947
|
if (!existing || existing.asUrl !== asUrl) return;
|
|
835
948
|
const path = pairingPath(dataDir);
|
|
836
|
-
|
|
949
|
+
writeFileSync3(
|
|
837
950
|
path,
|
|
838
951
|
JSON.stringify({ ...existing, tlsSpkiPinHex }, null, 2),
|
|
839
952
|
{ encoding: "utf-8", mode: 384 }
|
|
@@ -842,25 +955,25 @@ function recordTlsPin(dataDir, asUrl, tlsSpkiPinHex) {
|
|
|
842
955
|
function clearPairing(dataDir) {
|
|
843
956
|
const path = pairingPath(dataDir);
|
|
844
957
|
try {
|
|
845
|
-
if (
|
|
958
|
+
if (existsSync4(path)) unlinkSync(path);
|
|
846
959
|
} catch {
|
|
847
960
|
}
|
|
848
961
|
}
|
|
849
962
|
function fingerprintOf(publicKeyHex) {
|
|
850
963
|
const raw = Buffer.from(publicKeyHex, "hex");
|
|
851
|
-
const digest =
|
|
964
|
+
const digest = createHash3("sha256").update(raw).digest("hex").toUpperCase();
|
|
852
965
|
return digest.match(/.{1,4}/g)?.join(":") ?? digest;
|
|
853
966
|
}
|
|
854
967
|
|
|
855
968
|
// src/lib/as-challenge.ts
|
|
856
969
|
import { createPublicKey, randomBytes as randomBytes2, verify as cryptoVerify } from "crypto";
|
|
857
|
-
import { canonicalize } from "@hap/core";
|
|
970
|
+
import { canonicalize as canonicalize2 } from "@hap/core";
|
|
858
971
|
|
|
859
972
|
// src/lib/as-tls-pin.ts
|
|
860
|
-
import { createHash as
|
|
973
|
+
import { createHash as createHash4 } from "crypto";
|
|
861
974
|
import { checkServerIdentity as defaultCheckServerIdentity, rootCertificates } from "tls";
|
|
862
975
|
import { Agent as HttpsAgent2 } from "https";
|
|
863
|
-
import { readFileSync as
|
|
976
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
864
977
|
import { Agent, ProxyAgent, fetch as undiciFetch } from "undici";
|
|
865
978
|
|
|
866
979
|
// src/lib/proxy-env.ts
|
|
@@ -995,7 +1108,7 @@ function effectiveCa() {
|
|
|
995
1108
|
const path = process.env.NODE_EXTRA_CA_CERTS;
|
|
996
1109
|
if (!path) return void 0;
|
|
997
1110
|
try {
|
|
998
|
-
return [...rootCertificates,
|
|
1111
|
+
return [...rootCertificates, readFileSync6(path, "utf-8")];
|
|
999
1112
|
} catch {
|
|
1000
1113
|
return void 0;
|
|
1001
1114
|
}
|
|
@@ -1023,7 +1136,7 @@ function spkiSha256Hex(cert) {
|
|
|
1023
1136
|
if (!cert.pubkey) {
|
|
1024
1137
|
throw new Error("peer certificate carries no pubkey (expected the detailed certificate object)");
|
|
1025
1138
|
}
|
|
1026
|
-
return
|
|
1139
|
+
return createHash4("sha256").update(cert.pubkey).digest("hex");
|
|
1027
1140
|
}
|
|
1028
1141
|
function formatPinFingerprint(hex) {
|
|
1029
1142
|
const upper = hex.toUpperCase();
|
|
@@ -1216,7 +1329,7 @@ var CHALLENGE_DOMAIN_PREFIX = "hap-as-challenge\0";
|
|
|
1216
1329
|
function challengeSigningBytes(unsigned) {
|
|
1217
1330
|
return Buffer.concat([
|
|
1218
1331
|
Buffer.from(CHALLENGE_DOMAIN_PREFIX, "utf-8"),
|
|
1219
|
-
Buffer.from(
|
|
1332
|
+
Buffer.from(canonicalize2({ typ: unsigned.typ, nonce: unsigned.nonce, issuedAt: unsigned.issuedAt }), "utf-8")
|
|
1220
1333
|
]);
|
|
1221
1334
|
}
|
|
1222
1335
|
function publicKeyFromHex(publicKeyHex) {
|
|
@@ -1299,12 +1412,12 @@ async function verifyAsHoldsKey(asUrl, publicKeyHex, pinning = { enforce: false,
|
|
|
1299
1412
|
}
|
|
1300
1413
|
|
|
1301
1414
|
// src/lib/as-config.ts
|
|
1302
|
-
import { existsSync as
|
|
1303
|
-
import { dirname as
|
|
1415
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
1416
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
1304
1417
|
|
|
1305
1418
|
// src/lib/policy.ts
|
|
1306
|
-
import { existsSync as
|
|
1307
|
-
import { join as
|
|
1419
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
|
|
1420
|
+
import { join as join6 } from "path";
|
|
1308
1421
|
import { spawnSync } from "child_process";
|
|
1309
1422
|
var POLICY_KEYS = ["AsUrl", "CaFile", "PinTls", "Proxy", "NoProxy", "Simulation", "InstallMethod", "Team"];
|
|
1310
1423
|
var DEFAULT_REGISTRY_BASE_KEY = "SOFTWARE\\Policies\\Suveren\\Gateway";
|
|
@@ -1339,7 +1452,7 @@ function validateAsUrl(candidate) {
|
|
|
1339
1452
|
function validateCaFile(candidate) {
|
|
1340
1453
|
const trimmed = candidate.trim();
|
|
1341
1454
|
if (!trimmed) return { ok: false, error: "The CA file path is empty." };
|
|
1342
|
-
if (!
|
|
1455
|
+
if (!existsSync5(trimmed)) return { ok: false, error: `No file at "${trimmed}".` };
|
|
1343
1456
|
return { ok: true, path: trimmed };
|
|
1344
1457
|
}
|
|
1345
1458
|
function validateTeamId(candidate) {
|
|
@@ -1410,7 +1523,7 @@ function policyFilePath() {
|
|
|
1410
1523
|
if (process.env.SUVEREN_POLICY_FILE) return process.env.SUVEREN_POLICY_FILE;
|
|
1411
1524
|
if (process.platform === "win32") {
|
|
1412
1525
|
const programData = process.env.ProgramData || process.env.PROGRAMDATA || "C:\\ProgramData";
|
|
1413
|
-
return
|
|
1526
|
+
return join6(programData, "Suveren", "gateway-policy.json");
|
|
1414
1527
|
}
|
|
1415
1528
|
if (process.platform === "darwin") {
|
|
1416
1529
|
return "/Library/Application Support/Suveren/gateway-policy.json";
|
|
@@ -1420,10 +1533,10 @@ function policyFilePath() {
|
|
|
1420
1533
|
function readPolicyFile() {
|
|
1421
1534
|
const path = policyFilePath();
|
|
1422
1535
|
const source = `the policy file (${path})`;
|
|
1423
|
-
if (!
|
|
1536
|
+
if (!existsSync5(path)) return { values: {}, source };
|
|
1424
1537
|
let data;
|
|
1425
1538
|
try {
|
|
1426
|
-
data = JSON.parse(
|
|
1539
|
+
data = JSON.parse(readFileSync7(path, "utf8"));
|
|
1427
1540
|
} catch (err) {
|
|
1428
1541
|
throw new Error(`Could not parse policy file ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1429
1542
|
}
|
|
@@ -1535,14 +1648,14 @@ function readPolicy() {
|
|
|
1535
1648
|
// src/lib/as-config.ts
|
|
1536
1649
|
var DEFAULT_AS_URL = "https://www.suveren.ai";
|
|
1537
1650
|
function configPath(dataDir) {
|
|
1538
|
-
return
|
|
1651
|
+
return join7(dataDir, "config.json");
|
|
1539
1652
|
}
|
|
1540
1653
|
function readAsConfigStrict(dataDir) {
|
|
1541
1654
|
const path = configPath(dataDir);
|
|
1542
|
-
if (!
|
|
1655
|
+
if (!existsSync6(path)) return {};
|
|
1543
1656
|
let data;
|
|
1544
1657
|
try {
|
|
1545
|
-
data = JSON.parse(
|
|
1658
|
+
data = JSON.parse(readFileSync8(path, "utf-8"));
|
|
1546
1659
|
} catch (err) {
|
|
1547
1660
|
throw new Error(
|
|
1548
1661
|
`Could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -1557,9 +1670,9 @@ function readAsConfigStrict(dataDir) {
|
|
|
1557
1670
|
}
|
|
1558
1671
|
function readAsConfig(dataDir) {
|
|
1559
1672
|
const path = configPath(dataDir);
|
|
1560
|
-
if (!
|
|
1673
|
+
if (!existsSync6(path)) return {};
|
|
1561
1674
|
try {
|
|
1562
|
-
const data = JSON.parse(
|
|
1675
|
+
const data = JSON.parse(readFileSync8(path, "utf-8"));
|
|
1563
1676
|
const out = {};
|
|
1564
1677
|
if (typeof data.asUrl === "string") out.asUrl = data.asUrl;
|
|
1565
1678
|
if (typeof data.caFile === "string") out.caFile = data.caFile;
|
|
@@ -1573,8 +1686,8 @@ function readAsConfig(dataDir) {
|
|
|
1573
1686
|
function writeAsConfig(dataDir, patch) {
|
|
1574
1687
|
const merged = { ...readAsConfig(dataDir), ...patch };
|
|
1575
1688
|
const path = configPath(dataDir);
|
|
1576
|
-
|
|
1577
|
-
|
|
1689
|
+
mkdirSync4(dirname5(path), { recursive: true, mode: 448 });
|
|
1690
|
+
writeFileSync4(path, JSON.stringify(merged, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1578
1691
|
return merged;
|
|
1579
1692
|
}
|
|
1580
1693
|
function validateAsUrl2(candidate) {
|
|
@@ -1717,17 +1830,17 @@ var eventBus = new EventBus();
|
|
|
1717
1830
|
import { spawn, spawnSync as spawnSync2 } from "child_process";
|
|
1718
1831
|
|
|
1719
1832
|
// src/lib/notifier-icon.ts
|
|
1720
|
-
import { writeFileSync as
|
|
1721
|
-
import { homedir as
|
|
1722
|
-
import { join as
|
|
1833
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync7 } from "fs";
|
|
1834
|
+
import { homedir as homedir3 } from "os";
|
|
1835
|
+
import { join as join8, dirname as dirname6 } from "path";
|
|
1723
1836
|
var ICON_BASE64 = "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";
|
|
1724
1837
|
function ensureNotifierIcon(dataDir) {
|
|
1725
1838
|
try {
|
|
1726
|
-
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ??
|
|
1727
|
-
const path =
|
|
1728
|
-
if (!
|
|
1729
|
-
|
|
1730
|
-
|
|
1839
|
+
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join8(homedir3(), ".suveren");
|
|
1840
|
+
const path = join8(dir, "notifier-icon.png");
|
|
1841
|
+
if (!existsSync7(path)) {
|
|
1842
|
+
mkdirSync5(dirname6(path), { recursive: true });
|
|
1843
|
+
writeFileSync5(path, Buffer.from(ICON_BASE64, "base64"));
|
|
1731
1844
|
}
|
|
1732
1845
|
return path;
|
|
1733
1846
|
} catch {
|
|
@@ -2892,18 +3005,18 @@ async function testAIConnectivity(config) {
|
|
|
2892
3005
|
}
|
|
2893
3006
|
|
|
2894
3007
|
// src/lib/agent-brief-store.ts
|
|
2895
|
-
import { existsSync as
|
|
2896
|
-
import { homedir as
|
|
2897
|
-
import { join as
|
|
3008
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
|
|
3009
|
+
import { homedir as homedir4 } from "os";
|
|
3010
|
+
import { join as join9 } from "path";
|
|
2898
3011
|
var AGENT_CONTEXT_MAX_BYTES = 16 * 1024;
|
|
2899
3012
|
function agentBriefPath() {
|
|
2900
|
-
const dataDir = process.env.SUVEREN_DATA_DIR ??
|
|
2901
|
-
return
|
|
3013
|
+
const dataDir = process.env.SUVEREN_DATA_DIR ?? join9(homedir4(), ".suveren");
|
|
3014
|
+
return join9(dataDir, "context.md");
|
|
2902
3015
|
}
|
|
2903
3016
|
function readAgentBrief() {
|
|
2904
3017
|
const filePath = agentBriefPath();
|
|
2905
|
-
if (!
|
|
2906
|
-
return
|
|
3018
|
+
if (!existsSync8(filePath)) return "";
|
|
3019
|
+
return readFileSync9(filePath, "utf-8");
|
|
2907
3020
|
}
|
|
2908
3021
|
|
|
2909
3022
|
// src/routes/ai.ts
|
|
@@ -3256,28 +3369,28 @@ function createMCPRouter() {
|
|
|
3256
3369
|
// src/routes/autostart.ts
|
|
3257
3370
|
import { Router as Router6 } from "express";
|
|
3258
3371
|
import { execFile } from "child_process";
|
|
3259
|
-
import { existsSync as
|
|
3372
|
+
import { existsSync as existsSync9 } from "fs";
|
|
3260
3373
|
import { dirname as pathDirname, resolve } from "path";
|
|
3261
3374
|
import { fileURLToPath } from "url";
|
|
3262
3375
|
import { promisify } from "util";
|
|
3263
3376
|
var run = promisify(execFile);
|
|
3264
3377
|
var SUPPORTED = /* @__PURE__ */ new Set(["darwin", "win32", "linux"]);
|
|
3265
3378
|
var MODULE_DIR = pathDirname(fileURLToPath(import.meta.url));
|
|
3266
|
-
function findCli(
|
|
3379
|
+
function findCli(dirname11 = MODULE_DIR) {
|
|
3267
3380
|
const candidates = [
|
|
3268
3381
|
// Bundled (the shipped layout): dist/control-plane → dist → root → bin
|
|
3269
|
-
resolve(
|
|
3382
|
+
resolve(dirname11, "..", "..", "bin", "suveren-gateway.js"),
|
|
3270
3383
|
// Bundled, were the routes/ directory ever preserved.
|
|
3271
|
-
resolve(
|
|
3384
|
+
resolve(dirname11, "..", "..", "..", "bin", "suveren-gateway.js"),
|
|
3272
3385
|
// Dev, running from source: the repo's own bundle directory.
|
|
3273
|
-
resolve(
|
|
3274
|
-
resolve(
|
|
3386
|
+
resolve(dirname11, "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js"),
|
|
3387
|
+
resolve(dirname11, "..", "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js")
|
|
3275
3388
|
// Deliberately NO process.cwd() fallback: it makes the result depend on
|
|
3276
3389
|
// where the process happened to be launched from, so the same install
|
|
3277
3390
|
// resolves differently between a shell and a login service — and it would
|
|
3278
3391
|
// mask a genuinely missing CLI whenever the cwd happened to be the repo.
|
|
3279
3392
|
];
|
|
3280
|
-
return candidates.find(
|
|
3393
|
+
return candidates.find(existsSync9) ?? null;
|
|
3281
3394
|
}
|
|
3282
3395
|
async function cli(args) {
|
|
3283
3396
|
const bin = findCli();
|
|
@@ -3430,24 +3543,24 @@ import { Router as Router9 } from "express";
|
|
|
3430
3543
|
|
|
3431
3544
|
// src/routes/approved-intents.ts
|
|
3432
3545
|
import { Router as Router8 } from "express";
|
|
3433
|
-
import { readFileSync as
|
|
3434
|
-
import { homedir as
|
|
3435
|
-
import { join as
|
|
3436
|
-
var SUVEREN_DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
3437
|
-
var FILE_PATH =
|
|
3546
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, existsSync as existsSync10, mkdirSync as mkdirSync6 } from "fs";
|
|
3547
|
+
import { homedir as homedir5 } from "os";
|
|
3548
|
+
import { join as join10 } from "path";
|
|
3549
|
+
var SUVEREN_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join10(homedir5(), ".suveren");
|
|
3550
|
+
var FILE_PATH = join10(SUVEREN_DATA_DIR, "approved-intents.enc.json");
|
|
3438
3551
|
function readFile() {
|
|
3439
|
-
if (!
|
|
3552
|
+
if (!existsSync10(FILE_PATH)) {
|
|
3440
3553
|
return { version: 1, entries: {} };
|
|
3441
3554
|
}
|
|
3442
3555
|
try {
|
|
3443
|
-
return JSON.parse(
|
|
3556
|
+
return JSON.parse(readFileSync10(FILE_PATH, "utf-8"));
|
|
3444
3557
|
} catch {
|
|
3445
3558
|
return { version: 1, entries: {} };
|
|
3446
3559
|
}
|
|
3447
3560
|
}
|
|
3448
3561
|
function writeFile(data) {
|
|
3449
|
-
|
|
3450
|
-
|
|
3562
|
+
mkdirSync6(SUVEREN_DATA_DIR, { recursive: true });
|
|
3563
|
+
writeFileSync6(FILE_PATH, JSON.stringify(data, null, 2), "utf-8");
|
|
3451
3564
|
}
|
|
3452
3565
|
function readApprovedIntents(vault2) {
|
|
3453
3566
|
const file = readFile();
|
|
@@ -3579,11 +3692,11 @@ function createEvidenceExportRouter(spUrl, vault2, dataDir, lockAsTlsMismatch2)
|
|
|
3579
3692
|
import { Router as Router10 } from "express";
|
|
3580
3693
|
|
|
3581
3694
|
// src/lib/content-crypto.ts
|
|
3582
|
-
import { createHash as
|
|
3583
|
-
import { canonicalize as
|
|
3695
|
+
import { createHash as createHash5 } from "crypto";
|
|
3696
|
+
import { canonicalize as canonicalize3 } from "@hap/core";
|
|
3584
3697
|
var CONTENT_ALG = "HPKE-X25519-HKDF-SHA256-AES256GCM";
|
|
3585
3698
|
function computeProposalContentHash(toolArgs) {
|
|
3586
|
-
return `sha256:${
|
|
3699
|
+
return `sha256:${createHash5("sha256").update(canonicalize3(toolArgs)).digest("hex")}`;
|
|
3587
3700
|
}
|
|
3588
3701
|
function toB64(bytes) {
|
|
3589
3702
|
return Buffer.from(bytes).toString("base64");
|
|
@@ -3638,7 +3751,7 @@ async function resolveRecipientPubkeys(recipientUserIds, deps) {
|
|
|
3638
3751
|
return { resolved, unreadableFor };
|
|
3639
3752
|
}
|
|
3640
3753
|
async function encryptProposalContent(toolArgs, recipientUserIds, deps) {
|
|
3641
|
-
const canonical =
|
|
3754
|
+
const canonical = canonicalize3(toolArgs);
|
|
3642
3755
|
const hash = computeProposalContentHash(toolArgs);
|
|
3643
3756
|
const { resolved, unreadableFor } = await resolveRecipientPubkeys(recipientUserIds, deps);
|
|
3644
3757
|
if (resolved.length === 0) {
|
|
@@ -3679,13 +3792,13 @@ async function decryptProposalContent(wire, myUserId, myPrivateKey) {
|
|
|
3679
3792
|
}
|
|
3680
3793
|
|
|
3681
3794
|
// src/lib/execution-journal-reader.ts
|
|
3682
|
-
import { existsSync as
|
|
3683
|
-
import { join as
|
|
3795
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
|
|
3796
|
+
import { join as join11 } from "path";
|
|
3684
3797
|
function loadJournalEntries(dataDir) {
|
|
3685
|
-
const path =
|
|
3686
|
-
if (!
|
|
3798
|
+
const path = join11(dataDir, "execution-journal.json");
|
|
3799
|
+
if (!existsSync11(path)) return [];
|
|
3687
3800
|
try {
|
|
3688
|
-
const parsed = JSON.parse(
|
|
3801
|
+
const parsed = JSON.parse(readFileSync11(path, "utf-8"));
|
|
3689
3802
|
return parsed.entries ?? [];
|
|
3690
3803
|
} catch {
|
|
3691
3804
|
return [];
|
|
@@ -3698,71 +3811,74 @@ function findLatestByProposal(dataDir, proposalId) {
|
|
|
3698
3811
|
|
|
3699
3812
|
// src/routes/proposal-preview.ts
|
|
3700
3813
|
var AS_FETCH_TIMEOUT_MS2 = 1e4;
|
|
3701
|
-
function
|
|
3702
|
-
const
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3814
|
+
async function resolveProposalToolArgsForRoute(spUrl, vault2, dataDir, lockAsTlsMismatch2, proposalId, res) {
|
|
3815
|
+
const cookie = vault2.getSpCookie();
|
|
3816
|
+
if (!cookie) {
|
|
3817
|
+
res.status(401).json({ error: "No Authority Server session \u2014 sign in to preview this proposal." });
|
|
3818
|
+
return void 0;
|
|
3819
|
+
}
|
|
3820
|
+
let proposal;
|
|
3821
|
+
try {
|
|
3822
|
+
const controller = new AbortController();
|
|
3823
|
+
const timeout = setTimeout(() => controller.abort(), AS_FETCH_TIMEOUT_MS2);
|
|
3824
|
+
const pin = readPairing(dataDir);
|
|
3825
|
+
let asStatus;
|
|
3826
|
+
let asBody;
|
|
3827
|
+
try {
|
|
3828
|
+
const { res: asRes } = await fetchAs(
|
|
3829
|
+
`${spUrl}/api/proposals/${encodeURIComponent(proposalId)}`,
|
|
3830
|
+
{ headers: { Cookie: cookie }, signal: controller.signal },
|
|
3831
|
+
{
|
|
3832
|
+
enforce: resolvePinTls(dataDir),
|
|
3833
|
+
pinnedSpkiHex: pin && pin.asUrl === spUrl ? pin.tlsSpkiPinHex : void 0
|
|
3834
|
+
}
|
|
3835
|
+
);
|
|
3836
|
+
asStatus = asRes.status;
|
|
3837
|
+
asBody = await asRes.json().catch(() => ({}));
|
|
3838
|
+
} finally {
|
|
3839
|
+
clearTimeout(timeout);
|
|
3840
|
+
}
|
|
3841
|
+
if (asStatus < 200 || asStatus >= 300 || !asBody.proposal) {
|
|
3842
|
+
res.status(asStatus >= 400 ? asStatus : 502).json({
|
|
3843
|
+
error: asBody.error ?? `Could not fetch proposal ${proposalId} (${asStatus})`
|
|
3844
|
+
});
|
|
3845
|
+
return void 0;
|
|
3846
|
+
}
|
|
3847
|
+
proposal = asBody.proposal;
|
|
3848
|
+
} catch (err) {
|
|
3849
|
+
if (err instanceof AsTlsMismatchError) {
|
|
3850
|
+
lockAsTlsMismatch2();
|
|
3851
|
+
res.status(502).json({ error: err.message });
|
|
3852
|
+
return void 0;
|
|
3709
3853
|
}
|
|
3710
|
-
|
|
3854
|
+
res.status(502).json({ error: `Authority Server unreachable: ${err instanceof Error ? err.message : String(err)}` });
|
|
3855
|
+
return void 0;
|
|
3856
|
+
}
|
|
3857
|
+
if (proposal.toolArgs) {
|
|
3858
|
+
return { tool: proposal.tool, toolArgs: proposal.toolArgs };
|
|
3859
|
+
}
|
|
3860
|
+
if (proposal.content) {
|
|
3711
3861
|
try {
|
|
3712
|
-
const
|
|
3713
|
-
const
|
|
3714
|
-
const
|
|
3715
|
-
|
|
3716
|
-
let asBody;
|
|
3717
|
-
try {
|
|
3718
|
-
const { res: asRes } = await fetchAs(
|
|
3719
|
-
`${spUrl}/api/proposals/${encodeURIComponent(proposalId)}`,
|
|
3720
|
-
{ headers: { Cookie: cookie }, signal: controller.signal },
|
|
3721
|
-
{
|
|
3722
|
-
enforce: resolvePinTls(dataDir),
|
|
3723
|
-
pinnedSpkiHex: pin && pin.asUrl === spUrl ? pin.tlsSpkiPinHex : void 0
|
|
3724
|
-
}
|
|
3725
|
-
);
|
|
3726
|
-
asStatus = asRes.status;
|
|
3727
|
-
asBody = await asRes.json().catch(() => ({}));
|
|
3728
|
-
} finally {
|
|
3729
|
-
clearTimeout(timeout);
|
|
3730
|
-
}
|
|
3731
|
-
if (asStatus < 200 || asStatus >= 300 || !asBody.proposal) {
|
|
3732
|
-
res.status(asStatus >= 400 ? asStatus : 502).json({
|
|
3733
|
-
error: asBody.error ?? `Could not fetch proposal ${proposalId} (${asStatus})`
|
|
3734
|
-
});
|
|
3735
|
-
return;
|
|
3736
|
-
}
|
|
3737
|
-
proposal = asBody.proposal;
|
|
3862
|
+
const kp = await loadOrGenerateKeyPair(vault2);
|
|
3863
|
+
const me = vault2.getSessionUser();
|
|
3864
|
+
const plaintext = await decryptProposalContent(proposal.content, me?.id ?? "", kp.privateKey);
|
|
3865
|
+
return { tool: proposal.tool, toolArgs: JSON.parse(plaintext) };
|
|
3738
3866
|
} catch (err) {
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
res.status(502).json({ error: err.message });
|
|
3742
|
-
return;
|
|
3743
|
-
}
|
|
3744
|
-
res.status(502).json({ error: `Authority Server unreachable: ${err instanceof Error ? err.message : String(err)}` });
|
|
3745
|
-
return;
|
|
3746
|
-
}
|
|
3747
|
-
let toolArgs;
|
|
3748
|
-
if (proposal.toolArgs) {
|
|
3749
|
-
toolArgs = proposal.toolArgs;
|
|
3750
|
-
} else if (proposal.content) {
|
|
3751
|
-
try {
|
|
3752
|
-
const kp = await loadOrGenerateKeyPair(vault2);
|
|
3753
|
-
const me = vault2.getSessionUser();
|
|
3754
|
-
const plaintext = await decryptProposalContent(proposal.content, me?.id ?? "", kp.privateKey);
|
|
3755
|
-
toolArgs = JSON.parse(plaintext);
|
|
3756
|
-
} catch (err) {
|
|
3757
|
-
res.json({ status: "unavailable", reason: "connector_error", message: `Could not decrypt this proposal's content on this device: ${err instanceof Error ? err.message : String(err)}` });
|
|
3758
|
-
return;
|
|
3759
|
-
}
|
|
3760
|
-
} else {
|
|
3761
|
-
res.status(502).json({ error: `Proposal ${proposalId} carries neither tool_args nor content.` });
|
|
3762
|
-
return;
|
|
3867
|
+
res.json({ status: "unavailable", reason: "connector_error", message: `Could not decrypt this proposal's content on this device: ${err instanceof Error ? err.message : String(err)}` });
|
|
3868
|
+
return void 0;
|
|
3763
3869
|
}
|
|
3870
|
+
}
|
|
3871
|
+
res.status(502).json({ error: `Proposal ${proposalId} carries neither tool_args nor content.` });
|
|
3872
|
+
return void 0;
|
|
3873
|
+
}
|
|
3874
|
+
function createProposalStatusRouter(spUrl, vault2, dataDir, lockAsTlsMismatch2, shownSnapshotStore2) {
|
|
3875
|
+
const router = Router10();
|
|
3876
|
+
router.get("/:id/preview", async (req, res) => {
|
|
3877
|
+
const proposalId = req.params.id;
|
|
3878
|
+
const resolved = await resolveProposalToolArgsForRoute(spUrl, vault2, dataDir, lockAsTlsMismatch2, proposalId, res);
|
|
3879
|
+
if (!resolved) return;
|
|
3764
3880
|
try {
|
|
3765
|
-
const result = await getToolPreview({ proposalId, tool:
|
|
3881
|
+
const result = await getToolPreview({ proposalId, tool: resolved.tool, toolArgs: resolved.toolArgs });
|
|
3766
3882
|
if (result.status !== "ok") {
|
|
3767
3883
|
res.json(result);
|
|
3768
3884
|
return;
|
|
@@ -3773,6 +3889,17 @@ function createProposalStatusRouter(spUrl, vault2, dataDir, lockAsTlsMismatch2)
|
|
|
3773
3889
|
res.status(502).json({ error: `MCP server unavailable: ${err instanceof Error ? err.message : String(err)}` });
|
|
3774
3890
|
}
|
|
3775
3891
|
});
|
|
3892
|
+
router.get("/:id/request-context", async (req, res) => {
|
|
3893
|
+
const proposalId = req.params.id;
|
|
3894
|
+
const resolved = await resolveProposalToolArgsForRoute(spUrl, vault2, dataDir, lockAsTlsMismatch2, proposalId, res);
|
|
3895
|
+
if (!resolved) return;
|
|
3896
|
+
try {
|
|
3897
|
+
const result = await getToolRequestContext({ tool: resolved.tool, toolArgs: resolved.toolArgs });
|
|
3898
|
+
res.json(result);
|
|
3899
|
+
} catch (err) {
|
|
3900
|
+
res.status(502).json({ error: `MCP server unavailable: ${err instanceof Error ? err.message : String(err)}` });
|
|
3901
|
+
}
|
|
3902
|
+
});
|
|
3776
3903
|
router.get("/:id/outcome", (req, res) => {
|
|
3777
3904
|
try {
|
|
3778
3905
|
const entry = findLatestByProposal(dataDir, req.params.id);
|
|
@@ -3802,13 +3929,53 @@ function createProposalStatusRouter(spUrl, vault2, dataDir, lockAsTlsMismatch2)
|
|
|
3802
3929
|
res.status(500).json({ error: `Could not read the execution journal: ${err instanceof Error ? err.message : String(err)}` });
|
|
3803
3930
|
}
|
|
3804
3931
|
});
|
|
3932
|
+
router.post("/:id/shown-snapshot", (req, res) => {
|
|
3933
|
+
if (!vault2.isUnlocked()) {
|
|
3934
|
+
res.status(401).json({ error: "Vault is locked." });
|
|
3935
|
+
return;
|
|
3936
|
+
}
|
|
3937
|
+
const snapshot = req.body;
|
|
3938
|
+
if (snapshot === void 0 || snapshot === null || typeof snapshot !== "object") {
|
|
3939
|
+
res.status(400).json({ error: "Request body must be the rendered snapshot (an object)." });
|
|
3940
|
+
return;
|
|
3941
|
+
}
|
|
3942
|
+
try {
|
|
3943
|
+
const shownHash = shownSnapshotStore2.save(req.params.id, snapshot, vault2);
|
|
3944
|
+
res.json({ shownHash });
|
|
3945
|
+
} catch (err) {
|
|
3946
|
+
res.status(500).json({ error: `Could not store the snapshot: ${err instanceof Error ? err.message : String(err)}` });
|
|
3947
|
+
}
|
|
3948
|
+
});
|
|
3949
|
+
router.get("/:id/shown-snapshot", (req, res) => {
|
|
3950
|
+
if (!vault2.isUnlocked()) {
|
|
3951
|
+
res.status(401).json({ error: "Vault is locked." });
|
|
3952
|
+
return;
|
|
3953
|
+
}
|
|
3954
|
+
try {
|
|
3955
|
+
const entry = shownSnapshotStore2.get(req.params.id, vault2);
|
|
3956
|
+
if (!entry) {
|
|
3957
|
+
res.json({ status: "off_device" });
|
|
3958
|
+
return;
|
|
3959
|
+
}
|
|
3960
|
+
const recomputed = computeShownHash(entry.snapshot);
|
|
3961
|
+
res.json({
|
|
3962
|
+
status: "ok",
|
|
3963
|
+
snapshot: entry.snapshot,
|
|
3964
|
+
shownHash: entry.shownHash,
|
|
3965
|
+
storedAt: entry.storedAt,
|
|
3966
|
+
verifiedLocally: recomputed === entry.shownHash
|
|
3967
|
+
});
|
|
3968
|
+
} catch (err) {
|
|
3969
|
+
res.status(500).json({ error: `Could not read the snapshot: ${err instanceof Error ? err.message : String(err)}` });
|
|
3970
|
+
}
|
|
3971
|
+
});
|
|
3805
3972
|
return router;
|
|
3806
3973
|
}
|
|
3807
3974
|
|
|
3808
3975
|
// src/routes/evidence.ts
|
|
3809
3976
|
import { Router as Router11 } from "express";
|
|
3810
3977
|
import { createPublicKey as createPublicKey2, verify as verifyEd25519 } from "crypto";
|
|
3811
|
-
import { verifyTicketSignature, encodeDidKey, canonicalize as
|
|
3978
|
+
import { verifyTicketSignature, encodeDidKey, canonicalize as canonicalize4 } from "@hap/core";
|
|
3812
3979
|
function issuerFromPublicKeyHex(publicKeyHex) {
|
|
3813
3980
|
return encodeDidKey(Buffer.from(publicKeyHex, "hex"));
|
|
3814
3981
|
}
|
|
@@ -3827,7 +3994,7 @@ function verifyPreV07TicketSignature(receipt, asPublicKeyHex) {
|
|
|
3827
3994
|
});
|
|
3828
3995
|
const sig = Buffer.from(signature, "base64");
|
|
3829
3996
|
if (sig.length !== 64) return false;
|
|
3830
|
-
return verifyEd25519(null, Buffer.from(
|
|
3997
|
+
return verifyEd25519(null, Buffer.from(canonicalize4(signed), "utf8"), key, sig);
|
|
3831
3998
|
} catch {
|
|
3832
3999
|
return false;
|
|
3833
4000
|
}
|
|
@@ -4196,7 +4363,7 @@ function createInternalContentCryptoRouter(getSecret, vault2, spUrl, dataDir) {
|
|
|
4196
4363
|
|
|
4197
4364
|
// src/lib/update-checker.ts
|
|
4198
4365
|
import { execSync } from "child_process";
|
|
4199
|
-
import { dirname as
|
|
4366
|
+
import { dirname as dirname7 } from "path";
|
|
4200
4367
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4201
4368
|
var GHCR_IMAGE = "suverenai/suveren-gateway";
|
|
4202
4369
|
var NPM_PACKAGE = "@suveren/gateway";
|
|
@@ -4262,7 +4429,7 @@ function compareSemver(a, b) {
|
|
|
4262
4429
|
return 0;
|
|
4263
4430
|
}
|
|
4264
4431
|
function checkDev() {
|
|
4265
|
-
const cwd =
|
|
4432
|
+
const cwd = dirname7(fileURLToPath2(import.meta.url));
|
|
4266
4433
|
try {
|
|
4267
4434
|
execSync("git fetch origin --quiet", { cwd, stdio: "ignore", timeout: 15e3 });
|
|
4268
4435
|
const out = execSync("git rev-list HEAD..origin/main --count", {
|
|
@@ -4335,19 +4502,19 @@ data: ${data}
|
|
|
4335
4502
|
import { Router as Router17 } from "express";
|
|
4336
4503
|
|
|
4337
4504
|
// src/lib/gateway-settings.ts
|
|
4338
|
-
import { readFileSync as
|
|
4339
|
-
import { homedir as
|
|
4340
|
-
import { join as
|
|
4505
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
|
|
4506
|
+
import { homedir as homedir6 } from "os";
|
|
4507
|
+
import { join as join12, dirname as dirname8 } from "path";
|
|
4341
4508
|
var DEFAULT_SETTINGS = {
|
|
4342
4509
|
desktopNotifications: true
|
|
4343
4510
|
};
|
|
4344
4511
|
function settingsPath(dataDir) {
|
|
4345
|
-
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ??
|
|
4346
|
-
return
|
|
4512
|
+
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join12(homedir6(), ".suveren");
|
|
4513
|
+
return join12(dir, "settings.json");
|
|
4347
4514
|
}
|
|
4348
4515
|
function readSettings(dataDir) {
|
|
4349
4516
|
try {
|
|
4350
|
-
const raw = JSON.parse(
|
|
4517
|
+
const raw = JSON.parse(readFileSync12(settingsPath(dataDir), "utf8"));
|
|
4351
4518
|
return {
|
|
4352
4519
|
desktopNotifications: typeof raw.desktopNotifications === "boolean" ? raw.desktopNotifications : DEFAULT_SETTINGS.desktopNotifications
|
|
4353
4520
|
};
|
|
@@ -4361,8 +4528,8 @@ function writeSettings(patch, dataDir) {
|
|
|
4361
4528
|
...typeof patch.desktopNotifications === "boolean" ? { desktopNotifications: patch.desktopNotifications } : {}
|
|
4362
4529
|
};
|
|
4363
4530
|
const path = settingsPath(dataDir);
|
|
4364
|
-
|
|
4365
|
-
|
|
4531
|
+
mkdirSync7(dirname8(path), { recursive: true });
|
|
4532
|
+
writeFileSync7(path, JSON.stringify(next, null, 2) + "\n", "utf8");
|
|
4366
4533
|
return next;
|
|
4367
4534
|
}
|
|
4368
4535
|
|
|
@@ -4394,20 +4561,20 @@ function createGatewaySettingsRouter() {
|
|
|
4394
4561
|
import { Router as Router18 } from "express";
|
|
4395
4562
|
|
|
4396
4563
|
// src/lib/archived-mandates-store.ts
|
|
4397
|
-
import { readFileSync as
|
|
4398
|
-
import { homedir as
|
|
4399
|
-
import { join as
|
|
4564
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, renameSync as renameSync2 } from "fs";
|
|
4565
|
+
import { homedir as homedir7 } from "os";
|
|
4566
|
+
import { join as join13, dirname as dirname9 } from "path";
|
|
4400
4567
|
var ID_PATTERN = /^[A-Za-z0-9_.:-]{1,200}$/;
|
|
4401
4568
|
function isValidMandateId(id) {
|
|
4402
4569
|
return typeof id === "string" && ID_PATTERN.test(id);
|
|
4403
4570
|
}
|
|
4404
4571
|
function storePath(dataDir) {
|
|
4405
|
-
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ??
|
|
4406
|
-
return
|
|
4572
|
+
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join13(homedir7(), ".suveren");
|
|
4573
|
+
return join13(dir, "archived-mandates.json");
|
|
4407
4574
|
}
|
|
4408
4575
|
function readArchived(dataDir) {
|
|
4409
4576
|
try {
|
|
4410
|
-
const raw = JSON.parse(
|
|
4577
|
+
const raw = JSON.parse(readFileSync13(storePath(dataDir), "utf8"));
|
|
4411
4578
|
if (!Array.isArray(raw.archived)) return [];
|
|
4412
4579
|
return [...new Set(raw.archived.filter(isValidMandateId))];
|
|
4413
4580
|
} catch {
|
|
@@ -4417,10 +4584,10 @@ function readArchived(dataDir) {
|
|
|
4417
4584
|
function writeArchived(ids, dataDir) {
|
|
4418
4585
|
const next = [...new Set(ids.filter(isValidMandateId))].sort();
|
|
4419
4586
|
const path = storePath(dataDir);
|
|
4420
|
-
|
|
4587
|
+
mkdirSync8(dirname9(path), { recursive: true });
|
|
4421
4588
|
const tmp = `${path}.tmp`;
|
|
4422
|
-
|
|
4423
|
-
|
|
4589
|
+
writeFileSync8(tmp, JSON.stringify({ archived: next }, null, 2) + "\n", "utf8");
|
|
4590
|
+
renameSync2(tmp, path);
|
|
4424
4591
|
return next;
|
|
4425
4592
|
}
|
|
4426
4593
|
function archiveMandate(id, dataDir) {
|
|
@@ -4872,17 +5039,17 @@ function buildSessionHealth(vault2) {
|
|
|
4872
5039
|
}
|
|
4873
5040
|
|
|
4874
5041
|
// src/lib/denials-reader.ts
|
|
4875
|
-
import { existsSync as
|
|
4876
|
-
import { join as
|
|
5042
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
5043
|
+
import { join as join14 } from "path";
|
|
4877
5044
|
function loadDenials(dataDir, decrypt) {
|
|
4878
|
-
const encPath =
|
|
4879
|
-
const plainPath =
|
|
4880
|
-
if (
|
|
4881
|
-
const { blob } = JSON.parse(
|
|
5045
|
+
const encPath = join14(dataDir, "denials.enc.json");
|
|
5046
|
+
const plainPath = join14(dataDir, "denials.json");
|
|
5047
|
+
if (existsSync12(encPath)) {
|
|
5048
|
+
const { blob } = JSON.parse(readFileSync14(encPath, "utf-8"));
|
|
4882
5049
|
return JSON.parse(decrypt(blob)).records ?? [];
|
|
4883
5050
|
}
|
|
4884
|
-
if (
|
|
4885
|
-
return JSON.parse(
|
|
5051
|
+
if (existsSync12(plainPath)) {
|
|
5052
|
+
return JSON.parse(readFileSync14(plainPath, "utf-8")).records ?? [];
|
|
4886
5053
|
}
|
|
4887
5054
|
return [];
|
|
4888
5055
|
}
|
|
@@ -4906,11 +5073,11 @@ function isSimulationMode() {
|
|
|
4906
5073
|
// src/index.ts
|
|
4907
5074
|
(function migrateDataDir() {
|
|
4908
5075
|
if (process.env.SUVEREN_DATA_DIR) return;
|
|
4909
|
-
const oldDir =
|
|
4910
|
-
const newDir =
|
|
4911
|
-
if (!
|
|
5076
|
+
const oldDir = join15(homedir8(), ".hap");
|
|
5077
|
+
const newDir = join15(homedir8(), ".suveren");
|
|
5078
|
+
if (!existsSync13(newDir) && existsSync13(oldDir)) {
|
|
4912
5079
|
try {
|
|
4913
|
-
|
|
5080
|
+
renameSync3(oldDir, newDir);
|
|
4914
5081
|
console.error(`[Control Plane] Migrated data directory: ${oldDir} \u2192 ${newDir}`);
|
|
4915
5082
|
} catch (err) {
|
|
4916
5083
|
console.error(`[Control Plane] FATAL: could not migrate data directory ${oldDir} \u2192 ${newDir}:`, err);
|
|
@@ -4918,7 +5085,7 @@ function isSimulationMode() {
|
|
|
4918
5085
|
}
|
|
4919
5086
|
}
|
|
4920
5087
|
})();
|
|
4921
|
-
var DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
5088
|
+
var DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join15(homedir8(), ".suveren");
|
|
4922
5089
|
var SP_URL = resolveAsUrl(DATA_DIR);
|
|
4923
5090
|
validatePinTlsForUrl(resolvePinTls(DATA_DIR), SP_URL);
|
|
4924
5091
|
var POLICY_LOCKED = [...readPolicy().locked];
|
|
@@ -4926,8 +5093,9 @@ var POLICY_LOCKED_INSTALL_METHOD = readPolicy().policy.installMethod;
|
|
|
4926
5093
|
var POLICY_LOCKED_TEAM = readPolicy().policy.team;
|
|
4927
5094
|
var port = parseInt(process.env.SUVEREN_CP_PORT ?? "3402", 10);
|
|
4928
5095
|
var HAP_MODE = process.env.HAP_MODE ?? "personal";
|
|
4929
|
-
var UI_DIST = process.env.HAP_UI_DIST ??
|
|
5096
|
+
var UI_DIST = process.env.HAP_UI_DIST ?? join15(import.meta.dirname ?? __dirname, "../../ui/dist");
|
|
4930
5097
|
var vault = new Vault(DATA_DIR);
|
|
5098
|
+
var shownSnapshotStore = new ShownSnapshotStore(DATA_DIR);
|
|
4931
5099
|
(function repairIfAsUrlChanged() {
|
|
4932
5100
|
const pairing = readPairing(DATA_DIR);
|
|
4933
5101
|
if (pairing && pairing.asUrl !== SP_URL) {
|
|
@@ -5236,7 +5404,7 @@ app.use("/api/gateway-settings", jsonParser, authGuard, createGatewaySettingsRou
|
|
|
5236
5404
|
app.use("/api/archived-mandates", jsonParser, authGuard, createArchivedMandatesRouter());
|
|
5237
5405
|
app.use("/api/encrypt-intent", jsonParser, authGuard, createEncryptIntentRouter());
|
|
5238
5406
|
app.use("/api/evidence-export", authGuard, createEvidenceExportRouter(SP_URL, vault, DATA_DIR, lockAsTlsMismatch));
|
|
5239
|
-
app.use("/proposal-status", authGuard, createProposalStatusRouter(SP_URL, vault, DATA_DIR, lockAsTlsMismatch));
|
|
5407
|
+
app.use("/proposal-status", jsonParser, authGuard, createProposalStatusRouter(SP_URL, vault, DATA_DIR, lockAsTlsMismatch, shownSnapshotStore));
|
|
5240
5408
|
app.use("/api/evidence", authGuard, createEvidenceRouter());
|
|
5241
5409
|
app.use("/api/report", jsonParser, authGuard, createReportRouter(() => RUNNING_VERSION));
|
|
5242
5410
|
app.use("/api/decrypt-intent", jsonParser, authGuard, createDecryptIntentRouter(vault));
|
|
@@ -5401,7 +5569,7 @@ app.get("/as-pairing", authGuard, (_req, res) => {
|
|
|
5401
5569
|
});
|
|
5402
5570
|
app.get("/denials", authGuard, (req, res) => {
|
|
5403
5571
|
try {
|
|
5404
|
-
const dataDir = process.env.SUVEREN_DATA_DIR ??
|
|
5572
|
+
const dataDir = process.env.SUVEREN_DATA_DIR ?? join15(homedir8(), ".suveren");
|
|
5405
5573
|
const all = loadDenials(dataDir, (blob) => vault.decrypt(blob));
|
|
5406
5574
|
const since = Number(req.query.since);
|
|
5407
5575
|
const limit = Number(req.query.limit);
|
|
@@ -5460,12 +5628,12 @@ app.put("/agent-brief/context", jsonParser, authGuard, async (req, res) => {
|
|
|
5460
5628
|
res.status(413).json({ error: `Context exceeds ${AGENT_CONTEXT_MAX_BYTES}-byte cap` });
|
|
5461
5629
|
return;
|
|
5462
5630
|
}
|
|
5463
|
-
const { mkdirSync:
|
|
5631
|
+
const { mkdirSync: mkdirSync9, writeFileSync: writeFileSync9, renameSync: renameSync4 } = await import("fs");
|
|
5464
5632
|
const filePath = agentBriefPath();
|
|
5465
|
-
|
|
5633
|
+
mkdirSync9(dirname10(filePath), { recursive: true });
|
|
5466
5634
|
const tmpPath = `${filePath}.tmp`;
|
|
5467
|
-
|
|
5468
|
-
|
|
5635
|
+
writeFileSync9(tmpPath, content, "utf-8");
|
|
5636
|
+
renameSync4(tmpPath, filePath);
|
|
5469
5637
|
res.json({ ok: true });
|
|
5470
5638
|
} catch (err) {
|
|
5471
5639
|
console.error("[Control Plane] agent-brief/context PUT failed:", err);
|
|
@@ -5547,7 +5715,7 @@ app.use(
|
|
|
5547
5715
|
function detectInstallMethod2() {
|
|
5548
5716
|
return detectInstallMethod({
|
|
5549
5717
|
dir: import.meta.dirname ?? __dirname,
|
|
5550
|
-
dockerEnv:
|
|
5718
|
+
dockerEnv: existsSync13("/.dockerenv"),
|
|
5551
5719
|
managed: process.env.SUVEREN_INSTALL_METHOD === "managed" || POLICY_LOCKED_INSTALL_METHOD === "managed"
|
|
5552
5720
|
});
|
|
5553
5721
|
}
|
|
@@ -5557,10 +5725,10 @@ function detectRunningVersion() {
|
|
|
5557
5725
|
return process.env.HAP_BUILD_SHA ?? "dev";
|
|
5558
5726
|
}
|
|
5559
5727
|
const dir = import.meta.dirname ?? __dirname;
|
|
5560
|
-
const bundlePkg =
|
|
5561
|
-
if (
|
|
5728
|
+
const bundlePkg = join15(dir, "..", "..", "package.json");
|
|
5729
|
+
if (existsSync13(bundlePkg)) {
|
|
5562
5730
|
try {
|
|
5563
|
-
const pkg = JSON.parse(
|
|
5731
|
+
const pkg = JSON.parse(readFileSync15(bundlePkg, "utf8"));
|
|
5564
5732
|
if (pkg.name === "@suveren/gateway" && typeof pkg.version === "string") {
|
|
5565
5733
|
return pkg.version;
|
|
5566
5734
|
}
|
|
@@ -5602,7 +5770,7 @@ app.get("/health", async (req, res) => {
|
|
|
5602
5770
|
}
|
|
5603
5771
|
});
|
|
5604
5772
|
});
|
|
5605
|
-
if (
|
|
5773
|
+
if (existsSync13(UI_DIST)) {
|
|
5606
5774
|
app.use(express.static(UI_DIST, {
|
|
5607
5775
|
setHeaders: (res, filePath) => {
|
|
5608
5776
|
if (filePath.endsWith("index.html")) {
|
|
@@ -5612,7 +5780,7 @@ if (existsSync12(UI_DIST)) {
|
|
|
5612
5780
|
}));
|
|
5613
5781
|
app.get("*", (_req, res) => {
|
|
5614
5782
|
res.setHeader("Cache-Control", "no-store, must-revalidate");
|
|
5615
|
-
res.sendFile(
|
|
5783
|
+
res.sendFile(join15(UI_DIST, "index.html"));
|
|
5616
5784
|
});
|
|
5617
5785
|
} else {
|
|
5618
5786
|
app.get("/", (_req, res) => {
|