@suveren/gateway 0.28.1 → 0.29.1
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 +27 -2
- package/content/integrations/mail.json +28 -3
- package/content/integrations/mollie.json +10 -0
- package/dist/control-plane/index.mjs +387 -201
- package/dist/mcp-server/http.mjs +1358 -385
- package/dist/ui/assets/{index-RX5juZkm.css → index-DUIlMVlP.css} +1 -1
- package/dist/ui/assets/index-wtX8LuiU.js +123 -0
- package/dist/ui/index.html +2 -2
- package/node_modules/sanitize-html/index.js +47 -10
- package/node_modules/sanitize-html/package.json +2 -3
- package/package.json +1 -1
- package/dist/ui/assets/index-I9WCdLMY.js +0 -120
- package/node_modules/deepmerge/.editorconfig +0 -7
- package/node_modules/deepmerge/.eslintcache +0 -1
- package/node_modules/deepmerge/changelog.md +0 -167
- package/node_modules/deepmerge/dist/cjs.js +0 -133
- package/node_modules/deepmerge/dist/umd.js +0 -139
- package/node_modules/deepmerge/index.d.ts +0 -20
- package/node_modules/deepmerge/index.js +0 -106
- package/node_modules/deepmerge/license.txt +0 -21
- package/node_modules/deepmerge/package.json +0 -42
- package/node_modules/deepmerge/readme.md +0 -264
- package/node_modules/deepmerge/rollup.config.js +0 -22
|
@@ -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) {
|
|
@@ -4458,7 +4625,7 @@ function createArchivedMandatesRouter(dataDir) {
|
|
|
4458
4625
|
// src/routes/internal-events.ts
|
|
4459
4626
|
import { Router as Router19 } from "express";
|
|
4460
4627
|
import { timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
4461
|
-
var ALLOWED = ["proposal-added", "action-approval-needed"];
|
|
4628
|
+
var ALLOWED = ["proposal-added", "action-approval-needed", "execution-failed"];
|
|
4462
4629
|
var SESSION_EXPIRED = "session-expired";
|
|
4463
4630
|
var AS_KEY_MISMATCH = "as-key-mismatch";
|
|
4464
4631
|
var AS_TLS_MISMATCH = "as-tls-mismatch";
|
|
@@ -4720,9 +4887,13 @@ function createInternalMandateRouter(getSecret, deps) {
|
|
|
4720
4887
|
}
|
|
4721
4888
|
|
|
4722
4889
|
// src/lib/notification-dispatcher.ts
|
|
4723
|
-
var
|
|
4890
|
+
var REVIEW_EVENTS = ["proposal-added", "action-approval-needed"];
|
|
4891
|
+
var FAILURE_EVENTS = ["execution-failed"];
|
|
4892
|
+
var TRIGGER_EVENTS = [...REVIEW_EVENTS, ...FAILURE_EVENTS];
|
|
4724
4893
|
var TITLE = "Suveren";
|
|
4725
4894
|
var WAITING_MESSAGE = "Something is waiting for your review.";
|
|
4895
|
+
var FAILURE_MESSAGE = "An approved action failed.";
|
|
4896
|
+
var MIXED_MESSAGE = "Something needs your attention.";
|
|
4726
4897
|
var FLOOD_THRESHOLD = 10;
|
|
4727
4898
|
var floodMessage = (n) => `Unusual volume: ${n} requests in the last minute.`;
|
|
4728
4899
|
var DEBOUNCE_MS = 5e3;
|
|
@@ -4733,6 +4904,9 @@ var NotificationDispatcher = class {
|
|
|
4733
4904
|
pendingWhileCoolingDown = false;
|
|
4734
4905
|
/** Events observed since the current window opened — the flood signal only. */
|
|
4735
4906
|
observedInWindow = 0;
|
|
4907
|
+
/** Which kinds were observed since the current window opened — selects the message. */
|
|
4908
|
+
reviewSeenInWindow = false;
|
|
4909
|
+
failureSeenInWindow = false;
|
|
4736
4910
|
unsubscribe = null;
|
|
4737
4911
|
now;
|
|
4738
4912
|
setTimeoutFn;
|
|
@@ -4750,7 +4924,8 @@ var NotificationDispatcher = class {
|
|
|
4750
4924
|
start() {
|
|
4751
4925
|
if (this.unsubscribe) return this.unsubscribe;
|
|
4752
4926
|
const off = eventBus.subscribe((event) => {
|
|
4753
|
-
if (
|
|
4927
|
+
if (FAILURE_EVENTS.includes(event.type)) this.onTrigger("failure");
|
|
4928
|
+
else if (REVIEW_EVENTS.includes(event.type)) this.onTrigger("review");
|
|
4754
4929
|
});
|
|
4755
4930
|
this.unsubscribe = () => {
|
|
4756
4931
|
off();
|
|
@@ -4762,9 +4937,15 @@ var NotificationDispatcher = class {
|
|
|
4762
4937
|
};
|
|
4763
4938
|
return this.unsubscribe;
|
|
4764
4939
|
}
|
|
4765
|
-
/**
|
|
4766
|
-
|
|
4940
|
+
/**
|
|
4941
|
+
* Exposed for tests; the bus subscription calls this. `kind` defaults to
|
|
4942
|
+
* 'review' so every existing call site/test (written before FAILURE_EVENTS
|
|
4943
|
+
* existed) keeps its exact behaviour.
|
|
4944
|
+
*/
|
|
4945
|
+
onTrigger(kind = "review") {
|
|
4767
4946
|
this.observedInWindow += 1;
|
|
4947
|
+
if (kind === "failure") this.failureSeenInWindow = true;
|
|
4948
|
+
else this.reviewSeenInWindow = true;
|
|
4768
4949
|
if (this.cooldownTimer) {
|
|
4769
4950
|
this.pendingWhileCoolingDown = true;
|
|
4770
4951
|
return;
|
|
@@ -4777,7 +4958,11 @@ var NotificationDispatcher = class {
|
|
|
4777
4958
|
}
|
|
4778
4959
|
fire() {
|
|
4779
4960
|
const count = this.observedInWindow;
|
|
4961
|
+
const sawReview = this.reviewSeenInWindow;
|
|
4962
|
+
const sawFailure = this.failureSeenInWindow;
|
|
4780
4963
|
this.observedInWindow = 0;
|
|
4964
|
+
this.reviewSeenInWindow = false;
|
|
4965
|
+
this.failureSeenInWindow = false;
|
|
4781
4966
|
let enabled = true;
|
|
4782
4967
|
try {
|
|
4783
4968
|
enabled = this.settingsFn().desktopNotifications;
|
|
@@ -4788,7 +4973,7 @@ var NotificationDispatcher = class {
|
|
|
4788
4973
|
`[Control Plane] Review pending \u2014 desktop notification ${enabled ? "sent" : "suppressed (turned off)"}`
|
|
4789
4974
|
);
|
|
4790
4975
|
if (enabled) {
|
|
4791
|
-
const message = count >= FLOOD_THRESHOLD ? floodMessage(count) : WAITING_MESSAGE;
|
|
4976
|
+
const message = count >= FLOOD_THRESHOLD ? floodMessage(count) : sawReview && sawFailure ? MIXED_MESSAGE : sawFailure ? FAILURE_MESSAGE : WAITING_MESSAGE;
|
|
4792
4977
|
this.notifyFn(TITLE, message, process.platform, this.url);
|
|
4793
4978
|
}
|
|
4794
4979
|
this.cooldownTimer = this.setTimeoutFn(() => {
|
|
@@ -4872,17 +5057,17 @@ function buildSessionHealth(vault2) {
|
|
|
4872
5057
|
}
|
|
4873
5058
|
|
|
4874
5059
|
// src/lib/denials-reader.ts
|
|
4875
|
-
import { existsSync as
|
|
4876
|
-
import { join as
|
|
5060
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
5061
|
+
import { join as join14 } from "path";
|
|
4877
5062
|
function loadDenials(dataDir, decrypt) {
|
|
4878
|
-
const encPath =
|
|
4879
|
-
const plainPath =
|
|
4880
|
-
if (
|
|
4881
|
-
const { blob } = JSON.parse(
|
|
5063
|
+
const encPath = join14(dataDir, "denials.enc.json");
|
|
5064
|
+
const plainPath = join14(dataDir, "denials.json");
|
|
5065
|
+
if (existsSync12(encPath)) {
|
|
5066
|
+
const { blob } = JSON.parse(readFileSync14(encPath, "utf-8"));
|
|
4882
5067
|
return JSON.parse(decrypt(blob)).records ?? [];
|
|
4883
5068
|
}
|
|
4884
|
-
if (
|
|
4885
|
-
return JSON.parse(
|
|
5069
|
+
if (existsSync12(plainPath)) {
|
|
5070
|
+
return JSON.parse(readFileSync14(plainPath, "utf-8")).records ?? [];
|
|
4886
5071
|
}
|
|
4887
5072
|
return [];
|
|
4888
5073
|
}
|
|
@@ -4906,11 +5091,11 @@ function isSimulationMode() {
|
|
|
4906
5091
|
// src/index.ts
|
|
4907
5092
|
(function migrateDataDir() {
|
|
4908
5093
|
if (process.env.SUVEREN_DATA_DIR) return;
|
|
4909
|
-
const oldDir =
|
|
4910
|
-
const newDir =
|
|
4911
|
-
if (!
|
|
5094
|
+
const oldDir = join15(homedir8(), ".hap");
|
|
5095
|
+
const newDir = join15(homedir8(), ".suveren");
|
|
5096
|
+
if (!existsSync13(newDir) && existsSync13(oldDir)) {
|
|
4912
5097
|
try {
|
|
4913
|
-
|
|
5098
|
+
renameSync3(oldDir, newDir);
|
|
4914
5099
|
console.error(`[Control Plane] Migrated data directory: ${oldDir} \u2192 ${newDir}`);
|
|
4915
5100
|
} catch (err) {
|
|
4916
5101
|
console.error(`[Control Plane] FATAL: could not migrate data directory ${oldDir} \u2192 ${newDir}:`, err);
|
|
@@ -4918,7 +5103,7 @@ function isSimulationMode() {
|
|
|
4918
5103
|
}
|
|
4919
5104
|
}
|
|
4920
5105
|
})();
|
|
4921
|
-
var DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
5106
|
+
var DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join15(homedir8(), ".suveren");
|
|
4922
5107
|
var SP_URL = resolveAsUrl(DATA_DIR);
|
|
4923
5108
|
validatePinTlsForUrl(resolvePinTls(DATA_DIR), SP_URL);
|
|
4924
5109
|
var POLICY_LOCKED = [...readPolicy().locked];
|
|
@@ -4926,8 +5111,9 @@ var POLICY_LOCKED_INSTALL_METHOD = readPolicy().policy.installMethod;
|
|
|
4926
5111
|
var POLICY_LOCKED_TEAM = readPolicy().policy.team;
|
|
4927
5112
|
var port = parseInt(process.env.SUVEREN_CP_PORT ?? "3402", 10);
|
|
4928
5113
|
var HAP_MODE = process.env.HAP_MODE ?? "personal";
|
|
4929
|
-
var UI_DIST = process.env.HAP_UI_DIST ??
|
|
5114
|
+
var UI_DIST = process.env.HAP_UI_DIST ?? join15(import.meta.dirname ?? __dirname, "../../ui/dist");
|
|
4930
5115
|
var vault = new Vault(DATA_DIR);
|
|
5116
|
+
var shownSnapshotStore = new ShownSnapshotStore(DATA_DIR);
|
|
4931
5117
|
(function repairIfAsUrlChanged() {
|
|
4932
5118
|
const pairing = readPairing(DATA_DIR);
|
|
4933
5119
|
if (pairing && pairing.asUrl !== SP_URL) {
|
|
@@ -5236,7 +5422,7 @@ app.use("/api/gateway-settings", jsonParser, authGuard, createGatewaySettingsRou
|
|
|
5236
5422
|
app.use("/api/archived-mandates", jsonParser, authGuard, createArchivedMandatesRouter());
|
|
5237
5423
|
app.use("/api/encrypt-intent", jsonParser, authGuard, createEncryptIntentRouter());
|
|
5238
5424
|
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));
|
|
5425
|
+
app.use("/proposal-status", jsonParser, authGuard, createProposalStatusRouter(SP_URL, vault, DATA_DIR, lockAsTlsMismatch, shownSnapshotStore));
|
|
5240
5426
|
app.use("/api/evidence", authGuard, createEvidenceRouter());
|
|
5241
5427
|
app.use("/api/report", jsonParser, authGuard, createReportRouter(() => RUNNING_VERSION));
|
|
5242
5428
|
app.use("/api/decrypt-intent", jsonParser, authGuard, createDecryptIntentRouter(vault));
|
|
@@ -5401,7 +5587,7 @@ app.get("/as-pairing", authGuard, (_req, res) => {
|
|
|
5401
5587
|
});
|
|
5402
5588
|
app.get("/denials", authGuard, (req, res) => {
|
|
5403
5589
|
try {
|
|
5404
|
-
const dataDir = process.env.SUVEREN_DATA_DIR ??
|
|
5590
|
+
const dataDir = process.env.SUVEREN_DATA_DIR ?? join15(homedir8(), ".suveren");
|
|
5405
5591
|
const all = loadDenials(dataDir, (blob) => vault.decrypt(blob));
|
|
5406
5592
|
const since = Number(req.query.since);
|
|
5407
5593
|
const limit = Number(req.query.limit);
|
|
@@ -5460,12 +5646,12 @@ app.put("/agent-brief/context", jsonParser, authGuard, async (req, res) => {
|
|
|
5460
5646
|
res.status(413).json({ error: `Context exceeds ${AGENT_CONTEXT_MAX_BYTES}-byte cap` });
|
|
5461
5647
|
return;
|
|
5462
5648
|
}
|
|
5463
|
-
const { mkdirSync:
|
|
5649
|
+
const { mkdirSync: mkdirSync9, writeFileSync: writeFileSync9, renameSync: renameSync4 } = await import("fs");
|
|
5464
5650
|
const filePath = agentBriefPath();
|
|
5465
|
-
|
|
5651
|
+
mkdirSync9(dirname10(filePath), { recursive: true });
|
|
5466
5652
|
const tmpPath = `${filePath}.tmp`;
|
|
5467
|
-
|
|
5468
|
-
|
|
5653
|
+
writeFileSync9(tmpPath, content, "utf-8");
|
|
5654
|
+
renameSync4(tmpPath, filePath);
|
|
5469
5655
|
res.json({ ok: true });
|
|
5470
5656
|
} catch (err) {
|
|
5471
5657
|
console.error("[Control Plane] agent-brief/context PUT failed:", err);
|
|
@@ -5547,7 +5733,7 @@ app.use(
|
|
|
5547
5733
|
function detectInstallMethod2() {
|
|
5548
5734
|
return detectInstallMethod({
|
|
5549
5735
|
dir: import.meta.dirname ?? __dirname,
|
|
5550
|
-
dockerEnv:
|
|
5736
|
+
dockerEnv: existsSync13("/.dockerenv"),
|
|
5551
5737
|
managed: process.env.SUVEREN_INSTALL_METHOD === "managed" || POLICY_LOCKED_INSTALL_METHOD === "managed"
|
|
5552
5738
|
});
|
|
5553
5739
|
}
|
|
@@ -5557,10 +5743,10 @@ function detectRunningVersion() {
|
|
|
5557
5743
|
return process.env.HAP_BUILD_SHA ?? "dev";
|
|
5558
5744
|
}
|
|
5559
5745
|
const dir = import.meta.dirname ?? __dirname;
|
|
5560
|
-
const bundlePkg =
|
|
5561
|
-
if (
|
|
5746
|
+
const bundlePkg = join15(dir, "..", "..", "package.json");
|
|
5747
|
+
if (existsSync13(bundlePkg)) {
|
|
5562
5748
|
try {
|
|
5563
|
-
const pkg = JSON.parse(
|
|
5749
|
+
const pkg = JSON.parse(readFileSync15(bundlePkg, "utf8"));
|
|
5564
5750
|
if (pkg.name === "@suveren/gateway" && typeof pkg.version === "string") {
|
|
5565
5751
|
return pkg.version;
|
|
5566
5752
|
}
|
|
@@ -5602,7 +5788,7 @@ app.get("/health", async (req, res) => {
|
|
|
5602
5788
|
}
|
|
5603
5789
|
});
|
|
5604
5790
|
});
|
|
5605
|
-
if (
|
|
5791
|
+
if (existsSync13(UI_DIST)) {
|
|
5606
5792
|
app.use(express.static(UI_DIST, {
|
|
5607
5793
|
setHeaders: (res, filePath) => {
|
|
5608
5794
|
if (filePath.endsWith("index.html")) {
|
|
@@ -5612,7 +5798,7 @@ if (existsSync12(UI_DIST)) {
|
|
|
5612
5798
|
}));
|
|
5613
5799
|
app.get("*", (_req, res) => {
|
|
5614
5800
|
res.setHeader("Cache-Control", "no-store, must-revalidate");
|
|
5615
|
-
res.sendFile(
|
|
5801
|
+
res.sendFile(join15(UI_DIST, "index.html"));
|
|
5616
5802
|
});
|
|
5617
5803
|
} else {
|
|
5618
5804
|
app.get("/", (_req, res) => {
|