@suveren/gateway 0.25.0 → 0.26.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.
@@ -127,6 +127,13 @@ var Vault = class {
127
127
  this.clearKey();
128
128
  this.lockedReason = "as-tls-mismatch";
129
129
  }
130
+ /** Lock because this is a managed gateway (IT policy `Team`) and the
131
+ * signed-in account's active workspace is no longer that team — see
132
+ * lib/managed-team.ts. Mirrors lockAsKeyMismatch() exactly. */
133
+ lockManagedWrongTeam() {
134
+ this.clearKey();
135
+ this.lockedReason = "managed-wrong-team";
136
+ }
130
137
  /**
131
138
  * Boot-time only: record that the gateway starts locked because the
132
139
  * resolved Authority Server URL differs from the one it was last paired
@@ -1299,7 +1306,7 @@ import { dirname as dirname4, join as join6 } from "path";
1299
1306
  import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
1300
1307
  import { join as join5 } from "path";
1301
1308
  import { spawnSync } from "child_process";
1302
- var POLICY_KEYS = ["AsUrl", "CaFile", "PinTls", "Proxy", "NoProxy", "Simulation", "InstallMethod"];
1309
+ var POLICY_KEYS = ["AsUrl", "CaFile", "PinTls", "Proxy", "NoProxy", "Simulation", "InstallMethod", "Team"];
1303
1310
  var DEFAULT_REGISTRY_BASE_KEY = "SOFTWARE\\Policies\\Suveren\\Gateway";
1304
1311
  function registryKeyPath(hive) {
1305
1312
  const baseKey = process.env.SUVEREN_POLICY_REGISTRY_KEY || DEFAULT_REGISTRY_BASE_KEY;
@@ -1335,6 +1342,11 @@ function validateCaFile(candidate) {
1335
1342
  if (!existsSync4(trimmed)) return { ok: false, error: `No file at "${trimmed}".` };
1336
1343
  return { ok: true, path: trimmed };
1337
1344
  }
1345
+ function validateTeamId(candidate) {
1346
+ const trimmed = candidate.trim();
1347
+ if (!trimmed) return { ok: false, error: "The team id is empty." };
1348
+ return { ok: true, id: trimmed };
1349
+ }
1338
1350
  function validateProxyUrl(candidate) {
1339
1351
  const trimmed = candidate.trim();
1340
1352
  if (!trimmed) return { ok: false, error: "The proxy URL is empty." };
@@ -1508,6 +1520,14 @@ function readPolicy() {
1508
1520
  policy.installMethod = "managed";
1509
1521
  locked.add("installMethod");
1510
1522
  }
1523
+ if (merged.Team !== void 0) {
1524
+ const v = validateTeamId(String(merged.Team));
1525
+ if (!v.ok) {
1526
+ throw new Error(`Invalid policy Team ("${String(merged.Team)}") from ${sourceOf.Team}: ${v.error}`);
1527
+ }
1528
+ policy.team = v.id;
1529
+ locked.add("team");
1530
+ }
1511
1531
  cached = { policy, locked };
1512
1532
  return cached;
1513
1533
  }
@@ -1624,6 +1644,298 @@ function clearPinTlsExpectedFingerprint(dataDir) {
1624
1644
  writeAsConfig(dataDir, { pinTlsExpectedFingerprint: void 0 });
1625
1645
  }
1626
1646
 
1647
+ // src/lib/managed-team.ts
1648
+ async function activeTeamId(asUrl, cookie, pinning) {
1649
+ const { res } = await fetchAs(
1650
+ `${asUrl}/api/groups/me`,
1651
+ { headers: { Cookie: cookie }, signal: AbortSignal.timeout(1e4) },
1652
+ pinning
1653
+ );
1654
+ if (!res.ok) throw new Error(`GET /api/groups/me answered ${res.status}`);
1655
+ const data = await res.json();
1656
+ return data.group?.id ?? null;
1657
+ }
1658
+ var ManagedTeamScheduler = class {
1659
+ constructor(check2, opts = {}) {
1660
+ this.check = check2;
1661
+ this.setIntervalFn = opts.setIntervalFn ?? setInterval;
1662
+ this.clearIntervalFn = opts.clearIntervalFn ?? clearInterval;
1663
+ this.intervalMs = opts.intervalMs ?? 5 * 6e4;
1664
+ }
1665
+ timer = null;
1666
+ setIntervalFn;
1667
+ clearIntervalFn;
1668
+ intervalMs;
1669
+ start() {
1670
+ if (this.timer) return;
1671
+ this.timer = this.setIntervalFn(() => {
1672
+ void this.check();
1673
+ }, this.intervalMs);
1674
+ }
1675
+ stop() {
1676
+ if (this.timer) this.clearIntervalFn(this.timer);
1677
+ this.timer = null;
1678
+ }
1679
+ /** Test-only: run the check immediately, bypassing the timer. */
1680
+ runNow() {
1681
+ return this.check();
1682
+ }
1683
+ };
1684
+
1685
+ // src/lib/event-bus.ts
1686
+ var EventBus = class {
1687
+ listeners = /* @__PURE__ */ new Set();
1688
+ /**
1689
+ * Subscribe to all bus events.
1690
+ * Returns an unsubscribe function — callers MUST call it on disconnect / unmount
1691
+ * to avoid listener leaks.
1692
+ */
1693
+ subscribe(handler) {
1694
+ this.listeners.add(handler);
1695
+ return () => {
1696
+ this.listeners.delete(handler);
1697
+ };
1698
+ }
1699
+ emit(type, payload) {
1700
+ const event = { type, payload };
1701
+ for (const handler of this.listeners) {
1702
+ try {
1703
+ handler(event);
1704
+ } catch (err) {
1705
+ console.error("[EventBus] subscriber threw:", err);
1706
+ }
1707
+ }
1708
+ }
1709
+ /** How many listeners are currently subscribed. Useful for tests and health checks. */
1710
+ get listenerCount() {
1711
+ return this.listeners.size;
1712
+ }
1713
+ };
1714
+ var eventBus = new EventBus();
1715
+
1716
+ // src/lib/desktop-notify.ts
1717
+ import { spawn, spawnSync as spawnSync2 } from "child_process";
1718
+
1719
+ // src/lib/notifier-icon.ts
1720
+ import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync6 } from "fs";
1721
+ import { homedir as homedir2 } from "os";
1722
+ import { join as join7, dirname as dirname5 } from "path";
1723
+ var ICON_BASE64 = "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";
1724
+ function ensureNotifierIcon(dataDir) {
1725
+ try {
1726
+ const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join7(homedir2(), ".suveren");
1727
+ const path = join7(dir, "notifier-icon.png");
1728
+ if (!existsSync6(path)) {
1729
+ mkdirSync4(dirname5(path), { recursive: true });
1730
+ writeFileSync4(path, Buffer.from(ICON_BASE64, "base64"));
1731
+ }
1732
+ return path;
1733
+ } catch {
1734
+ return null;
1735
+ }
1736
+ }
1737
+
1738
+ // src/lib/desktop-notify.ts
1739
+ function osaQuote(value) {
1740
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
1741
+ }
1742
+ function psQuote(value) {
1743
+ return `'${value.replace(/'/g, "''")}'`;
1744
+ }
1745
+ function xmlEscape(value) {
1746
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1747
+ }
1748
+ var WINDOWS_AUMID = "Suveren.Gateway";
1749
+ function windowsToastScript(title, message, url, iconPath) {
1750
+ const toastXml = `<toast${url ? ` activationType="protocol" launch="${xmlEscape(url)}"` : ""}><visual><binding template="ToastGeneric"><text>${xmlEscape(title)}</text><text>${xmlEscape(message)}</text>` + (iconPath ? `<image placement="appLogoOverride" src="${xmlEscape(iconPath)}"/>` : "") + `</binding></visual><audio src="ms-winsoundevent:Notification.Default"/></toast>`;
1751
+ return `$ErrorActionPreference='SilentlyContinue'; $aumid=${psQuote(WINDOWS_AUMID)}; $reg="HKCU:\\Software\\Classes\\AppUserModelId\\$aumid"; if (-not (Test-Path $reg)) { New-Item -Path $reg -Force | Out-Null }; New-ItemProperty -Path $reg -Name DisplayName -Value 'Suveren' -PropertyType String -Force | Out-Null; ` + (iconPath ? `New-ItemProperty -Path $reg -Name IconUri -Value ${psQuote(iconPath)} -PropertyType String -Force | Out-Null; ` : "") + `[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType=WindowsRuntime] | Out-Null; [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType=WindowsRuntime] | Out-Null; $doc = New-Object Windows.Data.Xml.Dom.XmlDocument; $doc.LoadXml(${psQuote(toastXml)}); $toast = New-Object Windows.UI.Notifications.ToastNotification $doc; [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($aumid).Show($toast)`;
1752
+ }
1753
+ var MAC_SOUND = "Glass";
1754
+ function buildNotifyCommand(platform, title, message, options = {}) {
1755
+ const { url, hasTerminalNotifier: hasTerminalNotifier2 = false, iconPath } = options;
1756
+ switch (platform) {
1757
+ case "darwin":
1758
+ if (url && hasTerminalNotifier2) {
1759
+ return {
1760
+ cmd: "terminal-notifier",
1761
+ args: [
1762
+ "-title",
1763
+ title,
1764
+ "-message",
1765
+ message,
1766
+ "-open",
1767
+ url,
1768
+ "-sound",
1769
+ MAC_SOUND,
1770
+ ...iconPath ? ["-appIcon", iconPath] : [],
1771
+ "-group",
1772
+ "ai.suveren.gateway"
1773
+ ]
1774
+ };
1775
+ }
1776
+ return {
1777
+ cmd: "osascript",
1778
+ args: [
1779
+ "-e",
1780
+ `display notification ${osaQuote(message)} with title ${osaQuote(title)} sound name ${osaQuote(MAC_SOUND)}`
1781
+ ]
1782
+ };
1783
+ case "win32":
1784
+ return {
1785
+ cmd: "powershell",
1786
+ args: ["-NoProfile", "-NonInteractive", "-Command", windowsToastScript(title, message, url, iconPath)]
1787
+ };
1788
+ case "linux":
1789
+ return {
1790
+ cmd: "notify-send",
1791
+ args: [
1792
+ "--app-name=Suveren",
1793
+ ...iconPath ? ["-i", iconPath] : [],
1794
+ title,
1795
+ message
1796
+ ]
1797
+ };
1798
+ default:
1799
+ return null;
1800
+ }
1801
+ }
1802
+ function lockedNotification(port2) {
1803
+ const url = `http://localhost:${port2}`;
1804
+ return {
1805
+ title: "Suveren is running but locked",
1806
+ message: `Your agent has no authority until you unlock it. Open ${url} and enter your API key.`,
1807
+ url
1808
+ };
1809
+ }
1810
+ function sessionExpiredNotification(port2) {
1811
+ const url = `http://localhost:${port2}`;
1812
+ return {
1813
+ title: "Suveren Gateway locked",
1814
+ message: "Your sign-in has ended. Sign in again so your agents can act.",
1815
+ url
1816
+ };
1817
+ }
1818
+ var terminalNotifierCache = null;
1819
+ function hasTerminalNotifier() {
1820
+ if (terminalNotifierCache !== null) return terminalNotifierCache;
1821
+ try {
1822
+ const probe = spawnSync2("terminal-notifier", ["-help"], { stdio: "ignore" });
1823
+ terminalNotifierCache = probe.error === void 0;
1824
+ } catch {
1825
+ terminalNotifierCache = false;
1826
+ }
1827
+ return terminalNotifierCache;
1828
+ }
1829
+ function notifySpawnOptions(platform) {
1830
+ return {
1831
+ detached: platform !== "win32",
1832
+ stdio: ["ignore", "ignore", "pipe"]
1833
+ };
1834
+ }
1835
+ function notify(title, message, platform = process.platform, url) {
1836
+ const command = buildNotifyCommand(platform, title, message, {
1837
+ url,
1838
+ hasTerminalNotifier: platform === "darwin" ? hasTerminalNotifier() : false,
1839
+ // Windows and Linux show it on the toast itself; macOS only via
1840
+ // terminal-notifier. Failure to write it must never block the sound.
1841
+ iconPath: ensureNotifierIcon()
1842
+ });
1843
+ if (!command) return;
1844
+ try {
1845
+ const child = spawn(command.cmd, command.args, notifySpawnOptions(platform));
1846
+ let stderr = "";
1847
+ child.stderr?.on("data", (d) => {
1848
+ stderr += d.toString().slice(0, 500);
1849
+ });
1850
+ child.on("close", (code) => {
1851
+ if (code !== 0) {
1852
+ console.error(`[notify] ${command.cmd} exited ${code}${stderr ? `: ${stderr.trim()}` : ""}`);
1853
+ }
1854
+ });
1855
+ child.on("error", () => {
1856
+ });
1857
+ child.unref();
1858
+ } catch {
1859
+ }
1860
+ }
1861
+
1862
+ // src/lib/session-lock.ts
1863
+ var SESSION_LOCKED_MESSAGE = "Your sign-in ended after 30 days or was revoked. Sign in again.";
1864
+ var AS_KEY_MISMATCH_MESSAGE = "The Authority Server presented a signing key that does not match the one pinned at pairing. Sign in again to review.";
1865
+ var AS_TLS_MISMATCH_MESSAGE = "A connection to the Authority Server presented a TLS certificate that does not match the one pinned at pairing. The connection was refused before anything was sent. If the certificate changed intentionally (a new key, not just renewal), clear the pairing and sign in again to re-pin it.";
1866
+ function managedWrongTeamMessage(team) {
1867
+ return `This gateway is set up by your IT for team ${team}. Your account is no longer in this team. Sign in with your team account, or ask your IT.`;
1868
+ }
1869
+ function createSessionLock(deps) {
1870
+ const { vault: vault2, port: port2 } = deps;
1871
+ const notifyFn = deps.notifyFn ?? notify;
1872
+ const unconfigureSessionFn = deps.unconfigureSessionFn ?? unconfigureSession;
1873
+ const emit = deps.emit ?? eventBus.emit.bind(eventBus);
1874
+ return function lockExpiredSession2() {
1875
+ if (!vault2.isUnlocked()) return;
1876
+ vault2.lockExpired();
1877
+ console.error("[Control Plane] Authority Server session ended \u2014 gateway LOCKED");
1878
+ void unconfigureSessionFn().catch((err) => {
1879
+ console.error("[Control Plane] Failed to push cleared session to MCP:", err);
1880
+ });
1881
+ emit("session-locked", { reason: "expired", message: SESSION_LOCKED_MESSAGE });
1882
+ const { title, message, url } = sessionExpiredNotification(port2);
1883
+ notifyFn(title, message, process.platform, url);
1884
+ };
1885
+ }
1886
+ function createAsKeyMismatchLock(deps) {
1887
+ const { vault: vault2, port: port2 } = deps;
1888
+ const notifyFn = deps.notifyFn ?? notify;
1889
+ const unconfigureSessionFn = deps.unconfigureSessionFn ?? unconfigureSession;
1890
+ const emit = deps.emit ?? eventBus.emit.bind(eventBus);
1891
+ return function lockOnAsKeyMismatch() {
1892
+ if (!vault2.isUnlocked()) return;
1893
+ vault2.lockAsKeyMismatch();
1894
+ console.error("[Control Plane] Authority Server key mismatch \u2014 gateway LOCKED");
1895
+ void unconfigureSessionFn("as-key-mismatch").catch((err) => {
1896
+ console.error("[Control Plane] Failed to push cleared session to MCP:", err);
1897
+ });
1898
+ emit("session-locked", { reason: "as-key-mismatch", message: AS_KEY_MISMATCH_MESSAGE });
1899
+ const { title, url } = sessionExpiredNotification(port2);
1900
+ notifyFn(title, AS_KEY_MISMATCH_MESSAGE, process.platform, url);
1901
+ };
1902
+ }
1903
+ function createAsTlsMismatchLock(deps) {
1904
+ const { vault: vault2, port: port2 } = deps;
1905
+ const notifyFn = deps.notifyFn ?? notify;
1906
+ const unconfigureSessionFn = deps.unconfigureSessionFn ?? unconfigureSession;
1907
+ const emit = deps.emit ?? eventBus.emit.bind(eventBus);
1908
+ return function lockOnAsTlsMismatch() {
1909
+ if (!vault2.isUnlocked()) return;
1910
+ vault2.lockAsTlsMismatch();
1911
+ console.error("[Control Plane] Authority Server TLS pin mismatch \u2014 gateway LOCKED");
1912
+ void unconfigureSessionFn("as-tls-mismatch").catch((err) => {
1913
+ console.error("[Control Plane] Failed to push cleared session to MCP:", err);
1914
+ });
1915
+ emit("session-locked", { reason: "as-tls-mismatch", message: AS_TLS_MISMATCH_MESSAGE });
1916
+ const { title, url } = sessionExpiredNotification(port2);
1917
+ notifyFn(title, AS_TLS_MISMATCH_MESSAGE, process.platform, url);
1918
+ };
1919
+ }
1920
+ function createManagedWrongTeamLock(deps) {
1921
+ const { vault: vault2, port: port2 } = deps;
1922
+ const notifyFn = deps.notifyFn ?? notify;
1923
+ const unconfigureSessionFn = deps.unconfigureSessionFn ?? unconfigureSession;
1924
+ const emit = deps.emit ?? eventBus.emit.bind(eventBus);
1925
+ return function lockOnManagedWrongTeam(team) {
1926
+ if (!vault2.isUnlocked()) return;
1927
+ vault2.lockManagedWrongTeam();
1928
+ console.error("[Control Plane] Managed gateway: signed-in account is no longer in the locked team \u2014 gateway LOCKED");
1929
+ const message = managedWrongTeamMessage(team);
1930
+ void unconfigureSessionFn("managed-wrong-team").catch((err) => {
1931
+ console.error("[Control Plane] Failed to push cleared session to MCP:", err);
1932
+ });
1933
+ emit("session-locked", { reason: "managed-wrong-team", message });
1934
+ const { title, url } = sessionExpiredNotification(port2);
1935
+ notifyFn(title, message, process.platform, url);
1936
+ };
1937
+ }
1938
+
1627
1939
  // src/routes/auth.ts
1628
1940
  var DEFAULT_SP_URL = process.env.SUVEREN_AS_URL ?? "https://www.suveren.ai";
1629
1941
  async function fetchAsPublicKey(asUrl) {
@@ -1706,7 +2018,7 @@ async function checkAsKeyBeforeLogin(asUrl, dataDir) {
1706
2018
  };
1707
2019
  }
1708
2020
  function createAuthRouter(vault2, logoutAuth, loginRateLimit2, onSessionEstablished, pairing, lockAsTlsMismatch2 = () => {
1709
- }) {
2021
+ }, managedTeamId) {
1710
2022
  const router = Router();
1711
2023
  const SP_URL2 = pairing?.asUrl ?? DEFAULT_SP_URL;
1712
2024
  router.post("/login", loginRateLimit2, async (req, res) => {
@@ -1788,6 +2100,25 @@ function createAuthRouter(vault2, logoutAuth, loginRateLimit2, onSessionEstablis
1788
2100
  const data = await spRes.json();
1789
2101
  const setCookieHeaders = spRes.headers.getSetCookie?.() ?? [];
1790
2102
  const sessionCookie = setCookieHeaders.join("; ");
2103
+ if (managedTeamId) {
2104
+ let activeTeam;
2105
+ try {
2106
+ activeTeam = await activeTeamId(SP_URL2, sessionCookie, sessionPinning);
2107
+ } catch (err) {
2108
+ res.status(502).json({
2109
+ error: "as_unreachable",
2110
+ message: `Could not confirm your team membership with the Authority Server \u2014 ${err instanceof Error ? err.message : String(err)}. Refusing to sign in.`
2111
+ });
2112
+ return;
2113
+ }
2114
+ if (activeTeam !== managedTeamId) {
2115
+ res.status(403).json({
2116
+ error: "managed_wrong_team",
2117
+ message: managedWrongTeamMessage(managedTeamId)
2118
+ });
2119
+ return;
2120
+ }
2121
+ }
1791
2122
  vault2.setSpCookie(sessionCookie);
1792
2123
  await vault2.deriveAndSetKey(apiKey);
1793
2124
  if (vault2.isVaultFromDifferentKey()) {
@@ -2561,17 +2892,17 @@ async function testAIConnectivity(config) {
2561
2892
  }
2562
2893
 
2563
2894
  // src/lib/agent-brief-store.ts
2564
- import { existsSync as existsSync6, readFileSync as readFileSync8 } from "fs";
2565
- import { homedir as homedir2 } from "os";
2566
- import { join as join7 } from "path";
2895
+ import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
2896
+ import { homedir as homedir3 } from "os";
2897
+ import { join as join8 } from "path";
2567
2898
  var AGENT_CONTEXT_MAX_BYTES = 16 * 1024;
2568
2899
  function agentBriefPath() {
2569
- const dataDir = process.env.SUVEREN_DATA_DIR ?? join7(homedir2(), ".suveren");
2570
- return join7(dataDir, "context.md");
2900
+ const dataDir = process.env.SUVEREN_DATA_DIR ?? join8(homedir3(), ".suveren");
2901
+ return join8(dataDir, "context.md");
2571
2902
  }
2572
2903
  function readAgentBrief() {
2573
2904
  const filePath = agentBriefPath();
2574
- if (!existsSync6(filePath)) return "";
2905
+ if (!existsSync7(filePath)) return "";
2575
2906
  return readFileSync8(filePath, "utf-8");
2576
2907
  }
2577
2908
 
@@ -2853,39 +3184,6 @@ async function backfillReadPolicyDefaults(data) {
2853
3184
 
2854
3185
  // src/routes/mcp.ts
2855
3186
  import { Router as Router5 } from "express";
2856
-
2857
- // src/lib/event-bus.ts
2858
- var EventBus = class {
2859
- listeners = /* @__PURE__ */ new Set();
2860
- /**
2861
- * Subscribe to all bus events.
2862
- * Returns an unsubscribe function — callers MUST call it on disconnect / unmount
2863
- * to avoid listener leaks.
2864
- */
2865
- subscribe(handler) {
2866
- this.listeners.add(handler);
2867
- return () => {
2868
- this.listeners.delete(handler);
2869
- };
2870
- }
2871
- emit(type, payload) {
2872
- const event = { type, payload };
2873
- for (const handler of this.listeners) {
2874
- try {
2875
- handler(event);
2876
- } catch (err) {
2877
- console.error("[EventBus] subscriber threw:", err);
2878
- }
2879
- }
2880
- }
2881
- /** How many listeners are currently subscribed. Useful for tests and health checks. */
2882
- get listenerCount() {
2883
- return this.listeners.size;
2884
- }
2885
- };
2886
- var eventBus = new EventBus();
2887
-
2888
- // src/routes/mcp.ts
2889
3187
  function createMCPRouter() {
2890
3188
  const router = Router5();
2891
3189
  router.get("/health", async (_req, res) => {
@@ -2958,7 +3256,7 @@ function createMCPRouter() {
2958
3256
  // src/routes/autostart.ts
2959
3257
  import { Router as Router6 } from "express";
2960
3258
  import { execFile } from "child_process";
2961
- import { existsSync as existsSync7 } from "fs";
3259
+ import { existsSync as existsSync8 } from "fs";
2962
3260
  import { dirname as pathDirname, resolve } from "path";
2963
3261
  import { fileURLToPath } from "url";
2964
3262
  import { promisify } from "util";
@@ -2979,7 +3277,7 @@ function findCli(dirname10 = MODULE_DIR) {
2979
3277
  // resolves differently between a shell and a login service — and it would
2980
3278
  // mask a genuinely missing CLI whenever the cwd happened to be the repo.
2981
3279
  ];
2982
- return candidates.find(existsSync7) ?? null;
3280
+ return candidates.find(existsSync8) ?? null;
2983
3281
  }
2984
3282
  async function cli(args) {
2985
3283
  const bin = findCli();
@@ -3056,152 +3354,6 @@ function createAutostartRouter() {
3056
3354
  return router;
3057
3355
  }
3058
3356
 
3059
- // src/lib/desktop-notify.ts
3060
- import { spawn, spawnSync as spawnSync2 } from "child_process";
3061
-
3062
- // src/lib/notifier-icon.ts
3063
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync8 } from "fs";
3064
- import { homedir as homedir3 } from "os";
3065
- import { join as join8, dirname as dirname5 } from "path";
3066
- var ICON_BASE64 = "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";
3067
- function ensureNotifierIcon(dataDir) {
3068
- try {
3069
- const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join8(homedir3(), ".suveren");
3070
- const path = join8(dir, "notifier-icon.png");
3071
- if (!existsSync8(path)) {
3072
- mkdirSync4(dirname5(path), { recursive: true });
3073
- writeFileSync4(path, Buffer.from(ICON_BASE64, "base64"));
3074
- }
3075
- return path;
3076
- } catch {
3077
- return null;
3078
- }
3079
- }
3080
-
3081
- // src/lib/desktop-notify.ts
3082
- function osaQuote(value) {
3083
- return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
3084
- }
3085
- function psQuote(value) {
3086
- return `'${value.replace(/'/g, "''")}'`;
3087
- }
3088
- function xmlEscape(value) {
3089
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
3090
- }
3091
- var WINDOWS_AUMID = "Suveren.Gateway";
3092
- function windowsToastScript(title, message, url, iconPath) {
3093
- const toastXml = `<toast${url ? ` activationType="protocol" launch="${xmlEscape(url)}"` : ""}><visual><binding template="ToastGeneric"><text>${xmlEscape(title)}</text><text>${xmlEscape(message)}</text>` + (iconPath ? `<image placement="appLogoOverride" src="${xmlEscape(iconPath)}"/>` : "") + `</binding></visual><audio src="ms-winsoundevent:Notification.Default"/></toast>`;
3094
- return `$ErrorActionPreference='SilentlyContinue'; $aumid=${psQuote(WINDOWS_AUMID)}; $reg="HKCU:\\Software\\Classes\\AppUserModelId\\$aumid"; if (-not (Test-Path $reg)) { New-Item -Path $reg -Force | Out-Null }; New-ItemProperty -Path $reg -Name DisplayName -Value 'Suveren' -PropertyType String -Force | Out-Null; ` + (iconPath ? `New-ItemProperty -Path $reg -Name IconUri -Value ${psQuote(iconPath)} -PropertyType String -Force | Out-Null; ` : "") + `[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType=WindowsRuntime] | Out-Null; [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType=WindowsRuntime] | Out-Null; $doc = New-Object Windows.Data.Xml.Dom.XmlDocument; $doc.LoadXml(${psQuote(toastXml)}); $toast = New-Object Windows.UI.Notifications.ToastNotification $doc; [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($aumid).Show($toast)`;
3095
- }
3096
- var MAC_SOUND = "Glass";
3097
- function buildNotifyCommand(platform, title, message, options = {}) {
3098
- const { url, hasTerminalNotifier: hasTerminalNotifier2 = false, iconPath } = options;
3099
- switch (platform) {
3100
- case "darwin":
3101
- if (url && hasTerminalNotifier2) {
3102
- return {
3103
- cmd: "terminal-notifier",
3104
- args: [
3105
- "-title",
3106
- title,
3107
- "-message",
3108
- message,
3109
- "-open",
3110
- url,
3111
- "-sound",
3112
- MAC_SOUND,
3113
- ...iconPath ? ["-appIcon", iconPath] : [],
3114
- "-group",
3115
- "ai.suveren.gateway"
3116
- ]
3117
- };
3118
- }
3119
- return {
3120
- cmd: "osascript",
3121
- args: [
3122
- "-e",
3123
- `display notification ${osaQuote(message)} with title ${osaQuote(title)} sound name ${osaQuote(MAC_SOUND)}`
3124
- ]
3125
- };
3126
- case "win32":
3127
- return {
3128
- cmd: "powershell",
3129
- args: ["-NoProfile", "-NonInteractive", "-Command", windowsToastScript(title, message, url, iconPath)]
3130
- };
3131
- case "linux":
3132
- return {
3133
- cmd: "notify-send",
3134
- args: [
3135
- "--app-name=Suveren",
3136
- ...iconPath ? ["-i", iconPath] : [],
3137
- title,
3138
- message
3139
- ]
3140
- };
3141
- default:
3142
- return null;
3143
- }
3144
- }
3145
- function lockedNotification(port2) {
3146
- const url = `http://localhost:${port2}`;
3147
- return {
3148
- title: "Suveren is running but locked",
3149
- message: `Your agent has no authority until you unlock it. Open ${url} and enter your API key.`,
3150
- url
3151
- };
3152
- }
3153
- function sessionExpiredNotification(port2) {
3154
- const url = `http://localhost:${port2}`;
3155
- return {
3156
- title: "Suveren Gateway locked",
3157
- message: "Your sign-in has ended. Sign in again so your agents can act.",
3158
- url
3159
- };
3160
- }
3161
- var terminalNotifierCache = null;
3162
- function hasTerminalNotifier() {
3163
- if (terminalNotifierCache !== null) return terminalNotifierCache;
3164
- try {
3165
- const probe = spawnSync2("terminal-notifier", ["-help"], { stdio: "ignore" });
3166
- terminalNotifierCache = probe.error === void 0;
3167
- } catch {
3168
- terminalNotifierCache = false;
3169
- }
3170
- return terminalNotifierCache;
3171
- }
3172
- function notifySpawnOptions(platform) {
3173
- return {
3174
- detached: platform !== "win32",
3175
- stdio: ["ignore", "ignore", "pipe"]
3176
- };
3177
- }
3178
- function notify(title, message, platform = process.platform, url) {
3179
- const command = buildNotifyCommand(platform, title, message, {
3180
- url,
3181
- hasTerminalNotifier: platform === "darwin" ? hasTerminalNotifier() : false,
3182
- // Windows and Linux show it on the toast itself; macOS only via
3183
- // terminal-notifier. Failure to write it must never block the sound.
3184
- iconPath: ensureNotifierIcon()
3185
- });
3186
- if (!command) return;
3187
- try {
3188
- const child = spawn(command.cmd, command.args, notifySpawnOptions(platform));
3189
- let stderr = "";
3190
- child.stderr?.on("data", (d) => {
3191
- stderr += d.toString().slice(0, 500);
3192
- });
3193
- child.on("close", (code) => {
3194
- if (code !== 0) {
3195
- console.error(`[notify] ${command.cmd} exited ${code}${stderr ? `: ${stderr.trim()}` : ""}`);
3196
- }
3197
- });
3198
- child.on("error", () => {
3199
- });
3200
- child.unref();
3201
- } catch {
3202
- }
3203
- }
3204
-
3205
3357
  // src/routes/encrypt-intent.ts
3206
3358
  import { Router as Router7 } from "express";
3207
3359
 
@@ -4380,62 +4532,6 @@ function startNotificationDispatcher(opts = {}) {
4380
4532
  return new NotificationDispatcher(opts).start();
4381
4533
  }
4382
4534
 
4383
- // src/lib/session-lock.ts
4384
- var SESSION_LOCKED_MESSAGE = "Your sign-in ended after 30 days or was revoked. Sign in again.";
4385
- var AS_KEY_MISMATCH_MESSAGE = "The Authority Server presented a signing key that does not match the one pinned at pairing. Sign in again to review.";
4386
- var AS_TLS_MISMATCH_MESSAGE = "A connection to the Authority Server presented a TLS certificate that does not match the one pinned at pairing. The connection was refused before anything was sent. If the certificate changed intentionally (a new key, not just renewal), clear the pairing and sign in again to re-pin it.";
4387
- function createSessionLock(deps) {
4388
- const { vault: vault2, port: port2 } = deps;
4389
- const notifyFn = deps.notifyFn ?? notify;
4390
- const unconfigureSessionFn = deps.unconfigureSessionFn ?? unconfigureSession;
4391
- const emit = deps.emit ?? eventBus.emit.bind(eventBus);
4392
- return function lockExpiredSession2() {
4393
- if (!vault2.isUnlocked()) return;
4394
- vault2.lockExpired();
4395
- console.error("[Control Plane] Authority Server session ended \u2014 gateway LOCKED");
4396
- void unconfigureSessionFn().catch((err) => {
4397
- console.error("[Control Plane] Failed to push cleared session to MCP:", err);
4398
- });
4399
- emit("session-locked", { reason: "expired", message: SESSION_LOCKED_MESSAGE });
4400
- const { title, message, url } = sessionExpiredNotification(port2);
4401
- notifyFn(title, message, process.platform, url);
4402
- };
4403
- }
4404
- function createAsKeyMismatchLock(deps) {
4405
- const { vault: vault2, port: port2 } = deps;
4406
- const notifyFn = deps.notifyFn ?? notify;
4407
- const unconfigureSessionFn = deps.unconfigureSessionFn ?? unconfigureSession;
4408
- const emit = deps.emit ?? eventBus.emit.bind(eventBus);
4409
- return function lockOnAsKeyMismatch() {
4410
- if (!vault2.isUnlocked()) return;
4411
- vault2.lockAsKeyMismatch();
4412
- console.error("[Control Plane] Authority Server key mismatch \u2014 gateway LOCKED");
4413
- void unconfigureSessionFn("as-key-mismatch").catch((err) => {
4414
- console.error("[Control Plane] Failed to push cleared session to MCP:", err);
4415
- });
4416
- emit("session-locked", { reason: "as-key-mismatch", message: AS_KEY_MISMATCH_MESSAGE });
4417
- const { title, url } = sessionExpiredNotification(port2);
4418
- notifyFn(title, AS_KEY_MISMATCH_MESSAGE, process.platform, url);
4419
- };
4420
- }
4421
- function createAsTlsMismatchLock(deps) {
4422
- const { vault: vault2, port: port2 } = deps;
4423
- const notifyFn = deps.notifyFn ?? notify;
4424
- const unconfigureSessionFn = deps.unconfigureSessionFn ?? unconfigureSession;
4425
- const emit = deps.emit ?? eventBus.emit.bind(eventBus);
4426
- return function lockOnAsTlsMismatch() {
4427
- if (!vault2.isUnlocked()) return;
4428
- vault2.lockAsTlsMismatch();
4429
- console.error("[Control Plane] Authority Server TLS pin mismatch \u2014 gateway LOCKED");
4430
- void unconfigureSessionFn("as-tls-mismatch").catch((err) => {
4431
- console.error("[Control Plane] Failed to push cleared session to MCP:", err);
4432
- });
4433
- emit("session-locked", { reason: "as-tls-mismatch", message: AS_TLS_MISMATCH_MESSAGE });
4434
- const { title, url } = sessionExpiredNotification(port2);
4435
- notifyFn(title, AS_TLS_MISMATCH_MESSAGE, process.platform, url);
4436
- };
4437
- }
4438
-
4439
4535
  // src/lib/session-expiry-scheduler.ts
4440
4536
  var MAX_TIMEOUT_MS = 2147483647;
4441
4537
  var SessionExpiryScheduler = class {
@@ -4555,6 +4651,7 @@ var SP_URL = resolveAsUrl(DATA_DIR);
4555
4651
  validatePinTlsForUrl(resolvePinTls(DATA_DIR), SP_URL);
4556
4652
  var POLICY_LOCKED = [...readPolicy().locked];
4557
4653
  var POLICY_LOCKED_INSTALL_METHOD = readPolicy().policy.installMethod;
4654
+ var POLICY_LOCKED_TEAM = readPolicy().policy.team;
4558
4655
  var port = parseInt(process.env.SUVEREN_CP_PORT ?? "3402", 10);
4559
4656
  var HAP_MODE = process.env.HAP_MODE ?? "personal";
4560
4657
  var UI_DIST = process.env.HAP_UI_DIST ?? join14(import.meta.dirname ?? __dirname, "../../ui/dist");
@@ -4594,6 +4691,7 @@ function loginRateLimit(req, res, next) {
4594
4691
  var lockExpiredSession = createSessionLock({ vault, port });
4595
4692
  var lockAsKeyMismatch = createAsKeyMismatchLock({ vault, port });
4596
4693
  var lockAsTlsMismatch = createAsTlsMismatchLock({ vault, port });
4694
+ var lockManagedWrongTeam = createManagedWrongTeamLock({ vault, port });
4597
4695
  var sessionExpiryScheduler = new SessionExpiryScheduler(
4598
4696
  () => vault.getSessionExpiresAt(),
4599
4697
  lockExpiredSession
@@ -4603,8 +4701,26 @@ app.use("/auth", jsonParser, createAuthRouter(
4603
4701
  requireAuth(vault),
4604
4702
  loginRateLimit,
4605
4703
  () => sessionExpiryScheduler.reschedule(),
4606
- { asUrl: SP_URL, dataDir: DATA_DIR }
4704
+ { asUrl: SP_URL, dataDir: DATA_DIR },
4705
+ lockAsTlsMismatch,
4706
+ POLICY_LOCKED_TEAM
4607
4707
  ));
4708
+ var managedTeamScheduler = POLICY_LOCKED_TEAM ? new ManagedTeamScheduler(async () => {
4709
+ if (!vault.isUnlocked()) return;
4710
+ const cookie = vault.getSpCookie();
4711
+ if (!cookie) return;
4712
+ try {
4713
+ const pin = readPairing(DATA_DIR);
4714
+ const team = await activeTeamId(SP_URL, cookie, {
4715
+ enforce: resolvePinTls(DATA_DIR),
4716
+ pinnedSpkiHex: pin && pin.asUrl === SP_URL ? pin.tlsSpkiPinHex : void 0
4717
+ });
4718
+ if (team !== POLICY_LOCKED_TEAM) lockManagedWrongTeam(POLICY_LOCKED_TEAM);
4719
+ } catch (err) {
4720
+ console.error("[Control Plane] Managed-team check failed (will retry):", err);
4721
+ }
4722
+ }) : null;
4723
+ managedTeamScheduler?.start();
4608
4724
  function resolveOrigin(req) {
4609
4725
  const fwdHost = req.get("x-forwarded-host");
4610
4726
  if (fwdHost) return `${req.protocol}://${fwdHost}`;
@@ -5141,8 +5257,13 @@ app.use(
5141
5257
  eventBus.emit("proposal-approved");
5142
5258
  } else if (method === "POST" && /^\/api\/proposals\/[^/]+\/reject$/.test(path)) {
5143
5259
  eventBus.emit("proposal-rejected");
5144
- } else if (method === "POST" && (path === "/api/groups" || path === "/api/groups/join" || /^\/api\/groups\/[^/]+\/(leave|disable|reactivate|admin-transfer)$/.test(path))) {
5260
+ } else if (method === "POST" && (path === "/api/groups" || path === "/api/groups/join" || /^\/api\/groups\/[^/]+\/(leave|disable|reactivate|admin-transfer)$/.test(path)) || // WS0 (work-plan "Added 2026-10-11"): withdrawing a pending join
5261
+ // request — the account stays Personal, but the onboarding UI's
5262
+ // waiting state ends, same as every other membership-affecting
5263
+ // call here.
5264
+ method === "DELETE" && path === "/api/groups/join") {
5145
5265
  eventBus.emit("team-membership-changed");
5266
+ if (managedTeamScheduler) void managedTeamScheduler.runNow();
5146
5267
  }
5147
5268
  }
5148
5269
  }