@suveren/gateway 0.11.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -1
- package/bin/suveren-gateway.js +137 -12
- package/content/integrations/calendar.json +1 -0
- package/content/integrations/crm.json +1 -67
- package/content/integrations/deploy-github.json +3 -5
- package/content/integrations/erp.json +1 -80
- package/content/integrations/gmail.json +1 -0
- package/content/integrations/linkedin.json +1 -0
- package/content/integrations/mail.json +1 -48
- package/content/integrations/mollie.json +1 -0
- package/content/integrations/records.json +1 -0
- package/dist/control-plane/index.mjs +555 -122
- package/dist/mcp-server/http.mjs +643 -168
- package/dist/ui/assets/{index-BzcgeHlq.js → index-CmLqzgqA.js} +21 -21
- package/dist/ui/assets/{index-BFOprnFZ.css → index-y1OFeYnS.css} +1 -1
- package/dist/ui/index.html +2 -2
- package/lib/autostart-templates.d.mts +6 -0
- package/lib/autostart-templates.mjs +21 -4
- package/lib/config.d.mts +10 -0
- package/lib/config.mjs +79 -18
- package/lib/node-version.d.mts +7 -0
- package/lib/node-version.mjs +28 -0
- package/lib/policy.d.mts +34 -0
- package/lib/policy.mjs +369 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.js +9 -7
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.d.ts +13 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.js +17 -4
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.d.ts +13 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.js +23 -10
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/elicitationFormExample.js +63 -20
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/elicitationFormExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/elicitationUrlExample.js +71 -29
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/elicitationUrlExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/jsonResponseStreamableHttp.js +45 -6
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/jsonResponseStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/simpleStreamableHttp.js +71 -31
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/simpleStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/simpleTaskInteractive.js +61 -18
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/simpleTaskInteractive.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/sseAndStreamableHttpCompatibleServer.js +57 -19
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/sseAndStreamableHttpCompatibleServer.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/ssePollingExample.js +64 -17
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/ssePollingExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/standaloneSseWithGetStreamableHttp.js +61 -13
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/standaloneSseWithGetStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/interfaces.d.ts +4 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/interfaces.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/interfaces.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/stores/in-memory.d.ts +17 -8
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/stores/in-memory.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/stores/in-memory.js +31 -14
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/stores/in-memory.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/middleware/bearerAuth.d.ts +9 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/middleware/bearerAuth.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/middleware/bearerAuth.js +17 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/middleware/bearerAuth.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.d.ts +12 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js +46 -2
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/protocol.d.ts +1 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/protocol.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/protocol.js +39 -9
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/protocol.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/transport.d.ts +10 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/transport.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/transport.js +77 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/transport.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js +9 -7
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/sse.d.ts +13 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/sse.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/sse.js +18 -5
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/sse.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.d.ts +13 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js +24 -11
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/elicitationFormExample.js +63 -20
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/elicitationFormExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/elicitationUrlExample.js +71 -29
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/elicitationUrlExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/jsonResponseStreamableHttp.js +45 -6
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/jsonResponseStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/simpleStreamableHttp.js +71 -31
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/simpleStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/simpleTaskInteractive.js +61 -18
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/simpleTaskInteractive.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/sseAndStreamableHttpCompatibleServer.js +57 -19
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/sseAndStreamableHttpCompatibleServer.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/ssePollingExample.js +64 -17
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/ssePollingExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/standaloneSseWithGetStreamableHttp.js +61 -13
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/standaloneSseWithGetStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.d.ts +4 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/stores/in-memory.d.ts +17 -8
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/stores/in-memory.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/stores/in-memory.js +31 -14
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/stores/in-memory.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/middleware/bearerAuth.d.ts +9 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/middleware/bearerAuth.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/middleware/bearerAuth.js +17 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/middleware/bearerAuth.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.d.ts +12 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js +46 -2
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.d.ts +1 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js +39 -9
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.d.ts +10 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.js +72 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/package.json +1 -1
- package/package.json +2 -2
- package/server.js +60 -8
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
7
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
8
|
-
import { existsSync as
|
|
9
|
-
import { dirname as dirname8, join as
|
|
8
|
+
import { existsSync as existsSync10, readFileSync as readFileSync12, renameSync as renameSync2 } from "fs";
|
|
9
|
+
import { dirname as dirname8, join as join12 } from "path";
|
|
10
10
|
import { homedir as homedir7 } from "os";
|
|
11
11
|
import express from "express";
|
|
12
12
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
@@ -732,9 +732,138 @@ import { canonicalize } from "@hap/core";
|
|
|
732
732
|
// src/lib/as-tls-pin.ts
|
|
733
733
|
import { createHash as createHash3 } from "crypto";
|
|
734
734
|
import { checkServerIdentity as defaultCheckServerIdentity, rootCertificates } from "tls";
|
|
735
|
-
import { Agent as
|
|
735
|
+
import { Agent as HttpsAgent2 } from "https";
|
|
736
736
|
import { readFileSync as readFileSync4 } from "fs";
|
|
737
|
-
import { Agent, fetch as undiciFetch } from "undici";
|
|
737
|
+
import { Agent, ProxyAgent, fetch as undiciFetch } from "undici";
|
|
738
|
+
|
|
739
|
+
// src/lib/proxy-env.ts
|
|
740
|
+
function isLoopbackHostname(hostname) {
|
|
741
|
+
const h = hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
742
|
+
if (h === "localhost") return true;
|
|
743
|
+
if (h === "::1" || h === "0:0:0:0:0:0:0:1") return true;
|
|
744
|
+
return /^127\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.test(h);
|
|
745
|
+
}
|
|
746
|
+
function readProxyEnv() {
|
|
747
|
+
const http = process.env.http_proxy || process.env.HTTP_PROXY || void 0;
|
|
748
|
+
const https = process.env.https_proxy || process.env.HTTPS_PROXY || void 0;
|
|
749
|
+
const noProxy = process.env.no_proxy ?? process.env.NO_PROXY ?? "";
|
|
750
|
+
return { http, https, noProxy };
|
|
751
|
+
}
|
|
752
|
+
function matchesNoProxy(hostname, port2, noProxy) {
|
|
753
|
+
const entries = noProxy.split(/[,\s]+/).filter(Boolean);
|
|
754
|
+
if (entries.length === 0) return false;
|
|
755
|
+
if (entries.includes("*")) return true;
|
|
756
|
+
const h = hostname.toLowerCase();
|
|
757
|
+
for (const raw of entries) {
|
|
758
|
+
const m = raw.match(/^(.+):(\d+)$/);
|
|
759
|
+
const entryHost = (m ? m[1] : raw).replace(/^\*?\./, "").toLowerCase();
|
|
760
|
+
const entryPort = m ? Number(m[2]) : 0;
|
|
761
|
+
if (entryPort && entryPort !== port2) continue;
|
|
762
|
+
if (h === entryHost || h.endsWith(`.${entryHost}`)) return true;
|
|
763
|
+
}
|
|
764
|
+
return false;
|
|
765
|
+
}
|
|
766
|
+
function selectProxyUrl(targetUrl) {
|
|
767
|
+
const url = typeof targetUrl === "string" ? new URL(targetUrl) : targetUrl;
|
|
768
|
+
if (isLoopbackHostname(url.hostname)) return void 0;
|
|
769
|
+
const { http, https, noProxy } = readProxyEnv();
|
|
770
|
+
const port2 = url.port ? Number(url.port) : url.protocol === "https:" ? 443 : 80;
|
|
771
|
+
if (matchesNoProxy(url.hostname, port2, noProxy)) return void 0;
|
|
772
|
+
return url.protocol === "https:" ? https ?? http : http;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// src/lib/proxy-https-agent.ts
|
|
776
|
+
import { Agent as HttpsAgent } from "https";
|
|
777
|
+
import { connect as netConnect } from "net";
|
|
778
|
+
import { connect as tlsConnect } from "tls";
|
|
779
|
+
var MAX_CONNECT_RESPONSE_BYTES = 64 * 1024;
|
|
780
|
+
function openConnectTunnel(proxyUrl, host, port2) {
|
|
781
|
+
return new Promise((resolve2, reject) => {
|
|
782
|
+
const socket = netConnect({ host: proxyUrl.hostname, port: Number(proxyUrl.port) || 80 });
|
|
783
|
+
const onSocketError = (err) => {
|
|
784
|
+
socket.removeListener("data", onData);
|
|
785
|
+
reject(err);
|
|
786
|
+
};
|
|
787
|
+
socket.once("error", onSocketError);
|
|
788
|
+
socket.once("connect", () => {
|
|
789
|
+
const authHeader = proxyUrl.username ? `Proxy-Authorization: Basic ${Buffer.from(
|
|
790
|
+
`${decodeURIComponent(proxyUrl.username)}:${decodeURIComponent(proxyUrl.password)}`
|
|
791
|
+
).toString("base64")}\r
|
|
792
|
+
` : "";
|
|
793
|
+
socket.write(
|
|
794
|
+
`CONNECT ${host}:${port2} HTTP/1.1\r
|
|
795
|
+
Host: ${host}:${port2}\r
|
|
796
|
+
${authHeader}Proxy-Connection: Keep-Alive\r
|
|
797
|
+
\r
|
|
798
|
+
`
|
|
799
|
+
);
|
|
800
|
+
});
|
|
801
|
+
let buffered = Buffer.alloc(0);
|
|
802
|
+
const onData = (chunk) => {
|
|
803
|
+
buffered = Buffer.concat([buffered, chunk]);
|
|
804
|
+
const headerEnd = buffered.indexOf("\r\n\r\n");
|
|
805
|
+
if (headerEnd === -1) {
|
|
806
|
+
if (buffered.length > MAX_CONNECT_RESPONSE_BYTES) {
|
|
807
|
+
socket.removeListener("error", onSocketError);
|
|
808
|
+
socket.destroy();
|
|
809
|
+
reject(new Error(`Proxy CONNECT response to ${host}:${port2} exceeded ${MAX_CONNECT_RESPONSE_BYTES} bytes without completing`));
|
|
810
|
+
}
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
socket.removeListener("data", onData);
|
|
814
|
+
socket.removeListener("error", onSocketError);
|
|
815
|
+
const statusLine = buffered.subarray(0, buffered.indexOf("\r\n")).toString("latin1");
|
|
816
|
+
const statusCode = Number(statusLine.split(" ")[1]);
|
|
817
|
+
if (statusCode !== 200) {
|
|
818
|
+
socket.destroy();
|
|
819
|
+
reject(new Error(`Proxy CONNECT to ${host}:${port2} failed: ${statusLine.trim() || `unexpected status ${statusCode}`}`));
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
const extra = buffered.subarray(headerEnd + 4);
|
|
823
|
+
if (extra.length > 0) socket.unshift(extra);
|
|
824
|
+
resolve2(socket);
|
|
825
|
+
};
|
|
826
|
+
socket.on("data", onData);
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
var ConnectTunnelHttpsAgent = class extends HttpsAgent {
|
|
830
|
+
proxyUrl;
|
|
831
|
+
constructor(options) {
|
|
832
|
+
const { proxyUrl, ...agentOptions } = options;
|
|
833
|
+
super(agentOptions);
|
|
834
|
+
this.proxyUrl = new URL(proxyUrl);
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Matches `https.Agent`'s own `createConnection` signature exactly (see
|
|
838
|
+
* `node:https`'s type definitions) so this remains a true override — the
|
|
839
|
+
* base type's `callback` parameter is optional (a from-scratch `Agent`
|
|
840
|
+
* subclass is allowed to connect synchronously and return the stream
|
|
841
|
+
* directly instead), but every real caller in this codebase (Node's own
|
|
842
|
+
* HTTP client machinery) always supplies one, since a CONNECT tunnel is
|
|
843
|
+
* inherently asynchronous. The explicit runtime check turns a theoretical
|
|
844
|
+
* synchronous caller into a clear error instead of a silent no-op.
|
|
845
|
+
*/
|
|
846
|
+
createConnection(options, callback) {
|
|
847
|
+
if (!callback) {
|
|
848
|
+
throw new Error("ConnectTunnelHttpsAgent.createConnection requires a callback (synchronous connection creation is not supported)");
|
|
849
|
+
}
|
|
850
|
+
const host = options.host ?? "localhost";
|
|
851
|
+
const port2 = options.port != null ? Number(options.port) : 443;
|
|
852
|
+
openConnectTunnel(this.proxyUrl, host, port2).then((tunnelSocket) => {
|
|
853
|
+
const tlsSocket = tlsConnect({
|
|
854
|
+
...this.options,
|
|
855
|
+
...options,
|
|
856
|
+
socket: tunnelSocket
|
|
857
|
+
});
|
|
858
|
+
tlsSocket.once("close", () => tunnelSocket.destroy());
|
|
859
|
+
tlsSocket.once("secureConnect", () => callback(null, tlsSocket));
|
|
860
|
+
tlsSocket.once("error", (err) => callback(err, tlsSocket));
|
|
861
|
+
}).catch((err) => callback(err, void 0));
|
|
862
|
+
return void 0;
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
// src/lib/as-tls-pin.ts
|
|
738
867
|
function effectiveCa() {
|
|
739
868
|
const path = process.env.NODE_EXTRA_CA_CERTS;
|
|
740
869
|
if (!path) return void 0;
|
|
@@ -794,42 +923,47 @@ function pinnedCheckServerIdentity(pinnedSpkiHex) {
|
|
|
794
923
|
return void 0;
|
|
795
924
|
};
|
|
796
925
|
}
|
|
797
|
-
function
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
926
|
+
function buildTlsDispatcher(url, connectOpts) {
|
|
927
|
+
const proxyUrl = selectProxyUrl(url);
|
|
928
|
+
if (proxyUrl) {
|
|
929
|
+
return new ProxyAgent({ uri: proxyUrl, requestTls: connectOpts });
|
|
930
|
+
}
|
|
931
|
+
return new Agent({ connect: connectOpts });
|
|
932
|
+
}
|
|
933
|
+
function buildPinnedDispatcher(pinnedSpkiHex, url) {
|
|
934
|
+
return buildTlsDispatcher(url, {
|
|
935
|
+
ca: effectiveCa(),
|
|
936
|
+
checkServerIdentity: pinnedCheckServerIdentity(pinnedSpkiHex)
|
|
803
937
|
});
|
|
804
938
|
}
|
|
805
|
-
function buildCapturingDispatcher() {
|
|
939
|
+
function buildCapturingDispatcher(url) {
|
|
806
940
|
let captured;
|
|
807
|
-
const dispatcher =
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
return defaultErr ? taggedCheckError(defaultErr) : void 0;
|
|
814
|
-
}
|
|
941
|
+
const dispatcher = buildTlsDispatcher(url, {
|
|
942
|
+
ca: effectiveCa(),
|
|
943
|
+
checkServerIdentity: (hostname, cert) => {
|
|
944
|
+
captured = cert;
|
|
945
|
+
const defaultErr = defaultCheckServerIdentity(hostname, cert);
|
|
946
|
+
return defaultErr ? taggedCheckError(defaultErr) : void 0;
|
|
815
947
|
}
|
|
816
948
|
});
|
|
817
949
|
return { dispatcher, getCapturedCert: () => captured };
|
|
818
950
|
}
|
|
819
|
-
function buildPinnedHttpsAgent(getPinning) {
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
951
|
+
function buildPinnedHttpsAgent(getPinning, asUrl) {
|
|
952
|
+
const checkServerIdentity = (hostname, cert) => {
|
|
953
|
+
const pinning = getPinning();
|
|
954
|
+
if (!pinning.enforce) return defaultCheckServerIdentity(hostname, cert);
|
|
955
|
+
if (!pinning.pinnedSpkiHex) {
|
|
956
|
+
return taggedCheckError(new Error(
|
|
957
|
+
`pin-tls is on but no certificate is pinned yet for ${hostname} \u2014 sign in again to establish it (re-pairing required).`
|
|
958
|
+
));
|
|
959
|
+
}
|
|
960
|
+
return pinnedCheckServerIdentity(pinning.pinnedSpkiHex)(hostname, cert);
|
|
961
|
+
};
|
|
962
|
+
const proxyUrl = selectProxyUrl(asUrl);
|
|
963
|
+
if (proxyUrl) {
|
|
964
|
+
return new ConnectTunnelHttpsAgent({ proxyUrl, checkServerIdentity, ca: effectiveCa() });
|
|
965
|
+
}
|
|
966
|
+
return new HttpsAgent2({ checkServerIdentity, ca: effectiveCa() });
|
|
833
967
|
}
|
|
834
968
|
var DEFAULT_AS_FETCH_TIMEOUT_MS = 15e3;
|
|
835
969
|
function withBoundedSignal(init) {
|
|
@@ -837,6 +971,34 @@ function withBoundedSignal(init) {
|
|
|
837
971
|
const signal = init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout;
|
|
838
972
|
return { ...init, signal };
|
|
839
973
|
}
|
|
974
|
+
var UNKNOWN_ISSUER_CODES = /* @__PURE__ */ new Set([
|
|
975
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
976
|
+
"UNABLE_TO_GET_ISSUER_CERT",
|
|
977
|
+
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
|
978
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
979
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
980
|
+
"CERT_UNTRUSTED"
|
|
981
|
+
]);
|
|
982
|
+
function tlsErrorCode(err) {
|
|
983
|
+
if (!(err instanceof Error)) return void 0;
|
|
984
|
+
const cause = err.cause;
|
|
985
|
+
const withCode = cause instanceof Error ? cause : err;
|
|
986
|
+
return withCode.code;
|
|
987
|
+
}
|
|
988
|
+
function isUnknownIssuerError(err) {
|
|
989
|
+
const code = tlsErrorCode(err);
|
|
990
|
+
return !!code && UNKNOWN_ISSUER_CODES.has(code);
|
|
991
|
+
}
|
|
992
|
+
function rethrowWithIssuerHint(err, url) {
|
|
993
|
+
if (isUnknownIssuerError(err)) {
|
|
994
|
+
const hinted = new Error(
|
|
995
|
+
`Could not verify the certificate presented for ${url} \u2014 its issuer is not trusted (${tlsErrorCode(err)}). This is the typical signature of a company network that inspects HTTPS traffic with its own root certificate: trust it with \`suveren-gateway config set ca-file <path-to-company-root.pem>\` (or --ca-file / NODE_EXTRA_CA_CERTS), then try again. Original error: ${describeFetchError(err)}`
|
|
996
|
+
);
|
|
997
|
+
hinted.cause = err;
|
|
998
|
+
throw hinted;
|
|
999
|
+
}
|
|
1000
|
+
throw err;
|
|
1001
|
+
}
|
|
840
1002
|
function closeAfter(dispatcher, pending) {
|
|
841
1003
|
pending.catch(() => {
|
|
842
1004
|
}).finally(() => {
|
|
@@ -846,8 +1008,24 @@ function closeAfter(dispatcher, pending) {
|
|
|
846
1008
|
}
|
|
847
1009
|
async function fetchAs(url, init, pinning) {
|
|
848
1010
|
if (!pinning.enforce && !pinning.capture) {
|
|
849
|
-
const
|
|
850
|
-
|
|
1011
|
+
const proxyUrl = selectProxyUrl(url);
|
|
1012
|
+
if (!proxyUrl) {
|
|
1013
|
+
try {
|
|
1014
|
+
const res2 = await fetch(url, withBoundedSignal(init));
|
|
1015
|
+
return { res: res2 };
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
rethrowWithIssuerHint(err, url);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
const dispatcher2 = new ProxyAgent({ uri: proxyUrl, requestTls: { ca: effectiveCa() } });
|
|
1021
|
+
const pending2 = undiciFetch(url, { ...withBoundedSignal(init), dispatcher: dispatcher2 });
|
|
1022
|
+
closeAfter(dispatcher2, pending2);
|
|
1023
|
+
try {
|
|
1024
|
+
const res2 = await pending2;
|
|
1025
|
+
return { res: res2 };
|
|
1026
|
+
} catch (err) {
|
|
1027
|
+
rethrowWithIssuerHint(err, url);
|
|
1028
|
+
}
|
|
851
1029
|
}
|
|
852
1030
|
if (pinning.enforce && !pinning.capture && !pinning.pinnedSpkiHex) {
|
|
853
1031
|
throw new AsTlsMismatchError(
|
|
@@ -855,27 +1033,27 @@ async function fetchAs(url, init, pinning) {
|
|
|
855
1033
|
);
|
|
856
1034
|
}
|
|
857
1035
|
if (pinning.pinnedSpkiHex && (pinning.enforce || !pinning.capture)) {
|
|
858
|
-
const dispatcher2 = buildPinnedDispatcher(pinning.pinnedSpkiHex);
|
|
1036
|
+
const dispatcher2 = buildPinnedDispatcher(pinning.pinnedSpkiHex, url);
|
|
859
1037
|
const pending2 = undiciFetch(url, { ...withBoundedSignal(init), dispatcher: dispatcher2 });
|
|
860
1038
|
closeAfter(dispatcher2, pending2);
|
|
861
1039
|
try {
|
|
862
1040
|
const res2 = await pending2;
|
|
863
1041
|
return { res: res2 };
|
|
864
1042
|
} catch (err) {
|
|
865
|
-
if (!isPinCheckError(err))
|
|
1043
|
+
if (!isPinCheckError(err)) rethrowWithIssuerHint(err, url);
|
|
866
1044
|
throw new AsTlsMismatchError(
|
|
867
|
-
`TLS pin check failed for ${url} \u2014 ${describeFetchError(err)}. Refusing the connection; the API key / session cookie were never sent.
|
|
1045
|
+
`TLS pin check failed for ${url} \u2014 ${describeFetchError(err)}. Refusing the connection; the API key / session cookie were never sent. Two things produce this: (1) the Authority Server's TLS certificate genuinely changed (a new signing key, not just a renewal) \u2014 clear the pairing and sign in again to re-pin it; or (2) a TLS-inspecting proxy on this network is re-signing traffic with its own certificate \u2014 ask network/IT to exempt the Authority Server's host from TLS inspection (pin-tls refusing this, even with a trusted --ca-file, is working as intended).`
|
|
868
1046
|
);
|
|
869
1047
|
}
|
|
870
1048
|
}
|
|
871
|
-
const { dispatcher, getCapturedCert } = buildCapturingDispatcher();
|
|
1049
|
+
const { dispatcher, getCapturedCert } = buildCapturingDispatcher(url);
|
|
872
1050
|
const pending = undiciFetch(url, { ...withBoundedSignal(init), dispatcher });
|
|
873
1051
|
closeAfter(dispatcher, pending);
|
|
874
1052
|
let res;
|
|
875
1053
|
try {
|
|
876
1054
|
res = await pending;
|
|
877
1055
|
} catch (err) {
|
|
878
|
-
if (!isPinCheckError(err))
|
|
1056
|
+
if (!isPinCheckError(err)) rethrowWithIssuerHint(err, url);
|
|
879
1057
|
throw new AsTlsMismatchError(
|
|
880
1058
|
`could not verify the Authority Server at ${url} while establishing its TLS pin \u2014 ${describeFetchError(err)}.`
|
|
881
1059
|
);
|
|
@@ -994,18 +1172,237 @@ async function verifyAsHoldsKey(asUrl, publicKeyHex, pinning = { enforce: false,
|
|
|
994
1172
|
}
|
|
995
1173
|
|
|
996
1174
|
// src/lib/as-config.ts
|
|
997
|
-
import { existsSync as
|
|
998
|
-
import { dirname as dirname3, join as
|
|
1175
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
1176
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
1177
|
+
|
|
1178
|
+
// src/lib/policy.ts
|
|
1179
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
1180
|
+
import { join as join4 } from "path";
|
|
1181
|
+
import { spawnSync } from "child_process";
|
|
1182
|
+
var POLICY_KEYS = ["AsUrl", "CaFile", "PinTls", "Proxy", "NoProxy", "Simulation", "InstallMethod"];
|
|
1183
|
+
var DEFAULT_REGISTRY_BASE_KEY = "SOFTWARE\\Policies\\Suveren\\Gateway";
|
|
1184
|
+
function registryKeyPath(hive) {
|
|
1185
|
+
const baseKey = process.env.SUVEREN_POLICY_REGISTRY_KEY || DEFAULT_REGISTRY_BASE_KEY;
|
|
1186
|
+
return `${hive}\\${baseKey}`;
|
|
1187
|
+
}
|
|
1188
|
+
function validateAsUrl(candidate) {
|
|
1189
|
+
const trimmed = candidate.trim();
|
|
1190
|
+
if (!trimmed) return { ok: false, error: "The Authority Server URL is empty." };
|
|
1191
|
+
let parsed;
|
|
1192
|
+
try {
|
|
1193
|
+
parsed = new URL(trimmed);
|
|
1194
|
+
} catch {
|
|
1195
|
+
return { ok: false, error: `"${trimmed}" is not a valid URL.` };
|
|
1196
|
+
}
|
|
1197
|
+
const isLocalHttp = parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1");
|
|
1198
|
+
if (parsed.protocol !== "https:" && !isLocalHttp) {
|
|
1199
|
+
return {
|
|
1200
|
+
ok: false,
|
|
1201
|
+
error: `"${trimmed}" must use https:// (http:// is only allowed for http://localhost or http://127.0.0.1).`
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
let normalized = `${parsed.origin}${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
1205
|
+
if (normalized.endsWith("/") && normalized !== `${parsed.origin}/`) {
|
|
1206
|
+
normalized = normalized.slice(0, -1);
|
|
1207
|
+
} else if (normalized === `${parsed.origin}/`) {
|
|
1208
|
+
normalized = parsed.origin;
|
|
1209
|
+
}
|
|
1210
|
+
return { ok: true, url: normalized };
|
|
1211
|
+
}
|
|
1212
|
+
function validateCaFile(candidate) {
|
|
1213
|
+
const trimmed = candidate.trim();
|
|
1214
|
+
if (!trimmed) return { ok: false, error: "The CA file path is empty." };
|
|
1215
|
+
if (!existsSync3(trimmed)) return { ok: false, error: `No file at "${trimmed}".` };
|
|
1216
|
+
return { ok: true, path: trimmed };
|
|
1217
|
+
}
|
|
1218
|
+
function validateProxyUrl(candidate) {
|
|
1219
|
+
const trimmed = candidate.trim();
|
|
1220
|
+
if (!trimmed) return { ok: false, error: "The proxy URL is empty." };
|
|
1221
|
+
let parsed;
|
|
1222
|
+
try {
|
|
1223
|
+
parsed = new URL(trimmed);
|
|
1224
|
+
} catch {
|
|
1225
|
+
return { ok: false, error: `"${trimmed}" is not a valid URL.` };
|
|
1226
|
+
}
|
|
1227
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
1228
|
+
return { ok: false, error: `"${trimmed}" must use http:// or https://.` };
|
|
1229
|
+
}
|
|
1230
|
+
return { ok: true, url: trimmed };
|
|
1231
|
+
}
|
|
1232
|
+
function coerceBoolean(value) {
|
|
1233
|
+
if (typeof value === "boolean") return value;
|
|
1234
|
+
if (typeof value === "number") {
|
|
1235
|
+
if (value === 0) return false;
|
|
1236
|
+
if (value === 1) return true;
|
|
1237
|
+
return void 0;
|
|
1238
|
+
}
|
|
1239
|
+
if (typeof value === "string") {
|
|
1240
|
+
const v = value.trim().toLowerCase();
|
|
1241
|
+
if (v === "true" || v === "1") return true;
|
|
1242
|
+
if (v === "false" || v === "0") return false;
|
|
1243
|
+
}
|
|
1244
|
+
return void 0;
|
|
1245
|
+
}
|
|
1246
|
+
function parseRegQueryOutput(stdout) {
|
|
1247
|
+
const out = {};
|
|
1248
|
+
const lineRe = /^ {4}(\S+)\s+(REG_SZ|REG_EXPAND_SZ|REG_DWORD|REG_QWORD|REG_BINARY|REG_MULTI_SZ)\s+(.*)$/;
|
|
1249
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
1250
|
+
const m = lineRe.exec(line);
|
|
1251
|
+
if (!m) continue;
|
|
1252
|
+
const [, name, type, rawValue] = m;
|
|
1253
|
+
if (!POLICY_KEYS.includes(name)) continue;
|
|
1254
|
+
const key = name;
|
|
1255
|
+
if (type === "REG_DWORD" || type === "REG_QWORD") {
|
|
1256
|
+
out[key] = parseInt(rawValue, 16);
|
|
1257
|
+
} else {
|
|
1258
|
+
out[key] = rawValue;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
return out;
|
|
1262
|
+
}
|
|
1263
|
+
function readRegistryHive(hive) {
|
|
1264
|
+
const path = registryKeyPath(hive);
|
|
1265
|
+
const source = `the registry policy (${path})`;
|
|
1266
|
+
if (process.env.SUVEREN_POLICY_REGISTRY === "off") return { values: {}, source };
|
|
1267
|
+
if (process.platform !== "win32") return { values: {}, source };
|
|
1268
|
+
let result;
|
|
1269
|
+
try {
|
|
1270
|
+
result = spawnSync("reg", ["query", path], { encoding: "utf8" });
|
|
1271
|
+
} catch {
|
|
1272
|
+
return { values: {}, source };
|
|
1273
|
+
}
|
|
1274
|
+
if (!result || result.status !== 0 || !result.stdout) return { values: {}, source };
|
|
1275
|
+
return { values: parseRegQueryOutput(result.stdout), source };
|
|
1276
|
+
}
|
|
1277
|
+
function policyFilePath() {
|
|
1278
|
+
if (process.env.SUVEREN_POLICY_FILE) return process.env.SUVEREN_POLICY_FILE;
|
|
1279
|
+
if (process.platform === "win32") {
|
|
1280
|
+
const programData = process.env.ProgramData || process.env.PROGRAMDATA || "C:\\ProgramData";
|
|
1281
|
+
return join4(programData, "Suveren", "gateway-policy.json");
|
|
1282
|
+
}
|
|
1283
|
+
if (process.platform === "darwin") {
|
|
1284
|
+
return "/Library/Application Support/Suveren/gateway-policy.json";
|
|
1285
|
+
}
|
|
1286
|
+
return "/etc/suveren/gateway-policy.json";
|
|
1287
|
+
}
|
|
1288
|
+
function readPolicyFile() {
|
|
1289
|
+
const path = policyFilePath();
|
|
1290
|
+
const source = `the policy file (${path})`;
|
|
1291
|
+
if (!existsSync3(path)) return { values: {}, source };
|
|
1292
|
+
let data;
|
|
1293
|
+
try {
|
|
1294
|
+
data = JSON.parse(readFileSync5(path, "utf8"));
|
|
1295
|
+
} catch (err) {
|
|
1296
|
+
throw new Error(`Could not parse policy file ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1297
|
+
}
|
|
1298
|
+
const values = {};
|
|
1299
|
+
for (const key of POLICY_KEYS) {
|
|
1300
|
+
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
1301
|
+
values[key] = data[key];
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
return { values, source };
|
|
1305
|
+
}
|
|
1306
|
+
function mergeBySource(entries) {
|
|
1307
|
+
const merged = {};
|
|
1308
|
+
const sourceOf = {};
|
|
1309
|
+
for (const key of POLICY_KEYS) {
|
|
1310
|
+
for (const entry of entries) {
|
|
1311
|
+
if (Object.prototype.hasOwnProperty.call(entry.values, key) && entry.values[key] !== void 0) {
|
|
1312
|
+
merged[key] = entry.values[key];
|
|
1313
|
+
sourceOf[key] = entry.source;
|
|
1314
|
+
break;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
return { merged, sourceOf };
|
|
1319
|
+
}
|
|
1320
|
+
var cached = null;
|
|
1321
|
+
function readPolicy() {
|
|
1322
|
+
if (cached) return cached;
|
|
1323
|
+
const hklm = readRegistryHive("HKLM");
|
|
1324
|
+
const hkcu = readRegistryHive("HKCU");
|
|
1325
|
+
const file = readPolicyFile();
|
|
1326
|
+
const { merged, sourceOf } = mergeBySource([hklm, hkcu, file]);
|
|
1327
|
+
const policy = {};
|
|
1328
|
+
const locked = /* @__PURE__ */ new Set();
|
|
1329
|
+
if (merged.AsUrl !== void 0) {
|
|
1330
|
+
const v = validateAsUrl(String(merged.AsUrl));
|
|
1331
|
+
if (!v.ok) {
|
|
1332
|
+
throw new Error(`Invalid policy AsUrl ("${merged.AsUrl}") from ${sourceOf.AsUrl}: ${v.error}`);
|
|
1333
|
+
}
|
|
1334
|
+
policy.asUrl = v.url;
|
|
1335
|
+
locked.add("asUrl");
|
|
1336
|
+
}
|
|
1337
|
+
if (merged.CaFile !== void 0) {
|
|
1338
|
+
const v = validateCaFile(String(merged.CaFile));
|
|
1339
|
+
if (!v.ok) {
|
|
1340
|
+
throw new Error(`Invalid policy CaFile ("${merged.CaFile}") from ${sourceOf.CaFile}: ${v.error}`);
|
|
1341
|
+
}
|
|
1342
|
+
policy.caFile = v.path;
|
|
1343
|
+
locked.add("caFile");
|
|
1344
|
+
}
|
|
1345
|
+
if (merged.PinTls !== void 0) {
|
|
1346
|
+
const b = coerceBoolean(merged.PinTls);
|
|
1347
|
+
if (b === void 0) {
|
|
1348
|
+
throw new Error(
|
|
1349
|
+
`Invalid policy PinTls ("${String(merged.PinTls)}") from ${sourceOf.PinTls}: must be a boolean or 0/1.`
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
policy.pinTls = b;
|
|
1353
|
+
locked.add("pinTls");
|
|
1354
|
+
}
|
|
1355
|
+
if (merged.Proxy !== void 0) {
|
|
1356
|
+
const v = validateProxyUrl(String(merged.Proxy));
|
|
1357
|
+
if (!v.ok) {
|
|
1358
|
+
throw new Error(`Invalid policy Proxy ("${String(merged.Proxy)}") from ${sourceOf.Proxy}: ${v.error}`);
|
|
1359
|
+
}
|
|
1360
|
+
policy.proxy = v.url;
|
|
1361
|
+
locked.add("proxy");
|
|
1362
|
+
}
|
|
1363
|
+
if (merged.NoProxy !== void 0) {
|
|
1364
|
+
const raw = String(merged.NoProxy).trim();
|
|
1365
|
+
if (!raw) {
|
|
1366
|
+
throw new Error(`Invalid policy NoProxy ("${String(merged.NoProxy)}") from ${sourceOf.NoProxy}: must not be empty.`);
|
|
1367
|
+
}
|
|
1368
|
+
policy.noProxy = raw;
|
|
1369
|
+
locked.add("noProxy");
|
|
1370
|
+
}
|
|
1371
|
+
if (merged.Simulation !== void 0) {
|
|
1372
|
+
const b = coerceBoolean(merged.Simulation);
|
|
1373
|
+
if (b === void 0) {
|
|
1374
|
+
throw new Error(
|
|
1375
|
+
`Invalid policy Simulation ("${String(merged.Simulation)}") from ${sourceOf.Simulation}: must be a boolean or 0/1.`
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
policy.simulation = b;
|
|
1379
|
+
locked.add("simulation");
|
|
1380
|
+
}
|
|
1381
|
+
if (merged.InstallMethod !== void 0) {
|
|
1382
|
+
const raw = String(merged.InstallMethod).trim().toLowerCase();
|
|
1383
|
+
if (raw !== "managed") {
|
|
1384
|
+
throw new Error(
|
|
1385
|
+
`Invalid policy InstallMethod ("${String(merged.InstallMethod)}") from ${sourceOf.InstallMethod}: only "managed" is supported.`
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
policy.installMethod = "managed";
|
|
1389
|
+
locked.add("installMethod");
|
|
1390
|
+
}
|
|
1391
|
+
cached = { policy, locked };
|
|
1392
|
+
return cached;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// src/lib/as-config.ts
|
|
999
1396
|
var DEFAULT_AS_URL = "https://www.suveren.ai";
|
|
1000
1397
|
function configPath(dataDir) {
|
|
1001
|
-
return
|
|
1398
|
+
return join5(dataDir, "config.json");
|
|
1002
1399
|
}
|
|
1003
1400
|
function readAsConfigStrict(dataDir) {
|
|
1004
1401
|
const path = configPath(dataDir);
|
|
1005
|
-
if (!
|
|
1402
|
+
if (!existsSync4(path)) return {};
|
|
1006
1403
|
let data;
|
|
1007
1404
|
try {
|
|
1008
|
-
data = JSON.parse(
|
|
1405
|
+
data = JSON.parse(readFileSync6(path, "utf-8"));
|
|
1009
1406
|
} catch (err) {
|
|
1010
1407
|
throw new Error(
|
|
1011
1408
|
`Could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -1020,9 +1417,9 @@ function readAsConfigStrict(dataDir) {
|
|
|
1020
1417
|
}
|
|
1021
1418
|
function readAsConfig(dataDir) {
|
|
1022
1419
|
const path = configPath(dataDir);
|
|
1023
|
-
if (!
|
|
1420
|
+
if (!existsSync4(path)) return {};
|
|
1024
1421
|
try {
|
|
1025
|
-
const data = JSON.parse(
|
|
1422
|
+
const data = JSON.parse(readFileSync6(path, "utf-8"));
|
|
1026
1423
|
const out = {};
|
|
1027
1424
|
if (typeof data.asUrl === "string") out.asUrl = data.asUrl;
|
|
1028
1425
|
if (typeof data.caFile === "string") out.caFile = data.caFile;
|
|
@@ -1040,7 +1437,7 @@ function writeAsConfig(dataDir, patch) {
|
|
|
1040
1437
|
writeFileSync3(path, JSON.stringify(merged, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1041
1438
|
return merged;
|
|
1042
1439
|
}
|
|
1043
|
-
function
|
|
1440
|
+
function validateAsUrl2(candidate) {
|
|
1044
1441
|
const trimmed = candidate.trim();
|
|
1045
1442
|
if (!trimmed) return { ok: false, error: "The Authority Server URL is empty." };
|
|
1046
1443
|
let parsed;
|
|
@@ -1065,26 +1462,30 @@ function validateAsUrl(candidate) {
|
|
|
1065
1462
|
return { ok: true, url: normalized };
|
|
1066
1463
|
}
|
|
1067
1464
|
function resolveAsUrl(dataDir, flag) {
|
|
1465
|
+
const policyUrl = readPolicy().policy.asUrl;
|
|
1466
|
+
if (policyUrl) return policyUrl;
|
|
1068
1467
|
if (flag) {
|
|
1069
|
-
const v =
|
|
1468
|
+
const v = validateAsUrl2(flag);
|
|
1070
1469
|
if (!v.ok) throw new Error(`Invalid --as-url: ${v.error}`);
|
|
1071
1470
|
return v.url;
|
|
1072
1471
|
}
|
|
1073
1472
|
const envUrl = process.env.SUVEREN_AS_URL;
|
|
1074
1473
|
if (envUrl) {
|
|
1075
|
-
const v =
|
|
1474
|
+
const v = validateAsUrl2(envUrl);
|
|
1076
1475
|
if (!v.ok) throw new Error(`Invalid SUVEREN_AS_URL: ${v.error}`);
|
|
1077
1476
|
return v.url;
|
|
1078
1477
|
}
|
|
1079
1478
|
const saved = readAsConfigStrict(dataDir).asUrl;
|
|
1080
1479
|
if (saved) {
|
|
1081
|
-
const v =
|
|
1480
|
+
const v = validateAsUrl2(saved);
|
|
1082
1481
|
if (!v.ok) throw new Error(`Invalid saved as-url "${saved}" in ${dataDir}/config.json: ${v.error}`);
|
|
1083
1482
|
return v.url;
|
|
1084
1483
|
}
|
|
1085
1484
|
return DEFAULT_AS_URL;
|
|
1086
1485
|
}
|
|
1087
1486
|
function resolvePinTls(dataDir) {
|
|
1487
|
+
const policyPinTls = readPolicy().policy.pinTls;
|
|
1488
|
+
if (policyPinTls !== void 0) return policyPinTls;
|
|
1088
1489
|
return readAsConfig(dataDir).pinTls === true;
|
|
1089
1490
|
}
|
|
1090
1491
|
function validatePinTlsForUrl(pinTls, asUrl) {
|
|
@@ -1107,7 +1508,7 @@ function clearPinTlsExpectedFingerprint(dataDir) {
|
|
|
1107
1508
|
var DEFAULT_SP_URL = process.env.SUVEREN_AS_URL ?? "https://www.suveren.ai";
|
|
1108
1509
|
async function fetchAsPublicKey(asUrl) {
|
|
1109
1510
|
try {
|
|
1110
|
-
const res = await
|
|
1511
|
+
const { res } = await fetchAs(`${asUrl}/api/as/pubkey`, { signal: AbortSignal.timeout(5e3) }, { enforce: false });
|
|
1111
1512
|
if (!res.ok) {
|
|
1112
1513
|
return {
|
|
1113
1514
|
ok: false,
|
|
@@ -1465,6 +1866,21 @@ function credentialView(cred, textFields) {
|
|
|
1465
1866
|
};
|
|
1466
1867
|
}
|
|
1467
1868
|
|
|
1869
|
+
// src/lib/proxied-fetch.ts
|
|
1870
|
+
import { ProxyAgent as ProxyAgent2, fetch as undiciFetch2 } from "undici";
|
|
1871
|
+
function proxiedFetch(url, init) {
|
|
1872
|
+
const proxyUrl = selectProxyUrl(url);
|
|
1873
|
+
if (!proxyUrl) return fetch(url, init);
|
|
1874
|
+
const dispatcher = new ProxyAgent2({ uri: proxyUrl });
|
|
1875
|
+
const pending = undiciFetch2(url, { ...init, dispatcher });
|
|
1876
|
+
pending.catch(() => {
|
|
1877
|
+
}).finally(() => {
|
|
1878
|
+
void dispatcher.close().catch(() => {
|
|
1879
|
+
});
|
|
1880
|
+
});
|
|
1881
|
+
return pending;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1468
1884
|
// src/routes/vault.ts
|
|
1469
1885
|
var manifestCache = null;
|
|
1470
1886
|
async function cachedManifests() {
|
|
@@ -1573,12 +1989,12 @@ function createVaultRouter(vault2) {
|
|
|
1573
1989
|
const headers = {};
|
|
1574
1990
|
if (cred.apiKey) headers["Authorization"] = `Bearer ${cred.apiKey}`;
|
|
1575
1991
|
if (cred.provider === "ollama") {
|
|
1576
|
-
const r = await
|
|
1992
|
+
const r = await proxiedFetch(`${cred.endpoint}/api/tags`, {
|
|
1577
1993
|
signal: AbortSignal.timeout(3e3)
|
|
1578
1994
|
});
|
|
1579
1995
|
if (!r.ok) throw new Error(`Ollama: ${r.status}`);
|
|
1580
1996
|
} else {
|
|
1581
|
-
const r = await
|
|
1997
|
+
const r = await proxiedFetch(`${cred.endpoint}/models`, {
|
|
1582
1998
|
headers,
|
|
1583
1999
|
signal: AbortSignal.timeout(3e3)
|
|
1584
2000
|
});
|
|
@@ -1697,7 +2113,7 @@ ${contextParts.join("\n")}` : "",
|
|
|
1697
2113
|
try {
|
|
1698
2114
|
let suggestion;
|
|
1699
2115
|
if (config.provider === "ollama") {
|
|
1700
|
-
const response = await
|
|
2116
|
+
const response = await proxiedFetch(`${config.endpoint}/api/chat`, {
|
|
1701
2117
|
method: "POST",
|
|
1702
2118
|
headers: { "Content-Type": "application/json" },
|
|
1703
2119
|
body: JSON.stringify({
|
|
@@ -1714,7 +2130,7 @@ ${contextParts.join("\n")}` : "",
|
|
|
1714
2130
|
} else {
|
|
1715
2131
|
const headers = { "Content-Type": "application/json" };
|
|
1716
2132
|
if (config.apiKey) headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
1717
|
-
const response = await
|
|
2133
|
+
const response = await proxiedFetch(`${config.endpoint}/chat/completions`, {
|
|
1718
2134
|
method: "POST",
|
|
1719
2135
|
headers,
|
|
1720
2136
|
body: JSON.stringify({
|
|
@@ -1827,7 +2243,7 @@ async function getAIChatResponse(config, request) {
|
|
|
1827
2243
|
try {
|
|
1828
2244
|
let reply;
|
|
1829
2245
|
if (config.provider === "ollama") {
|
|
1830
|
-
const response = await
|
|
2246
|
+
const response = await proxiedFetch(`${config.endpoint}/api/chat`, {
|
|
1831
2247
|
method: "POST",
|
|
1832
2248
|
headers: { "Content-Type": "application/json" },
|
|
1833
2249
|
body: JSON.stringify({
|
|
@@ -1851,7 +2267,7 @@ async function getAIChatResponse(config, request) {
|
|
|
1851
2267
|
} else {
|
|
1852
2268
|
const headers = { "Content-Type": "application/json" };
|
|
1853
2269
|
if (config.apiKey) headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
1854
|
-
const response = await
|
|
2270
|
+
const response = await proxiedFetch(`${config.endpoint}/chat/completions`, {
|
|
1855
2271
|
method: "POST",
|
|
1856
2272
|
headers,
|
|
1857
2273
|
body: JSON.stringify({
|
|
@@ -1889,7 +2305,7 @@ async function getAIChatResponse(config, request) {
|
|
|
1889
2305
|
}
|
|
1890
2306
|
async function callModel(config, messages, opts) {
|
|
1891
2307
|
if (config.provider === "ollama") {
|
|
1892
|
-
const response2 = await
|
|
2308
|
+
const response2 = await proxiedFetch(`${config.endpoint}/api/chat`, {
|
|
1893
2309
|
method: "POST",
|
|
1894
2310
|
headers: { "Content-Type": "application/json" },
|
|
1895
2311
|
body: JSON.stringify({
|
|
@@ -1906,7 +2322,7 @@ async function callModel(config, messages, opts) {
|
|
|
1906
2322
|
}
|
|
1907
2323
|
const headers = { "Content-Type": "application/json" };
|
|
1908
2324
|
if (config.apiKey) headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
1909
|
-
const response = await
|
|
2325
|
+
const response = await proxiedFetch(`${config.endpoint}/chat/completions`, {
|
|
1910
2326
|
method: "POST",
|
|
1911
2327
|
headers,
|
|
1912
2328
|
body: JSON.stringify({ model: config.model, messages, temperature: opts.temperature, max_tokens: opts.maxTokens }),
|
|
@@ -1968,7 +2384,7 @@ async function getIntentReview(config, request) {
|
|
|
1968
2384
|
async function listAIModels(config) {
|
|
1969
2385
|
try {
|
|
1970
2386
|
if (config.provider === "ollama") {
|
|
1971
|
-
const res2 = await
|
|
2387
|
+
const res2 = await proxiedFetch(`${config.endpoint}/api/tags`, {
|
|
1972
2388
|
signal: AbortSignal.timeout(5e3)
|
|
1973
2389
|
});
|
|
1974
2390
|
if (!res2.ok) throw new Error(`Ollama: ${res2.status}`);
|
|
@@ -1978,7 +2394,7 @@ async function listAIModels(config) {
|
|
|
1978
2394
|
}
|
|
1979
2395
|
const headers = {};
|
|
1980
2396
|
if (config.apiKey) headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
1981
|
-
const res = await
|
|
2397
|
+
const res = await proxiedFetch(`${config.endpoint}/models`, {
|
|
1982
2398
|
headers,
|
|
1983
2399
|
signal: AbortSignal.timeout(8e3)
|
|
1984
2400
|
});
|
|
@@ -2000,7 +2416,7 @@ async function listAIModels(config) {
|
|
|
2000
2416
|
async function testAIConnectivity(config) {
|
|
2001
2417
|
try {
|
|
2002
2418
|
if (config.provider === "ollama") {
|
|
2003
|
-
const res = await
|
|
2419
|
+
const res = await proxiedFetch(`${config.endpoint}/api/tags`, {
|
|
2004
2420
|
signal: AbortSignal.timeout(3e3)
|
|
2005
2421
|
});
|
|
2006
2422
|
if (!res.ok) throw new Error(`Ollama: ${res.status}`);
|
|
@@ -2008,7 +2424,7 @@ async function testAIConnectivity(config) {
|
|
|
2008
2424
|
} else {
|
|
2009
2425
|
const headers = {};
|
|
2010
2426
|
if (config.apiKey) headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
2011
|
-
const res = await
|
|
2427
|
+
const res = await proxiedFetch(`${config.endpoint}/models`, {
|
|
2012
2428
|
headers,
|
|
2013
2429
|
signal: AbortSignal.timeout(3e3)
|
|
2014
2430
|
});
|
|
@@ -2021,18 +2437,18 @@ async function testAIConnectivity(config) {
|
|
|
2021
2437
|
}
|
|
2022
2438
|
|
|
2023
2439
|
// src/lib/agent-brief-store.ts
|
|
2024
|
-
import { existsSync as
|
|
2440
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
|
|
2025
2441
|
import { homedir as homedir2 } from "os";
|
|
2026
|
-
import { join as
|
|
2442
|
+
import { join as join6 } from "path";
|
|
2027
2443
|
var AGENT_CONTEXT_MAX_BYTES = 16 * 1024;
|
|
2028
2444
|
function agentBriefPath() {
|
|
2029
|
-
const dataDir = process.env.SUVEREN_DATA_DIR ??
|
|
2030
|
-
return
|
|
2445
|
+
const dataDir = process.env.SUVEREN_DATA_DIR ?? join6(homedir2(), ".suveren");
|
|
2446
|
+
return join6(dataDir, "context.md");
|
|
2031
2447
|
}
|
|
2032
2448
|
function readAgentBrief() {
|
|
2033
2449
|
const filePath = agentBriefPath();
|
|
2034
|
-
if (!
|
|
2035
|
-
return
|
|
2450
|
+
if (!existsSync5(filePath)) return "";
|
|
2451
|
+
return readFileSync7(filePath, "utf-8");
|
|
2036
2452
|
}
|
|
2037
2453
|
|
|
2038
2454
|
// src/routes/ai.ts
|
|
@@ -2396,7 +2812,7 @@ function createMCPRouter() {
|
|
|
2396
2812
|
// src/routes/autostart.ts
|
|
2397
2813
|
import { Router as Router6 } from "express";
|
|
2398
2814
|
import { execFile } from "child_process";
|
|
2399
|
-
import { existsSync as
|
|
2815
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2400
2816
|
import { dirname as pathDirname, resolve } from "path";
|
|
2401
2817
|
import { fileURLToPath } from "url";
|
|
2402
2818
|
import { promisify } from "util";
|
|
@@ -2417,7 +2833,7 @@ function findCli(dirname9 = MODULE_DIR) {
|
|
|
2417
2833
|
// resolves differently between a shell and a login service — and it would
|
|
2418
2834
|
// mask a genuinely missing CLI whenever the cwd happened to be the repo.
|
|
2419
2835
|
];
|
|
2420
|
-
return candidates.find(
|
|
2836
|
+
return candidates.find(existsSync6) ?? null;
|
|
2421
2837
|
}
|
|
2422
2838
|
async function cli(args) {
|
|
2423
2839
|
const bin = findCli();
|
|
@@ -2495,18 +2911,18 @@ function createAutostartRouter() {
|
|
|
2495
2911
|
}
|
|
2496
2912
|
|
|
2497
2913
|
// src/lib/desktop-notify.ts
|
|
2498
|
-
import { spawn, spawnSync } from "child_process";
|
|
2914
|
+
import { spawn, spawnSync as spawnSync2 } from "child_process";
|
|
2499
2915
|
|
|
2500
2916
|
// src/lib/notifier-icon.ts
|
|
2501
|
-
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as
|
|
2917
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync7 } from "fs";
|
|
2502
2918
|
import { homedir as homedir3 } from "os";
|
|
2503
|
-
import { join as
|
|
2919
|
+
import { join as join7, dirname as dirname4 } from "path";
|
|
2504
2920
|
var ICON_BASE64 = "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";
|
|
2505
2921
|
function ensureNotifierIcon(dataDir) {
|
|
2506
2922
|
try {
|
|
2507
|
-
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ??
|
|
2508
|
-
const path =
|
|
2509
|
-
if (!
|
|
2923
|
+
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join7(homedir3(), ".suveren");
|
|
2924
|
+
const path = join7(dir, "notifier-icon.png");
|
|
2925
|
+
if (!existsSync7(path)) {
|
|
2510
2926
|
mkdirSync4(dirname4(path), { recursive: true });
|
|
2511
2927
|
writeFileSync4(path, Buffer.from(ICON_BASE64, "base64"));
|
|
2512
2928
|
}
|
|
@@ -2600,7 +3016,7 @@ var terminalNotifierCache = null;
|
|
|
2600
3016
|
function hasTerminalNotifier() {
|
|
2601
3017
|
if (terminalNotifierCache !== null) return terminalNotifierCache;
|
|
2602
3018
|
try {
|
|
2603
|
-
const probe =
|
|
3019
|
+
const probe = spawnSync2("terminal-notifier", ["-help"], { stdio: "ignore" });
|
|
2604
3020
|
terminalNotifierCache = probe.error === void 0;
|
|
2605
3021
|
} catch {
|
|
2606
3022
|
terminalNotifierCache = false;
|
|
@@ -2710,17 +3126,17 @@ import { Router as Router9 } from "express";
|
|
|
2710
3126
|
|
|
2711
3127
|
// src/routes/approved-intents.ts
|
|
2712
3128
|
import { Router as Router8 } from "express";
|
|
2713
|
-
import { readFileSync as
|
|
3129
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
2714
3130
|
import { homedir as homedir4 } from "os";
|
|
2715
|
-
import { join as
|
|
2716
|
-
var SUVEREN_DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
2717
|
-
var FILE_PATH =
|
|
3131
|
+
import { join as join8 } from "path";
|
|
3132
|
+
var SUVEREN_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join8(homedir4(), ".suveren");
|
|
3133
|
+
var FILE_PATH = join8(SUVEREN_DATA_DIR, "approved-intents.enc.json");
|
|
2718
3134
|
function readFile() {
|
|
2719
|
-
if (!
|
|
3135
|
+
if (!existsSync8(FILE_PATH)) {
|
|
2720
3136
|
return { version: 1, entries: {} };
|
|
2721
3137
|
}
|
|
2722
3138
|
try {
|
|
2723
|
-
return JSON.parse(
|
|
3139
|
+
return JSON.parse(readFileSync8(FILE_PATH, "utf-8"));
|
|
2724
3140
|
} catch {
|
|
2725
3141
|
return { version: 1, entries: {} };
|
|
2726
3142
|
}
|
|
@@ -3025,12 +3441,12 @@ async function forceCheck() {
|
|
|
3025
3441
|
}
|
|
3026
3442
|
}
|
|
3027
3443
|
async function ghcrToken() {
|
|
3028
|
-
const res = await
|
|
3444
|
+
const res = await proxiedFetch(`https://ghcr.io/token?scope=repository:${GHCR_IMAGE}:pull`);
|
|
3029
3445
|
const data = await res.json();
|
|
3030
3446
|
return data.token;
|
|
3031
3447
|
}
|
|
3032
3448
|
async function ghcrDigest(tag, token) {
|
|
3033
|
-
const res = await
|
|
3449
|
+
const res = await proxiedFetch(`https://ghcr.io/v2/${GHCR_IMAGE}/manifests/${tag}`, {
|
|
3034
3450
|
method: "HEAD",
|
|
3035
3451
|
headers: {
|
|
3036
3452
|
Authorization: `Bearer ${token}`,
|
|
@@ -3050,7 +3466,7 @@ async function checkDocker() {
|
|
|
3050
3466
|
lastCheckedAt = Date.now();
|
|
3051
3467
|
}
|
|
3052
3468
|
async function checkNpm() {
|
|
3053
|
-
const res = await
|
|
3469
|
+
const res = await proxiedFetch(`https://registry.npmjs.org/${encodeURIComponent(NPM_PACKAGE)}/latest`);
|
|
3054
3470
|
if (!res.ok) return;
|
|
3055
3471
|
const data = await res.json();
|
|
3056
3472
|
latestVersion = data.version;
|
|
@@ -3093,7 +3509,7 @@ async function check() {
|
|
|
3093
3509
|
checkDev();
|
|
3094
3510
|
return;
|
|
3095
3511
|
}
|
|
3096
|
-
if (installMethod === "npm") {
|
|
3512
|
+
if (installMethod === "npm" || installMethod === "managed") {
|
|
3097
3513
|
await checkNpm();
|
|
3098
3514
|
return;
|
|
3099
3515
|
}
|
|
@@ -3143,19 +3559,19 @@ data: ${data}
|
|
|
3143
3559
|
import { Router as Router12 } from "express";
|
|
3144
3560
|
|
|
3145
3561
|
// src/lib/gateway-settings.ts
|
|
3146
|
-
import { readFileSync as
|
|
3562
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
|
|
3147
3563
|
import { homedir as homedir5 } from "os";
|
|
3148
|
-
import { join as
|
|
3564
|
+
import { join as join9, dirname as dirname6 } from "path";
|
|
3149
3565
|
var DEFAULT_SETTINGS = {
|
|
3150
3566
|
desktopNotifications: true
|
|
3151
3567
|
};
|
|
3152
3568
|
function settingsPath(dataDir) {
|
|
3153
|
-
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ??
|
|
3154
|
-
return
|
|
3569
|
+
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join9(homedir5(), ".suveren");
|
|
3570
|
+
return join9(dir, "settings.json");
|
|
3155
3571
|
}
|
|
3156
3572
|
function readSettings(dataDir) {
|
|
3157
3573
|
try {
|
|
3158
|
-
const raw = JSON.parse(
|
|
3574
|
+
const raw = JSON.parse(readFileSync9(settingsPath(dataDir), "utf8"));
|
|
3159
3575
|
return {
|
|
3160
3576
|
desktopNotifications: typeof raw.desktopNotifications === "boolean" ? raw.desktopNotifications : DEFAULT_SETTINGS.desktopNotifications
|
|
3161
3577
|
};
|
|
@@ -3202,20 +3618,20 @@ function createGatewaySettingsRouter() {
|
|
|
3202
3618
|
import { Router as Router13 } from "express";
|
|
3203
3619
|
|
|
3204
3620
|
// src/lib/archived-mandates-store.ts
|
|
3205
|
-
import { readFileSync as
|
|
3621
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, renameSync } from "fs";
|
|
3206
3622
|
import { homedir as homedir6 } from "os";
|
|
3207
|
-
import { join as
|
|
3623
|
+
import { join as join10, dirname as dirname7 } from "path";
|
|
3208
3624
|
var ID_PATTERN = /^[A-Za-z0-9_.:-]{1,200}$/;
|
|
3209
3625
|
function isValidMandateId(id) {
|
|
3210
3626
|
return typeof id === "string" && ID_PATTERN.test(id);
|
|
3211
3627
|
}
|
|
3212
3628
|
function storePath(dataDir) {
|
|
3213
|
-
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ??
|
|
3214
|
-
return
|
|
3629
|
+
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join10(homedir6(), ".suveren");
|
|
3630
|
+
return join10(dir, "archived-mandates.json");
|
|
3215
3631
|
}
|
|
3216
3632
|
function readArchived(dataDir) {
|
|
3217
3633
|
try {
|
|
3218
|
-
const raw = JSON.parse(
|
|
3634
|
+
const raw = JSON.parse(readFileSync10(storePath(dataDir), "utf8"));
|
|
3219
3635
|
if (!Array.isArray(raw.archived)) return [];
|
|
3220
3636
|
return [...new Set(raw.archived.filter(isValidMandateId))];
|
|
3221
3637
|
} catch {
|
|
@@ -3521,17 +3937,17 @@ function buildSessionHealth(vault2) {
|
|
|
3521
3937
|
}
|
|
3522
3938
|
|
|
3523
3939
|
// src/lib/denials-reader.ts
|
|
3524
|
-
import { existsSync as
|
|
3525
|
-
import { join as
|
|
3940
|
+
import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
|
|
3941
|
+
import { join as join11 } from "path";
|
|
3526
3942
|
function loadDenials(dataDir, decrypt) {
|
|
3527
|
-
const encPath =
|
|
3528
|
-
const plainPath =
|
|
3529
|
-
if (
|
|
3530
|
-
const { blob } = JSON.parse(
|
|
3943
|
+
const encPath = join11(dataDir, "denials.enc.json");
|
|
3944
|
+
const plainPath = join11(dataDir, "denials.json");
|
|
3945
|
+
if (existsSync9(encPath)) {
|
|
3946
|
+
const { blob } = JSON.parse(readFileSync11(encPath, "utf-8"));
|
|
3531
3947
|
return JSON.parse(decrypt(blob)).records ?? [];
|
|
3532
3948
|
}
|
|
3533
|
-
if (
|
|
3534
|
-
return JSON.parse(
|
|
3949
|
+
if (existsSync9(plainPath)) {
|
|
3950
|
+
return JSON.parse(readFileSync11(plainPath, "utf-8")).records ?? [];
|
|
3535
3951
|
}
|
|
3536
3952
|
return [];
|
|
3537
3953
|
}
|
|
@@ -3547,15 +3963,17 @@ function selectDenials(records, opts = {}) {
|
|
|
3547
3963
|
|
|
3548
3964
|
// src/lib/simulation-mode.ts
|
|
3549
3965
|
function isSimulationMode() {
|
|
3966
|
+
const policySimulation = readPolicy().policy.simulation;
|
|
3967
|
+
if (policySimulation !== void 0) return policySimulation;
|
|
3550
3968
|
return process.env.SUVEREN_SIMULATION === "1";
|
|
3551
3969
|
}
|
|
3552
3970
|
|
|
3553
3971
|
// src/index.ts
|
|
3554
3972
|
(function migrateDataDir() {
|
|
3555
3973
|
if (process.env.SUVEREN_DATA_DIR) return;
|
|
3556
|
-
const oldDir =
|
|
3557
|
-
const newDir =
|
|
3558
|
-
if (!
|
|
3974
|
+
const oldDir = join12(homedir7(), ".hap");
|
|
3975
|
+
const newDir = join12(homedir7(), ".suveren");
|
|
3976
|
+
if (!existsSync10(newDir) && existsSync10(oldDir)) {
|
|
3559
3977
|
try {
|
|
3560
3978
|
renameSync2(oldDir, newDir);
|
|
3561
3979
|
console.error(`[Control Plane] Migrated data directory: ${oldDir} \u2192 ${newDir}`);
|
|
@@ -3565,12 +3983,14 @@ function isSimulationMode() {
|
|
|
3565
3983
|
}
|
|
3566
3984
|
}
|
|
3567
3985
|
})();
|
|
3568
|
-
var DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
3986
|
+
var DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join12(homedir7(), ".suveren");
|
|
3569
3987
|
var SP_URL = resolveAsUrl(DATA_DIR);
|
|
3570
3988
|
validatePinTlsForUrl(resolvePinTls(DATA_DIR), SP_URL);
|
|
3989
|
+
var POLICY_LOCKED = [...readPolicy().locked];
|
|
3990
|
+
var POLICY_LOCKED_INSTALL_METHOD = readPolicy().policy.installMethod;
|
|
3571
3991
|
var port = parseInt(process.env.SUVEREN_CP_PORT ?? "3402", 10);
|
|
3572
3992
|
var HAP_MODE = process.env.HAP_MODE ?? "personal";
|
|
3573
|
-
var UI_DIST = process.env.HAP_UI_DIST ??
|
|
3993
|
+
var UI_DIST = process.env.HAP_UI_DIST ?? join12(import.meta.dirname ?? __dirname, "../../ui/dist");
|
|
3574
3994
|
var vault = new Vault(DATA_DIR);
|
|
3575
3995
|
(function repairIfAsUrlChanged() {
|
|
3576
3996
|
const pairing = readPairing(DATA_DIR);
|
|
@@ -3984,12 +4404,17 @@ app.get("/as-pairing", authGuard, (_req, res) => {
|
|
|
3984
4404
|
// key (formatPinFingerprint mirrors fingerprintOf's grouping), applied
|
|
3985
4405
|
// to the already-computed SPKI digest rather than re-hashing a key.
|
|
3986
4406
|
pinTlsEnabled: resolvePinTls(DATA_DIR),
|
|
3987
|
-
tlsPinFingerprint: paired && pairing.tlsSpkiPinHex ? formatPinFingerprint(pairing.tlsSpkiPinHex) : null
|
|
4407
|
+
tlsPinFingerprint: paired && pairing.tlsSpkiPinHex ? formatPinFingerprint(pairing.tlsSpkiPinHex) : null,
|
|
4408
|
+
// IT policy (see lib/policy.ts) — read-only flags so Settings can show
|
|
4409
|
+
// "set by your IT" instead of the "change it via the CLI" hint, which
|
|
4410
|
+
// would be actively misleading for a locked value.
|
|
4411
|
+
asUrlLockedByPolicy: POLICY_LOCKED.includes("asUrl"),
|
|
4412
|
+
pinTlsLockedByPolicy: POLICY_LOCKED.includes("pinTls")
|
|
3988
4413
|
});
|
|
3989
4414
|
});
|
|
3990
4415
|
app.get("/denials", authGuard, (req, res) => {
|
|
3991
4416
|
try {
|
|
3992
|
-
const dataDir = process.env.SUVEREN_DATA_DIR ??
|
|
4417
|
+
const dataDir = process.env.SUVEREN_DATA_DIR ?? join12(homedir7(), ".suveren");
|
|
3993
4418
|
const all = loadDenials(dataDir, (blob) => vault.decrypt(blob));
|
|
3994
4419
|
const since = Number(req.query.since);
|
|
3995
4420
|
const limit = Number(req.query.limit);
|
|
@@ -4059,7 +4484,7 @@ var pinnedProxyAgent = SP_URL.startsWith("https:") ? buildPinnedHttpsAgent(() =>
|
|
|
4059
4484
|
enforce: resolvePinTls(DATA_DIR),
|
|
4060
4485
|
pinnedSpkiHex: pin && pin.asUrl === SP_URL ? pin.tlsSpkiPinHex : void 0
|
|
4061
4486
|
};
|
|
4062
|
-
}) : void 0;
|
|
4487
|
+
}, SP_URL) : void 0;
|
|
4063
4488
|
app.use(
|
|
4064
4489
|
createProxyMiddleware({
|
|
4065
4490
|
target: SP_URL,
|
|
@@ -4120,12 +4545,13 @@ app.use(
|
|
|
4120
4545
|
})
|
|
4121
4546
|
);
|
|
4122
4547
|
function detectInstallMethod() {
|
|
4123
|
-
if (
|
|
4548
|
+
if (process.env.SUVEREN_INSTALL_METHOD === "managed" || POLICY_LOCKED_INSTALL_METHOD === "managed") return "managed";
|
|
4549
|
+
if (existsSync10("/.dockerenv")) return "docker";
|
|
4124
4550
|
const dir = import.meta.dirname ?? __dirname;
|
|
4125
4551
|
if (dir.includes("/node_modules/@suveren/gateway/")) return "npm";
|
|
4126
4552
|
let cursor = dir;
|
|
4127
4553
|
for (let i = 0; i < 8; i++) {
|
|
4128
|
-
if (
|
|
4554
|
+
if (existsSync10(join12(cursor, ".git"))) return "dev";
|
|
4129
4555
|
const parent = dirname8(cursor);
|
|
4130
4556
|
if (parent === cursor) break;
|
|
4131
4557
|
cursor = parent;
|
|
@@ -4138,10 +4564,10 @@ function detectRunningVersion() {
|
|
|
4138
4564
|
return process.env.HAP_BUILD_SHA ?? "dev";
|
|
4139
4565
|
}
|
|
4140
4566
|
const dir = import.meta.dirname ?? __dirname;
|
|
4141
|
-
const bundlePkg =
|
|
4142
|
-
if (
|
|
4567
|
+
const bundlePkg = join12(dir, "..", "..", "package.json");
|
|
4568
|
+
if (existsSync10(bundlePkg)) {
|
|
4143
4569
|
try {
|
|
4144
|
-
const pkg = JSON.parse(
|
|
4570
|
+
const pkg = JSON.parse(readFileSync12(bundlePkg, "utf8"));
|
|
4145
4571
|
if (pkg.name === "@suveren/gateway" && typeof pkg.version === "string") {
|
|
4146
4572
|
return pkg.version;
|
|
4147
4573
|
}
|
|
@@ -4167,13 +4593,20 @@ app.get("/health", async (req, res) => {
|
|
|
4167
4593
|
// Purely local — the Authority Server never learns this. UI reads it to
|
|
4168
4594
|
// show a gateway-wide banner ("real systems are blocked").
|
|
4169
4595
|
simulation: isSimulationMode(),
|
|
4596
|
+
// Settings IT has locked centrally (Windows registry policy, or a
|
|
4597
|
+
// policy file — see lib/policy.ts). Read-only here: there is no API
|
|
4598
|
+
// route that lets the UI change any of these (as-url/ca-file/pin-tls
|
|
4599
|
+
// are CLI-only; simulation has no UI control either), so this exists
|
|
4600
|
+
// purely so Settings can render "set by your IT" instead of controls
|
|
4601
|
+
// that would silently do nothing.
|
|
4602
|
+
policyLocked: POLICY_LOCKED,
|
|
4170
4603
|
session: buildSessionHealth(vault),
|
|
4171
4604
|
security: {
|
|
4172
4605
|
note: "Gateway secures tool execution. Agent host isolation is the user's responsibility."
|
|
4173
4606
|
}
|
|
4174
4607
|
});
|
|
4175
4608
|
});
|
|
4176
|
-
if (
|
|
4609
|
+
if (existsSync10(UI_DIST)) {
|
|
4177
4610
|
app.use(express.static(UI_DIST, {
|
|
4178
4611
|
setHeaders: (res, filePath) => {
|
|
4179
4612
|
if (filePath.endsWith("index.html")) {
|
|
@@ -4183,7 +4616,7 @@ if (existsSync9(UI_DIST)) {
|
|
|
4183
4616
|
}));
|
|
4184
4617
|
app.get("*", (_req, res) => {
|
|
4185
4618
|
res.setHeader("Cache-Control", "no-store, must-revalidate");
|
|
4186
|
-
res.sendFile(
|
|
4619
|
+
res.sendFile(join12(UI_DIST, "index.html"));
|
|
4187
4620
|
});
|
|
4188
4621
|
} else {
|
|
4189
4622
|
app.get("/", (_req, res) => {
|