@trops/dash-core 0.1.508 → 0.1.510

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.
@@ -3,6 +3,7 @@
3
3
  var require$$0$1 = require('electron');
4
4
  var require$$1$1 = require('path');
5
5
  var require$$0$2 = require('fs');
6
+ var require$$0$3 = require('crypto');
6
7
  var require$$9$1 = require('objects-to-csv');
7
8
  var require$$1$2 = require('readline');
8
9
  var require$$2 = require('xtreamer');
@@ -10,30 +11,29 @@ var require$$3$1 = require('xml2js');
10
11
  var require$$4 = require('JSONStream');
11
12
  var require$$5 = require('stream');
12
13
  var require$$6 = require('csv-parser');
13
- var require$$0$3 = require('quickjs-emscripten');
14
+ var require$$0$4 = require('quickjs-emscripten');
14
15
  var require$$11 = require('https');
15
- var require$$0$5 = require('@modelcontextprotocol/sdk/client/index.js');
16
+ var require$$0$6 = require('@modelcontextprotocol/sdk/client/index.js');
16
17
  var require$$1$3 = require('@modelcontextprotocol/sdk/client/stdio.js');
17
- var require$$0$4 = require('pkce-challenge');
18
+ var require$$0$5 = require('pkce-challenge');
18
19
  var require$$2$1 = require('os');
19
20
  var require$$12 = require('child_process');
20
- var require$$0$6 = require('electron-store');
21
+ var require$$0$7 = require('electron-store');
21
22
  var require$$3$2 = require('adm-zip');
22
23
  var require$$4$1 = require('url');
23
24
  var require$$2$2 = require('vm');
24
25
  var require$$1$4 = require('croner');
25
26
  var require$$2$3 = require('algoliasearch');
26
27
  var require$$3$3 = require('node:path');
27
- var require$$0$7 = require('openai');
28
- var require$$0$a = require('@anthropic-ai/sdk');
29
- var require$$3$4 = require('crypto');
28
+ var require$$0$8 = require('openai');
29
+ var require$$0$b = require('@anthropic-ai/sdk');
30
30
  var require$$8$1 = require('zod');
31
- var require$$0$8 = require('http');
31
+ var require$$0$9 = require('http');
32
32
  var require$$1$5 = require('http2');
33
33
  var require$$2$4 = require('node-forge');
34
- var require$$0$9 = require('css');
34
+ var require$$0$a = require('css');
35
35
  var require$$1$6 = require('node-vibrant/node');
36
- var require$$3$5 = require('ws');
36
+ var require$$3$4 = require('ws');
37
37
 
38
38
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
39
39
 
@@ -993,14 +993,14 @@ var events$8 = {
993
993
  * Open a dialog window for choosing files
994
994
  */
995
995
 
996
- const { dialog: dialog$2 } = require$$0$1;
996
+ const { dialog: dialog$3 } = require$$0$1;
997
997
  const events$7 = events$8;
998
998
 
999
999
  const showDialog$1 = async (win, message, allowFile, extensions = ["*"]) => {
1000
1000
  const properties =
1001
1001
  allowFile === true ? ["openFile"] : ["openDirectory", "createDirectory"];
1002
1002
  const filters = allowFile === true ? [{ name: "Data", extensions }] : [];
1003
- const result = await dialog$2.showOpenDialog({ properties, filters });
1003
+ const result = await dialog$3.showOpenDialog({ properties, filters });
1004
1004
  if (result.canceled || !result.filePaths[0]) return null;
1005
1005
  return result.filePaths[0];
1006
1006
  };
@@ -2083,7 +2083,7 @@ var grantedPermissions = {
2083
2083
  * _resetForTest() → void (test-only)
2084
2084
  */
2085
2085
 
2086
- const { BrowserWindow: BrowserWindow$2, ipcMain: ipcMain$2 } = require$$0$1;
2086
+ const { BrowserWindow: BrowserWindow$3, ipcMain: ipcMain$3 } = require$$0$1;
2087
2087
 
2088
2088
  const REQUEST_CHANNEL = "widget:permission-required";
2089
2089
  const RESPONSE_CHANNEL = "widget:permission-response";
@@ -2129,7 +2129,7 @@ function coalesceKeyOf(req) {
2129
2129
  function emitEvent(payload) {
2130
2130
  let wins = [];
2131
2131
  try {
2132
- wins = BrowserWindow$2.getAllWindows() || [];
2132
+ wins = BrowserWindow$3.getAllWindows() || [];
2133
2133
  } catch {
2134
2134
  wins = [];
2135
2135
  }
@@ -2248,8 +2248,8 @@ let _handlersRegistered = false;
2248
2248
  */
2249
2249
  function setupJitConsentHandlers() {
2250
2250
  if (_handlersRegistered) return;
2251
- if (!ipcMain$2 || typeof ipcMain$2.on !== "function") return;
2252
- ipcMain$2.on(RESPONSE_CHANNEL, (_event, payload) => {
2251
+ if (!ipcMain$3 || typeof ipcMain$3.on !== "function") return;
2252
+ ipcMain$3.on(RESPONSE_CHANNEL, (_event, payload) => {
2253
2253
  _handleResponse(payload);
2254
2254
  });
2255
2255
  _handlersRegistered = true;
@@ -2265,6 +2265,93 @@ var jitConsent$1 = {
2265
2265
  DEFAULT_TIMEOUT_MS,
2266
2266
  };
2267
2267
 
2268
+ /**
2269
+ * mountTokenRegistry.js
2270
+ *
2271
+ * Trusted root of widget identity at the IPC boundary. Each time
2272
+ * `WidgetFactory` mounts a widget it calls
2273
+ * `framework:register-widget-mount`, which calls `register(widgetId)`
2274
+ * here. The returned token is baked into the widget's bound API
2275
+ * (`makeBoundApi`) and sent on every gated IPC call. Gates resolve the
2276
+ * widgetId via `lookup(token)` instead of trusting whatever the
2277
+ * renderer claims.
2278
+ *
2279
+ * Why server-generated tokens: the renderer-supplied widgetId path was
2280
+ * the original consent-bypass surface. Tokens are produced by
2281
+ * `crypto.randomBytes(24)` here (192 bits) and are NEVER accepted from
2282
+ * the renderer — the renderer can only present tokens it received from
2283
+ * a prior `register` call. A widget cannot fabricate a token for
2284
+ * another widgetId.
2285
+ *
2286
+ * Limit: in single-renderer (one BrowserWindow shared across all
2287
+ * widgets), a malicious widget can still walk the React fiber tree to
2288
+ * find another widget's bound API and call its functions — the bound
2289
+ * function fires IPC with the *victim's* token. Fully closing that
2290
+ * residual requires per-widget BrowserView (multi-week refactor). The
2291
+ * token model raises the bar from "type a widgetId string" to "walk
2292
+ * the fiber tree and call another widget's bound function," which is
2293
+ * a deliberate malicious step that's visible at install-time review.
2294
+ */
2295
+
2296
+ const crypto$1 = require$$0$3;
2297
+
2298
+ const _byToken = new Map(); // token → widgetId
2299
+
2300
+ function _generateToken() {
2301
+ // 24 random bytes = 48 hex chars = 192 bits of entropy. More than
2302
+ // enough; collision probability is negligible.
2303
+ return crypto$1.randomBytes(24).toString("hex");
2304
+ }
2305
+
2306
+ /**
2307
+ * Register a widgetId mount and return a fresh token bound to it.
2308
+ * @param {string} widgetId
2309
+ * @returns {string} token
2310
+ */
2311
+ function register$1(widgetId) {
2312
+ if (typeof widgetId !== "string" || widgetId.length === 0) {
2313
+ throw new Error(
2314
+ "mountTokenRegistry.register: widgetId must be a non-empty string",
2315
+ );
2316
+ }
2317
+ let token = _generateToken();
2318
+ // Defensive — collision is astronomically unlikely but if it did
2319
+ // happen we'd silently overwrite a valid mapping. Regenerate.
2320
+ while (_byToken.has(token)) {
2321
+ token = _generateToken();
2322
+ }
2323
+ _byToken.set(token, widgetId);
2324
+ return token;
2325
+ }
2326
+
2327
+ /**
2328
+ * Resolve a token to the widgetId it was bound to.
2329
+ * @param {string} token
2330
+ * @returns {string|null}
2331
+ */
2332
+ function lookup(token) {
2333
+ if (typeof token !== "string") return null;
2334
+ return _byToken.has(token) ? _byToken.get(token) : null;
2335
+ }
2336
+
2337
+ /**
2338
+ * Drop a token. Silent no-op for unknown tokens or non-strings —
2339
+ * unregister is called from unmount cleanup paths and should never
2340
+ * throw.
2341
+ * @param {string} token
2342
+ */
2343
+ function unregister$1(token) {
2344
+ if (typeof token !== "string") return;
2345
+ _byToken.delete(token);
2346
+ }
2347
+
2348
+ /** Test-only — clears the registry between cases. */
2349
+ function _resetForTests() {
2350
+ _byToken.clear();
2351
+ }
2352
+
2353
+ var mountTokenRegistry = { register: register$1, lookup, unregister: unregister$1, _resetForTests };
2354
+
2268
2355
  /**
2269
2356
  * fsGate.js
2270
2357
  *
@@ -2302,6 +2389,25 @@ var jitConsent$1 = {
2302
2389
 
2303
2390
  const { getGrant: getGrant$3, setGrant: setGrant$3 } = grantedPermissions;
2304
2391
  const { requestApproval: requestApproval$2 } = jitConsent$1;
2392
+ const { lookup: lookupMountToken$2 } = mountTokenRegistry;
2393
+
2394
+ // If a token is supplied, the gate resolves widgetId via the mount
2395
+ // registry and ignores any renderer-supplied widgetId. Tokens are
2396
+ // server-generated and bound to a widgetId at WidgetFactory mount —
2397
+ // the renderer cannot fabricate a token for another widget. See
2398
+ // `mountTokenRegistry.js`.
2399
+ //
2400
+ // Legacy callers without a token fall through to the existing
2401
+ // widgetId-based path (Slice 1 keeps this for back-compat; Slice 2
2402
+ // flips to deny).
2403
+ function _resolveIdentity$2({ token, widgetId }) {
2404
+ if (typeof token === "string" && token.length > 0) {
2405
+ const resolved = lookupMountToken$2(token);
2406
+ if (resolved) return { widgetId: resolved, source: "token" };
2407
+ return { widgetId: null, source: "token-unknown" };
2408
+ }
2409
+ return { widgetId: widgetId || null, source: "legacy" };
2410
+ }
2305
2411
 
2306
2412
  // Action names treated as writes. Anything not in this set is a read.
2307
2413
  // Conservative — when in doubt, classify as a read so write-protected
@@ -2336,7 +2442,15 @@ function _filenameMatches(filename, allowedList) {
2336
2442
  * Synchronous gate evaluation.
2337
2443
  * @returns {{ allow: true } | { allow: false, reason: string }}
2338
2444
  */
2339
- function gateFsCall$1({ widgetId, action, args }) {
2445
+ function gateFsCall$1({ widgetId, token, action, args }) {
2446
+ const resolved = _resolveIdentity$2({ token, widgetId });
2447
+ if (resolved.source === "token-unknown") {
2448
+ return {
2449
+ allow: false,
2450
+ reason: "fs gate: unknown mount token; widget identity not verifiable",
2451
+ };
2452
+ }
2453
+ widgetId = resolved.widgetId;
2340
2454
  if (!widgetId) {
2341
2455
  return {
2342
2456
  allow: false,
@@ -2460,11 +2574,21 @@ async function gateFsCallWithJit$1(req, opts = {}) {
2460
2574
  if (!opts.enableJit) return initial;
2461
2575
  if (!_isNoGrantDenial$2(initial.reason)) return initial;
2462
2576
 
2577
+ // Resolve verified identity once (token wins over claimed widgetId).
2578
+ // Re-using the same resolution as gateFsCall keeps the JIT prompt
2579
+ // and grant write tied to the same identity the gate just denied.
2580
+ const resolved = _resolveIdentity$2({
2581
+ token: req.token,
2582
+ widgetId: req.widgetId,
2583
+ });
2584
+ const verifiedWidgetId = resolved.widgetId;
2585
+ if (!verifiedWidgetId) return initial;
2586
+
2463
2587
  let decision;
2464
2588
  try {
2465
2589
  decision = await requestApproval$2(
2466
2590
  {
2467
- widgetId: req.widgetId,
2591
+ widgetId: verifiedWidgetId,
2468
2592
  domain: "fs",
2469
2593
  action: req.action,
2470
2594
  args: req.args || {},
@@ -2487,7 +2611,7 @@ async function gateFsCallWithJit$1(req, opts = {}) {
2487
2611
  allow: false,
2488
2612
  reason:
2489
2613
  "user declined JIT consent for widget '" +
2490
- req.widgetId +
2614
+ verifiedWidgetId +
2491
2615
  "' calling fs '" +
2492
2616
  req.action +
2493
2617
  "'",
@@ -2511,9 +2635,9 @@ async function gateFsCallWithJit$1(req, opts = {}) {
2511
2635
  addition.grantOrigin = "live";
2512
2636
 
2513
2637
  try {
2514
- const current = getGrant$3(req.widgetId);
2638
+ const current = getGrant$3(verifiedWidgetId);
2515
2639
  const merged = _mergeFsGrant(current, addition);
2516
- setGrant$3(req.widgetId, merged);
2640
+ setGrant$3(verifiedWidgetId, merged);
2517
2641
  } catch (e) {
2518
2642
  return {
2519
2643
  allow: false,
@@ -2575,6 +2699,20 @@ var fsGate = {
2575
2699
 
2576
2700
  const { getGrant: getGrant$2, setGrant: setGrant$2 } = grantedPermissions;
2577
2701
  const { requestApproval: requestApproval$1 } = jitConsent$1;
2702
+ const { lookup: lookupMountToken$1 } = mountTokenRegistry;
2703
+
2704
+ // See `mountTokenRegistry.js` and the matching block in fsGate.js —
2705
+ // when a token is supplied, it's the trusted identity source; the
2706
+ // renderer-claimed widgetId is ignored. Legacy widgetId-only callers
2707
+ // still work in slice 1 (additive); slice 2 will flip to deny.
2708
+ function _resolveIdentity$1({ token, widgetId }) {
2709
+ if (typeof token === "string" && token.length > 0) {
2710
+ const resolved = lookupMountToken$1(token);
2711
+ if (resolved) return { widgetId: resolved, source: "token" };
2712
+ return { widgetId: null, source: "token-unknown" };
2713
+ }
2714
+ return { widgetId: widgetId || null, source: "legacy" };
2715
+ }
2578
2716
 
2579
2717
  function _isNoGrantDenial$1(reason) {
2580
2718
  return (
@@ -2617,7 +2755,16 @@ function _parseHost(url) {
2617
2755
  * Synchronous gate evaluation.
2618
2756
  * @returns {{ allow: true } | { allow: false, reason: string }}
2619
2757
  */
2620
- function gateNetworkCall$2({ widgetId, action, args }) {
2758
+ function gateNetworkCall$2({ widgetId, token, action, args }) {
2759
+ const resolved = _resolveIdentity$1({ token, widgetId });
2760
+ if (resolved.source === "token-unknown") {
2761
+ return {
2762
+ allow: false,
2763
+ reason:
2764
+ "network gate: unknown mount token; widget identity not verifiable",
2765
+ };
2766
+ }
2767
+ widgetId = resolved.widgetId;
2621
2768
  if (!widgetId) {
2622
2769
  return {
2623
2770
  allow: false,
@@ -2708,11 +2855,21 @@ async function gateNetworkCallWithJit$2(req, opts = {}) {
2708
2855
  if (!opts.enableJit) return initial;
2709
2856
  if (!_isNoGrantDenial$1(initial.reason)) return initial;
2710
2857
 
2858
+ // Same identity-resolution as the sync gate — the JIT prompt and
2859
+ // grant write must use the verified widgetId, not whatever the
2860
+ // renderer claimed.
2861
+ const resolved = _resolveIdentity$1({
2862
+ token: req.token,
2863
+ widgetId: req.widgetId,
2864
+ });
2865
+ const verifiedWidgetId = resolved.widgetId;
2866
+ if (!verifiedWidgetId) return initial;
2867
+
2711
2868
  let decision;
2712
2869
  try {
2713
2870
  decision = await requestApproval$1(
2714
2871
  {
2715
- widgetId: req.widgetId,
2872
+ widgetId: verifiedWidgetId,
2716
2873
  domain: "network",
2717
2874
  action: req.action,
2718
2875
  args: req.args || {},
@@ -2735,7 +2892,7 @@ async function gateNetworkCallWithJit$2(req, opts = {}) {
2735
2892
  allow: false,
2736
2893
  reason:
2737
2894
  "user declined JIT consent for widget '" +
2738
- req.widgetId +
2895
+ verifiedWidgetId +
2739
2896
  "' calling network '" +
2740
2897
  req.action +
2741
2898
  "'",
@@ -2753,9 +2910,9 @@ async function gateNetworkCallWithJit$2(req, opts = {}) {
2753
2910
  addition.grantOrigin = "live";
2754
2911
 
2755
2912
  try {
2756
- const current = getGrant$2(req.widgetId);
2913
+ const current = getGrant$2(verifiedWidgetId);
2757
2914
  const merged = _mergeNetworkGrant(current, addition);
2758
- setGrant$2(req.widgetId, merged);
2915
+ setGrant$2(verifiedWidgetId, merged);
2759
2916
  } catch (e) {
2760
2917
  return {
2761
2918
  allow: false,
@@ -2867,7 +3024,7 @@ function requireSafeJsExecutor () {
2867
3024
  let _modulePromise = null;
2868
3025
  function getModule() {
2869
3026
  if (!_modulePromise) {
2870
- const { getQuickJS } = require$$0$3;
3027
+ const { getQuickJS } = require$$0$4;
2871
3028
  _modulePromise = getQuickJS();
2872
3029
  }
2873
3030
  return _modulePromise;
@@ -3541,13 +3698,19 @@ function _loadFlags$1() {
3541
3698
  *
3542
3699
  * @returns {Promise<boolean>}
3543
3700
  */
3544
- async function _runFsGate(win, action, widgetId, args, errorEvent) {
3701
+ async function _runFsGate(win, action, widgetId, args, errorEvent, token) {
3545
3702
  const settings = _loadFlags$1();
3546
3703
  if (!readEnforceFlag$2(settings)) return true; // gate disabled
3547
- if (!widgetId) return true; // legacy callers without widgetId — see plan
3704
+ // Slice 1 (additive): token is the trusted identity when present.
3705
+ // If neither token nor widgetId is supplied, legacy bypass still
3706
+ // applies; slice 2 will flip this to deny.
3707
+ if (!widgetId && !token) return true;
3548
3708
  const gate = readJitFlag$2(settings)
3549
- ? await gateFsCallWithJit({ widgetId, action, args }, { enableJit: true })
3550
- : gateFsCall({ widgetId, action, args });
3709
+ ? await gateFsCallWithJit(
3710
+ { widgetId, token, action, args },
3711
+ { enableJit: true },
3712
+ )
3713
+ : gateFsCall({ widgetId, token, action, args });
3551
3714
  if (gate.allow) return true;
3552
3715
  if (win && errorEvent) {
3553
3716
  win.webContents.send(errorEvent, {
@@ -3562,16 +3725,16 @@ async function _runFsGate(win, action, widgetId, args, errorEvent) {
3562
3725
  * Phase 3 network gate. Same shape as _runFsGate but for outbound
3563
3726
  * URLs. Mirrors fs's "disabled / no widgetId / sync vs async" branching.
3564
3727
  */
3565
- async function _runNetworkGate$1(win, action, widgetId, args, errorEvent) {
3728
+ async function _runNetworkGate$1(win, action, widgetId, args, errorEvent, token) {
3566
3729
  const settings = _loadFlags$1();
3567
3730
  if (!readEnforceFlag$2(settings)) return true;
3568
- if (!widgetId) return true;
3731
+ if (!widgetId && !token) return true;
3569
3732
  const gate = readJitFlag$2(settings)
3570
3733
  ? await gateNetworkCallWithJit$1(
3571
- { widgetId, action, args },
3734
+ { widgetId, token, action, args },
3572
3735
  { enableJit: true },
3573
3736
  )
3574
- : gateNetworkCall$1({ widgetId, action, args });
3737
+ : gateNetworkCall$1({ widgetId, token, action, args });
3575
3738
  if (gate.allow) return true;
3576
3739
  if (win && errorEvent) {
3577
3740
  win.webContents.send(errorEvent, {
@@ -3741,7 +3904,13 @@ const dataController$1 = {
3741
3904
  }
3742
3905
  },
3743
3906
 
3744
- readDataFromURL: async (win, url, toFilepath, widgetId = null) => {
3907
+ readDataFromURL: async (
3908
+ win,
3909
+ url,
3910
+ toFilepath,
3911
+ widgetId = null,
3912
+ token = null,
3913
+ ) => {
3745
3914
  // Phase 3 network gate. Runs before HTTPS-protocol + safePath
3746
3915
  // checks so JIT can prompt the user without leaking URL parser
3747
3916
  // edge cases through error timing.
@@ -3751,6 +3920,7 @@ const dataController$1 = {
3751
3920
  widgetId,
3752
3921
  { url },
3753
3922
  events$5.READ_DATA_URL_ERROR,
3923
+ token,
3754
3924
  );
3755
3925
  if (!gateOk) return;
3756
3926
  try {
@@ -3938,6 +4108,7 @@ const dataController$1 = {
3938
4108
  append,
3939
4109
  returnEmpty = {},
3940
4110
  widgetId = null,
4111
+ token = null,
3941
4112
  ) => {
3942
4113
  // Phase 2 fs gate. Runs before safePath containment so JIT can
3943
4114
  // prompt the user without leaking path-shape information through
@@ -3948,6 +4119,7 @@ const dataController$1 = {
3948
4119
  widgetId,
3949
4120
  { filename },
3950
4121
  events$5.DATA_SAVE_TO_FILE_ERROR,
4122
+ token,
3951
4123
  );
3952
4124
  if (!gateOk) return;
3953
4125
  try {
@@ -4043,7 +4215,13 @@ const dataController$1 = {
4043
4215
  }
4044
4216
  },
4045
4217
 
4046
- readFromFile: async (win, filename, returnIfEmpty = {}, widgetId = null) => {
4218
+ readFromFile: async (
4219
+ win,
4220
+ filename,
4221
+ returnIfEmpty = {},
4222
+ widgetId = null,
4223
+ token = null,
4224
+ ) => {
4047
4225
  // Phase 2 fs gate — same as saveToFile.
4048
4226
  const gateOk = await _runFsGate(
4049
4227
  win,
@@ -4051,6 +4229,7 @@ const dataController$1 = {
4051
4229
  widgetId,
4052
4230
  { filename },
4053
4231
  events$5.DATA_READ_FROM_FILE_ERROR,
4232
+ token,
4054
4233
  );
4055
4234
  if (!gateOk) return;
4056
4235
  try {
@@ -20707,7 +20886,7 @@ auth$2.exchangeAuthorization = exchangeAuthorization;
20707
20886
  auth$2.refreshAuthorization = refreshAuthorization;
20708
20887
  auth$2.fetchToken = fetchToken;
20709
20888
  auth$2.registerClient = registerClient;
20710
- const pkce_challenge_1 = __importDefault$2(require$$0$4);
20889
+ const pkce_challenge_1 = __importDefault$2(require$$0$5);
20711
20890
  const types_js_1$5 = types$2;
20712
20891
  const auth_js_1$1 = auth$1;
20713
20892
  const auth_js_2 = auth$1;
@@ -22265,6 +22444,21 @@ streamableHttp$1.StreamableHTTPClientTransport = StreamableHTTPClientTransport$1
22265
22444
  const { getGrant: getGrant$1, setGrant: setGrant$1 } = grantedPermissions;
22266
22445
  const { safePath: safePath$1 } = safePath_1;
22267
22446
  const { requestApproval } = jitConsent$1;
22447
+ const { lookup: lookupMountToken } = mountTokenRegistry;
22448
+
22449
+ // See `electron/security/mountTokenRegistry.js`. When a token is
22450
+ // supplied it's the trusted identity source — gates resolve widgetId
22451
+ // via lookupMountToken and ignore renderer-claimed widgetId. Slice 1
22452
+ // keeps the legacy widgetId-only path working (additive); slice 2 will
22453
+ // flip to deny-without-token.
22454
+ function _resolveIdentity({ token, widgetId }) {
22455
+ if (typeof token === "string" && token.length > 0) {
22456
+ const resolved = lookupMountToken(token);
22457
+ if (resolved) return { widgetId: resolved, source: "token" };
22458
+ return { widgetId: null, source: "token-unknown" };
22459
+ }
22460
+ return { widgetId: widgetId || null, source: "legacy" };
22461
+ }
22268
22462
 
22269
22463
  // Argument keys that look like paths. Different MCP servers use
22270
22464
  // different conventions; this list covers the common filesystem-style
@@ -22286,7 +22480,15 @@ function isWriteTool(toolName) {
22286
22480
  /**
22287
22481
  * @returns {{ allow: true } | { allow: false, reason: string }}
22288
22482
  */
22289
- function gateToolCall$1({ widgetId, serverName, toolName, args }) {
22483
+ function gateToolCall$1({ widgetId, token, serverName, toolName, args }) {
22484
+ const resolved = _resolveIdentity({ token, widgetId });
22485
+ if (resolved.source === "token-unknown") {
22486
+ return {
22487
+ allow: false,
22488
+ reason: "MCP gate: unknown mount token; widget identity not verifiable",
22489
+ };
22490
+ }
22491
+ widgetId = resolved.widgetId;
22290
22492
  if (!widgetId) {
22291
22493
  return {
22292
22494
  allow: false,
@@ -22440,11 +22642,20 @@ async function gateToolCallWithJit$1(req, opts = {}) {
22440
22642
  if (!opts.enableJit) return initial;
22441
22643
  if (!_isNoGrantDenial(initial.reason)) return initial;
22442
22644
 
22645
+ // Same identity-resolution as the sync gate — JIT prompt and grant
22646
+ // write must use the verified widgetId.
22647
+ const resolved = _resolveIdentity({
22648
+ token: req.token,
22649
+ widgetId: req.widgetId,
22650
+ });
22651
+ const verifiedWidgetId = resolved.widgetId;
22652
+ if (!verifiedWidgetId) return initial;
22653
+
22443
22654
  let decision;
22444
22655
  try {
22445
22656
  decision = await requestApproval(
22446
22657
  {
22447
- widgetId: req.widgetId,
22658
+ widgetId: verifiedWidgetId,
22448
22659
  domain: "mcp",
22449
22660
  action: "callTool",
22450
22661
  args: {
@@ -22471,7 +22682,7 @@ async function gateToolCallWithJit$1(req, opts = {}) {
22471
22682
  allow: false,
22472
22683
  reason:
22473
22684
  "user declined JIT consent for widget '" +
22474
- req.widgetId +
22685
+ verifiedWidgetId +
22475
22686
  "' calling '" +
22476
22687
  req.toolName +
22477
22688
  "' on '" +
@@ -22501,9 +22712,9 @@ async function gateToolCallWithJit$1(req, opts = {}) {
22501
22712
  addition.grantOrigin = "live";
22502
22713
 
22503
22714
  try {
22504
- const current = getGrant$1(req.widgetId);
22715
+ const current = getGrant$1(verifiedWidgetId);
22505
22716
  const merged = _mergeGrant(current, addition);
22506
- setGrant$1(req.widgetId, merged);
22717
+ setGrant$1(verifiedWidgetId, merged);
22507
22718
  } catch (e) {
22508
22719
  return {
22509
22720
  allow: false,
@@ -22716,7 +22927,7 @@ var mcpScopeResolver = {
22716
22927
  * Uses @modelcontextprotocol/sdk for protocol handling.
22717
22928
  */
22718
22929
 
22719
- const { Client } = require$$0$5;
22930
+ const { Client } = require$$0$6;
22720
22931
  const {
22721
22932
  StdioClientTransport,
22722
22933
  } = require$$1$3;
@@ -23521,6 +23732,7 @@ const mcpController$3 = {
23521
23732
  allowedTools = null,
23522
23733
  widgetId = null,
23523
23734
  workspaceId = null,
23735
+ token = null,
23524
23736
  ) => {
23525
23737
  const key = serverKey(workspaceId, serverName);
23526
23738
  try {
@@ -23544,8 +23756,12 @@ const mcpController$3 = {
23544
23756
  // startServer happens after the gate decides — and (b) avoids
23545
23757
  // leaking server-running state through error timing to a
23546
23758
  // probing widget that doesn't have permission anyway.
23547
- if (isWidgetPermissionEnforcementEnabled() && widgetId) {
23548
- const gateReq = { widgetId, serverName, toolName, args };
23759
+ // Slice 1 (additive): if a mount token is supplied it's the
23760
+ // trusted identity source; widgetId is verified-or-overwritten
23761
+ // inside the gate via mountTokenRegistry. Gate gets to run
23762
+ // whenever EITHER token or widgetId is present.
23763
+ if (isWidgetPermissionEnforcementEnabled() && (widgetId || token)) {
23764
+ const gateReq = { widgetId, token, serverName, toolName, args };
23549
23765
  const gate = isJitConsentEnabled()
23550
23766
  ? await gateToolCallWithJit(gateReq, { enableJit: true })
23551
23767
  : gateToolCall(gateReq);
@@ -24099,7 +24315,7 @@ const REGISTRY_BASE_URL$1 =
24099
24315
  let store$3 = null;
24100
24316
  function getStore$1() {
24101
24317
  if (!store$3) {
24102
- const Store = require$$0$6;
24318
+ const Store = require$$0$7;
24103
24319
  store$3 = new Store({
24104
24320
  name: "dash-registry-auth",
24105
24321
  encryptionKey: "dash-registry-v1",
@@ -25384,7 +25600,7 @@ var manifestScanner = {
25384
25600
  * and dispatching task-fired events to renderer windows.
25385
25601
  */
25386
25602
 
25387
- const Store$1 = require$$0$6;
25603
+ const Store$1 = require$$0$7;
25388
25604
  const { Cron } = require$$1$4;
25389
25605
 
25390
25606
  const store$2 = new Store$1({ name: "dash-scheduler" });
@@ -28600,7 +28816,7 @@ const algoliaController$1 = {
28600
28816
 
28601
28817
  var algoliaController_1 = algoliaController$1;
28602
28818
 
28603
- const OpenAI = require$$0$7;
28819
+ const OpenAI = require$$0$8;
28604
28820
  const events$2 = events$8;
28605
28821
 
28606
28822
  const openaiController$1 = {
@@ -34803,13 +35019,20 @@ boolean.parseBooleanDef = parseBooleanDef;
34803
35019
 
34804
35020
  var branded = {};
34805
35021
 
34806
- Object.defineProperty(branded, "__esModule", { value: true });
34807
- branded.parseBrandedDef = void 0;
34808
- const parseDef_js_1$1 = requireParseDef();
34809
- function parseBrandedDef(_def, refs) {
34810
- return (0, parseDef_js_1$1.parseDef)(_def.type._def, refs);
35022
+ var hasRequiredBranded;
35023
+
35024
+ function requireBranded () {
35025
+ if (hasRequiredBranded) return branded;
35026
+ hasRequiredBranded = 1;
35027
+ Object.defineProperty(branded, "__esModule", { value: true });
35028
+ branded.parseBrandedDef = void 0;
35029
+ const parseDef_js_1 = requireParseDef();
35030
+ function parseBrandedDef(_def, refs) {
35031
+ return (0, parseDef_js_1.parseDef)(_def.type._def, refs);
35032
+ }
35033
+ branded.parseBrandedDef = parseBrandedDef;
35034
+ return branded;
34811
35035
  }
34812
- branded.parseBrandedDef = parseBrandedDef;
34813
35036
 
34814
35037
  var _catch = {};
34815
35038
 
@@ -35392,7 +35615,7 @@ function requireRecord () {
35392
35615
  const v3_1 = v3;
35393
35616
  const parseDef_js_1 = requireParseDef();
35394
35617
  const string_js_1 = string;
35395
- const branded_js_1 = branded;
35618
+ const branded_js_1 = requireBranded();
35396
35619
  const any_js_1 = any;
35397
35620
  function parseRecordDef(def, refs) {
35398
35621
  if (refs.target === "openAi") {
@@ -36043,7 +36266,7 @@ function requireSelectParser () {
36043
36266
  const array_js_1 = requireArray();
36044
36267
  const bigint_js_1 = bigint;
36045
36268
  const boolean_js_1 = boolean;
36046
- const branded_js_1 = branded;
36269
+ const branded_js_1 = requireBranded();
36047
36270
  const catch_js_1 = require_catch();
36048
36271
  const date_js_1 = date;
36049
36272
  const default_js_1 = require_default();
@@ -36342,7 +36565,7 @@ zodToJsonSchema$1.zodToJsonSchema = zodToJsonSchema;
36342
36565
  __exportStar(requireArray(), exports);
36343
36566
  __exportStar(bigint, exports);
36344
36567
  __exportStar(boolean, exports);
36345
- __exportStar(branded, exports);
36568
+ __exportStar(requireBranded(), exports);
36346
36569
  __exportStar(require_catch(), exports);
36347
36570
  __exportStar(date, exports);
36348
36571
  __exportStar(require_default(), exports);
@@ -46953,7 +47176,7 @@ __export(src_exports, {
46953
47176
  var dist = __toCommonJS(src_exports);
46954
47177
 
46955
47178
  // src/server.ts
46956
- var import_node_http = require$$0$8;
47179
+ var import_node_http = require$$0$9;
46957
47180
 
46958
47181
  // src/listener.ts
46959
47182
  var import_node_http22 = require$$1$5;
@@ -47304,7 +47527,7 @@ var buildOutgoingHttpHeaders = (headers) => {
47304
47527
  var X_ALREADY_SENT = "x-hono-already-sent";
47305
47528
 
47306
47529
  // src/globals.ts
47307
- var import_node_crypto = __toESM(require$$3$4);
47530
+ var import_node_crypto = __toESM(require$$0$3);
47308
47531
  if (typeof commonjsGlobal.crypto === "undefined") {
47309
47532
  commonjsGlobal.crypto = import_node_crypto.default;
47310
47533
  }
@@ -48732,8 +48955,8 @@ var jsonSchemaToZod_1 = { jsonSchemaToZod: jsonSchemaToZod$1, jsonSchemaProperty
48732
48955
  */
48733
48956
 
48734
48957
  const https$1 = require$$11;
48735
- const { randomUUID } = require$$3$4;
48736
- const { BrowserWindow: BrowserWindow$1 } = require$$0$1;
48958
+ const { randomUUID } = require$$0$3;
48959
+ const { BrowserWindow: BrowserWindow$2 } = require$$0$1;
48737
48960
  const { McpServer } = mcp;
48738
48961
  const {
48739
48962
  StreamableHTTPServerTransport,
@@ -48777,7 +49000,7 @@ function broadcastStateChanged(toolName, result) {
48777
49000
  /* leave null */
48778
49001
  }
48779
49002
  const payload = { toolName, result: parsed };
48780
- for (const win of BrowserWindow$1.getAllWindows()) {
49003
+ for (const win of BrowserWindow$2.getAllWindows()) {
48781
49004
  if (!win.isDestroyed()) {
48782
49005
  try {
48783
49006
  win.webContents.send("dash-mcp:state-changed", payload);
@@ -50538,10 +50761,10 @@ var themeFromUrlErrors$1 = {
50538
50761
  * computed styles, and favicon/logo images (via node-vibrant).
50539
50762
  */
50540
50763
 
50541
- const css = require$$0$9;
50764
+ const css = require$$0$a;
50542
50765
  const { Vibrant } = require$$1$6;
50543
50766
  const https = require$$11;
50544
- const http = require$$0$8;
50767
+ const http = require$$0$9;
50545
50768
  const { URL: URL$1 } = require$$4$1;
50546
50769
  const {
50547
50770
  UrlUnreachableError,
@@ -55101,7 +55324,7 @@ var toolHandlers$1 = {
55101
55324
  * per-request, receiving the full messages array each time.
55102
55325
  */
55103
55326
 
55104
- const Anthropic = require$$0$a;
55327
+ const Anthropic = require$$0$b;
55105
55328
  const mcpController$2 = mcpControllerExports;
55106
55329
  const cliController$1 = cliController_1;
55107
55330
  const toolDefinitions = toolDefinitions$1;
@@ -57558,7 +57781,7 @@ function requireWidgetPublishManifest () {
57558
57781
  * and registry interaction.
57559
57782
  */
57560
57783
  const path$4 = require$$1$1;
57561
- const { app: app$4, dialog: dialog$1 } = require$$0$1;
57784
+ const { app: app$4, dialog: dialog$2 } = require$$0$1;
57562
57785
  const AdmZip$2 = require$$3$2;
57563
57786
 
57564
57787
  const themeController$3 = themeController_1;
@@ -57757,7 +57980,7 @@ async function prepareThemeForPublish$1(win, appId, themeKey, options = {}) {
57757
57980
  const sanitizedName = sanitizeName(themeKey);
57758
57981
  const defaultFilename = `theme-${sanitizedName}-v${manifest.version}.zip`;
57759
57982
 
57760
- const saveResult = await dialog$1.showSaveDialog(win, {
57983
+ const saveResult = await dialog$2.showSaveDialog(win, {
57761
57984
  title: "Save Theme Package",
57762
57985
  defaultPath: defaultFilename,
57763
57986
  filters: [{ name: "ZIP Files", extensions: ["zip"] }],
@@ -58296,7 +58519,7 @@ var themeRegistryController$1 = {
58296
58519
  * applies event wiring. (Import is implemented in DASH-13.)
58297
58520
  */
58298
58521
 
58299
- const { app: app$3, dialog } = require$$0$1;
58522
+ const { app: app$3, dialog: dialog$1 } = require$$0$1;
58300
58523
  const path$3 = require$$1$1;
58301
58524
  const AdmZip$1 = require$$3$2;
58302
58525
  const { getFileContents: getFileContents$1 } = file;
@@ -58458,7 +58681,7 @@ async function exportDashboardConfig$1(
58458
58681
  .replace(/\s+/g, "-")
58459
58682
  .toLowerCase();
58460
58683
 
58461
- const { canceled, filePath } = await dialog.showSaveDialog(win, {
58684
+ const { canceled, filePath } = await dialog$1.showSaveDialog(win, {
58462
58685
  title: "Export Dashboard as ZIP",
58463
58686
  defaultPath: path$3.join(
58464
58687
  app$3.getPath("desktop"),
@@ -58512,7 +58735,7 @@ async function exportDashboardConfig$1(
58512
58735
  */
58513
58736
  async function selectDashboardFile$1(win) {
58514
58737
  try {
58515
- const { canceled, filePaths } = await dialog.showOpenDialog(win, {
58738
+ const { canceled, filePaths } = await dialog$1.showOpenDialog(win, {
58516
58739
  title: "Import Dashboard Configuration",
58517
58740
  filters: [{ name: "ZIP Archive", extensions: ["zip"] }],
58518
58741
  properties: ["openFile"],
@@ -58619,7 +58842,7 @@ async function importDashboardConfig$1(
58619
58842
  zipPath = options.filePath;
58620
58843
  } else {
58621
58844
  // Show file picker
58622
- const { canceled, filePaths } = await dialog.showOpenDialog(win, {
58845
+ const { canceled, filePaths } = await dialog$1.showOpenDialog(win, {
58623
58846
  title: "Import Dashboard Configuration",
58624
58847
  filters: [{ name: "ZIP Archive", extensions: ["zip"] }],
58625
58848
  properties: ["openFile"],
@@ -59967,7 +60190,7 @@ async function prepareDashboardForPublish$1(
59967
60190
 
59968
60191
  // 9. Show save dialog for the publish package
59969
60192
  const sanitizedName = manifest.name;
59970
- const { canceled, filePath } = await dialog.showSaveDialog(win, {
60193
+ const { canceled, filePath } = await dialog$1.showSaveDialog(win, {
59971
60194
  title: "Save Dashboard Package for Registry",
59972
60195
  defaultPath: path$3.join(
59973
60196
  app$3.getPath("desktop"),
@@ -60266,7 +60489,7 @@ var dashboardConfigController$1 = {
60266
60489
  */
60267
60490
 
60268
60491
  const { Notification } = require$$0$1;
60269
- const Store = require$$0$6;
60492
+ const Store = require$$0$7;
60270
60493
 
60271
60494
  const store$1 = new Store({ name: "dash-notifications" });
60272
60495
 
@@ -60529,7 +60752,7 @@ var notificationController_1 = notificationController$2;
60529
60752
  const { app: app$2 } = require$$0$1;
60530
60753
  const fs$1 = require$$0$2;
60531
60754
  const path$2 = require$$1$1;
60532
- const WebSocket = require$$3$5;
60755
+ const WebSocket = require$$3$4;
60533
60756
  const {
60534
60757
  gateNetworkCall,
60535
60758
  gateNetworkCallWithJit,
@@ -60550,16 +60773,16 @@ function _loadFlags() {
60550
60773
  }
60551
60774
  }
60552
60775
 
60553
- async function _runNetworkGate(action, widgetId, args) {
60776
+ async function _runNetworkGate(action, widgetId, args, token) {
60554
60777
  const settings = _loadFlags();
60555
60778
  if (!readEnforceFlag(settings)) return { allow: true };
60556
- if (!widgetId) return { allow: true }; // legacy callers
60779
+ if (!widgetId && !token) return { allow: true }; // legacy callers
60557
60780
  return readJitFlag(settings)
60558
60781
  ? await gateNetworkCallWithJit(
60559
- { widgetId, action, args },
60782
+ { widgetId, token, action, args },
60560
60783
  { enableJit: true },
60561
60784
  )
60562
- : gateNetworkCall({ widgetId, action, args });
60785
+ : gateNetworkCall({ widgetId, token, action, args });
60563
60786
  }
60564
60787
 
60565
60788
  /**
@@ -60991,17 +61214,20 @@ const webSocketController$1 = {
60991
61214
  * @param {object} config - { url, headers, subprotocols, credentials }
60992
61215
  * @returns {{ success, providerName, status } | { error, message }}
60993
61216
  */
60994
- connect: async (win, providerName, config, widgetId = null) => {
60995
- // Phase 3 network gate — fires only when an explicit widgetId is
60996
- // supplied. The interpolated URL (with credentials substituted) is
60997
- // what we actually open the socket to, so the gate uses it for
61217
+ connect: async (win, providerName, config, widgetId = null, token = null) => {
61218
+ // Phase 3 network gate — fires when an explicit widgetId or token
61219
+ // is supplied. The interpolated URL (with credentials substituted)
61220
+ // is what we actually open the socket to, so the gate uses it for
60998
61221
  // hostname extraction.
60999
61222
  const interpolatedForGate = config?.credentials
61000
61223
  ? interpolate(config.url, config.credentials)
61001
61224
  : config?.url;
61002
- const gateResult = await _runNetworkGate("wsConnect", widgetId, {
61003
- url: interpolatedForGate,
61004
- });
61225
+ const gateResult = await _runNetworkGate(
61226
+ "wsConnect",
61227
+ widgetId,
61228
+ { url: interpolatedForGate },
61229
+ token,
61230
+ );
61005
61231
  if (!gateResult.allow) {
61006
61232
  return {
61007
61233
  error: true,
@@ -61465,6 +61691,133 @@ function buildGrantsListing$1(
61465
61691
 
61466
61692
  var widgetMcpGrantsListing = { buildGrantsListing: buildGrantsListing$1 };
61467
61693
 
61694
+ /**
61695
+ * grantDiff.js
61696
+ *
61697
+ * Pure-function diff between two grant blobs. Used by the
61698
+ * `widget-mcp:set-grant` IPC handler to decide whether the change
61699
+ * needs OS-native confirmation (broadening permissions) or can pass
61700
+ * through silently (revocations / equal / narrowing).
61701
+ *
61702
+ * Returns { broadening: boolean, summary: string[] }. `summary` is a
61703
+ * list of human-readable additions used to populate the native
61704
+ * confirm dialog.
61705
+ *
61706
+ * Broadening dimensions checked:
61707
+ * - servers: new server name present in newGrant.servers but not in
61708
+ * currentGrant.servers
61709
+ * - server tools: new tool name in an existing server's `tools[]`
61710
+ * - server paths: new entry in `readPaths[]` or `writePaths[]`
61711
+ * (including `*` wildcard added)
61712
+ * - domains.fs: new block, or new `readPaths[]` / `writePaths[]`
61713
+ * entry within an existing block (including `*`)
61714
+ * - domains.network: new block, or new `hosts[]` entry (including
61715
+ * `*` and `*.<base>` wildcards)
61716
+ *
61717
+ * Reductions, equality, and "no permission" → "no permission"
61718
+ * transitions are NOT broadening.
61719
+ */
61720
+
61721
+ function _arr(x) {
61722
+ return Array.isArray(x) ? x : [];
61723
+ }
61724
+
61725
+ function _added(currentList, newList) {
61726
+ const cur = new Set(_arr(currentList));
61727
+ const out = [];
61728
+ for (const item of _arr(newList)) {
61729
+ if (!cur.has(item)) out.push(item);
61730
+ }
61731
+ return out;
61732
+ }
61733
+
61734
+ function _diffServer(serverName, currentSrv, newSrv) {
61735
+ const summary = [];
61736
+ const cur = currentSrv || {};
61737
+ const nxt = newSrv || {};
61738
+
61739
+ for (const tool of _added(cur.tools, nxt.tools)) {
61740
+ summary.push(`server "${serverName}" tool "${tool}"`);
61741
+ }
61742
+ for (const p of _added(cur.readPaths, nxt.readPaths)) {
61743
+ summary.push(`server "${serverName}" readPath "${p}"`);
61744
+ }
61745
+ for (const p of _added(cur.writePaths, nxt.writePaths)) {
61746
+ summary.push(`server "${serverName}" writePath "${p}"`);
61747
+ }
61748
+ return summary;
61749
+ }
61750
+
61751
+ function _diffServers(curServers, nxtServers) {
61752
+ const summary = [];
61753
+ const cur = curServers || {};
61754
+ const nxt = nxtServers || {};
61755
+ for (const name of Object.keys(nxt)) {
61756
+ if (!cur[name]) {
61757
+ // Whole new server entry → list each component as broadening.
61758
+ const srv = nxt[name];
61759
+ const tools = _arr(srv?.tools);
61760
+ const reads = _arr(srv?.readPaths);
61761
+ const writes = _arr(srv?.writePaths);
61762
+ if (tools.length === 0 && reads.length === 0 && writes.length === 0) {
61763
+ // Empty-shell server entry — no actual permissions added.
61764
+ // Skip; not a meaningful broadening.
61765
+ continue;
61766
+ }
61767
+ summary.push(`new server "${name}"`);
61768
+ for (const t of tools) summary.push(` tool "${t}"`);
61769
+ for (const p of reads) summary.push(` readPath "${p}"`);
61770
+ for (const p of writes) summary.push(` writePath "${p}"`);
61771
+ } else {
61772
+ summary.push(..._diffServer(name, cur[name], nxt[name]));
61773
+ }
61774
+ }
61775
+ return summary;
61776
+ }
61777
+
61778
+ function _diffDomainsFs(curFs, nxtFs) {
61779
+ const summary = [];
61780
+ const cur = curFs || {};
61781
+ const nxt = nxtFs || {};
61782
+ for (const p of _added(cur.readPaths, nxt.readPaths)) {
61783
+ summary.push(`fs readPath "${p}"`);
61784
+ }
61785
+ for (const p of _added(cur.writePaths, nxt.writePaths)) {
61786
+ summary.push(`fs writePath "${p}"`);
61787
+ }
61788
+ return summary;
61789
+ }
61790
+
61791
+ function _diffDomainsNetwork(curNet, nxtNet) {
61792
+ const summary = [];
61793
+ const cur = curNet || {};
61794
+ const nxt = nxtNet || {};
61795
+ for (const h of _added(cur.hosts, nxt.hosts)) {
61796
+ summary.push(`network host "${h}"`);
61797
+ }
61798
+ return summary;
61799
+ }
61800
+
61801
+ /**
61802
+ * @param {object|null|undefined} currentGrant
61803
+ * @param {object|null|undefined} newGrant
61804
+ * @returns {{ broadening: boolean, summary: string[] }}
61805
+ */
61806
+ function isBroadening$1(currentGrant, newGrant) {
61807
+ const cur = currentGrant || {};
61808
+ const nxt = newGrant || {};
61809
+
61810
+ const summary = [
61811
+ ..._diffServers(cur.servers, nxt.servers),
61812
+ ..._diffDomainsFs(cur?.domains?.fs, nxt?.domains?.fs),
61813
+ ..._diffDomainsNetwork(cur?.domains?.network, nxt?.domains?.network),
61814
+ ];
61815
+
61816
+ return { broadening: summary.length > 0, summary };
61817
+ }
61818
+
61819
+ var grantDiff = { isBroadening: isBroadening$1 };
61820
+
61468
61821
  /**
61469
61822
  * widgetMcpGrantsController.js
61470
61823
  *
@@ -61480,7 +61833,7 @@ var widgetMcpGrantsListing = { buildGrantsListing: buildGrantsListing$1 };
61480
61833
  * grant are also surfaced — those are the install-consent retroactive prompts.
61481
61834
  */
61482
61835
 
61483
- const { ipcMain: ipcMain$1 } = require$$0$1;
61836
+ const { ipcMain: ipcMain$2, dialog, BrowserWindow: BrowserWindow$1 } = require$$0$1;
61484
61837
  const {
61485
61838
  getGrant,
61486
61839
  setGrant,
@@ -61491,21 +61844,70 @@ const {
61491
61844
  const { getWidgetMcpPermissions } = widgetPermissions;
61492
61845
  const { getWidgetRegistry } = widgetRegistryExports;
61493
61846
  const { buildGrantsListing } = widgetMcpGrantsListing;
61847
+ const { isBroadening } = grantDiff;
61848
+
61849
+ // Native confirm dialog for any set-grant call that broadens the
61850
+ // widget's current permissions. The dialog runs at OS level — a
61851
+ // renderer (including a malicious widget) cannot dismiss it
61852
+ // programmatically. This is the defense-in-depth fix for the
61853
+ // `widget-mcp:set-grant` consent-bypass gap documented in the IPC
61854
+ // audit doc: a widget calling `mainApi.widgetMcp.setGrant("@self",
61855
+ // {wide-open perms})` now triggers a system-level prompt the user
61856
+ // must explicitly approve. Reductions / equality pass unprompted.
61857
+ async function _confirmBroadening(event, widgetId, summary) {
61858
+ const senderWindow =
61859
+ BrowserWindow$1.fromWebContents(event.sender) ||
61860
+ BrowserWindow$1.getFocusedWindow();
61861
+ // Cap the listed lines so the dialog body stays readable.
61862
+ const MAX_LINES = 20;
61863
+ const trimmed = summary.slice(0, MAX_LINES);
61864
+ const overflow =
61865
+ summary.length > MAX_LINES
61866
+ ? `\n …and ${summary.length - MAX_LINES} more`
61867
+ : "";
61868
+ const detail =
61869
+ "Widget '" +
61870
+ widgetId +
61871
+ "' will be granted the following NEW permissions:\n\n " +
61872
+ trimmed.join("\n ") +
61873
+ overflow +
61874
+ "\n\nIf you didn't initiate this from Settings → Privacy & Security, " +
61875
+ "click Cancel — a malicious widget may be trying to escalate its own " +
61876
+ "permissions.";
61877
+
61878
+ const result = await dialog.showMessageBox(senderWindow, {
61879
+ type: "warning",
61880
+ title: "Confirm permissions change",
61881
+ message: "Allow new permissions for " + widgetId + "?",
61882
+ detail,
61883
+ buttons: ["Cancel", "Allow"],
61884
+ defaultId: 0,
61885
+ cancelId: 0,
61886
+ noLink: true,
61887
+ });
61888
+ return result.response === 1;
61889
+ }
61494
61890
 
61495
61891
  function setupWidgetMcpGrantsHandlers() {
61496
- ipcMain$1.handle("widget-mcp:get-grant", (event, widgetId) => {
61892
+ ipcMain$2.handle("widget-mcp:get-grant", (event, widgetId) => {
61497
61893
  return getGrant(widgetId);
61498
61894
  });
61499
61895
 
61500
- ipcMain$1.handle("widget-mcp:set-grant", (event, widgetId, perms) => {
61896
+ ipcMain$2.handle("widget-mcp:set-grant", async (event, widgetId, perms) => {
61897
+ const current = getGrant(widgetId);
61898
+ const diff = isBroadening(current, perms);
61899
+ if (diff.broadening) {
61900
+ const approved = await _confirmBroadening(event, widgetId, diff.summary);
61901
+ if (!approved) return false;
61902
+ }
61501
61903
  return setGrant(widgetId, perms);
61502
61904
  });
61503
61905
 
61504
- ipcMain$1.handle("widget-mcp:revoke", (event, widgetId) => {
61906
+ ipcMain$2.handle("widget-mcp:revoke", (event, widgetId) => {
61505
61907
  return revokeGrant(widgetId);
61506
61908
  });
61507
61909
 
61508
- ipcMain$1.handle("widget-mcp:revoke-server", (event, widgetId, serverName) => {
61910
+ ipcMain$2.handle("widget-mcp:revoke-server", (event, widgetId, serverName) => {
61509
61911
  return revokeServer(widgetId, serverName);
61510
61912
  });
61511
61913
 
@@ -61515,7 +61917,7 @@ function setupWidgetMcpGrantsHandlers() {
61515
61917
  // "Grant manually" for unmanifested widgets. Plus orphan-grant rows for
61516
61918
  // granted-but-uninstalled cases. Logic delegated to
61517
61919
  // widgetMcpGrantsListing.buildGrantsListing for unit-testability.
61518
- ipcMain$1.handle("widget-mcp:list-all", () => {
61920
+ ipcMain$2.handle("widget-mcp:list-all", () => {
61519
61921
  const grantsByWidget = new Map();
61520
61922
  for (const { widgetId, granted } of listAllGrants()) {
61521
61923
  grantsByWidget.set(widgetId, granted);
@@ -61546,6 +61948,46 @@ function setupWidgetMcpGrantsHandlers() {
61546
61948
 
61547
61949
  var widgetMcpGrantsController$1 = { setupWidgetMcpGrantsHandlers };
61548
61950
 
61951
+ /**
61952
+ * widgetMountTokenController.js
61953
+ *
61954
+ * IPC handlers for the widget mount-token registry. Called by
61955
+ * `WidgetFactory` at React mount/unmount time. Returns a fresh
61956
+ * server-generated token that the widget framework bakes into the
61957
+ * widget's bound API; the renderer never picks the token itself.
61958
+ *
61959
+ * See `electron/security/mountTokenRegistry.js` for the registry and
61960
+ * the longer threat-model note.
61961
+ *
61962
+ * Channels:
61963
+ * - "framework:register-widget-mount" (widgetId) → token string
61964
+ * - "framework:unregister-widget-mount" (token) → boolean (true = unregistered)
61965
+ */
61966
+
61967
+ const { ipcMain: ipcMain$1 } = require$$0$1;
61968
+ const { register, unregister } = mountTokenRegistry;
61969
+
61970
+ function setupWidgetMountTokenHandlers() {
61971
+ ipcMain$1.handle("framework:register-widget-mount", (_event, widgetId) => {
61972
+ if (typeof widgetId !== "string" || widgetId.length === 0) {
61973
+ return null;
61974
+ }
61975
+ try {
61976
+ return register(widgetId);
61977
+ } catch {
61978
+ return null;
61979
+ }
61980
+ });
61981
+
61982
+ ipcMain$1.handle("framework:unregister-widget-mount", (_event, token) => {
61983
+ if (typeof token !== "string" || token.length === 0) return false;
61984
+ unregister(token);
61985
+ return true;
61986
+ });
61987
+ }
61988
+
61989
+ var widgetMountTokenController$1 = { setupWidgetMountTokenHandlers };
61990
+
61549
61991
  /**
61550
61992
  * clientFactories.js
61551
61993
  *
@@ -61569,7 +62011,7 @@ clientCache$1.registerFactory("algolia", (credentials) => {
61569
62011
 
61570
62012
  // --- OpenAI ---
61571
62013
  clientCache$1.registerFactory("openai", (credentials) => {
61572
- const OpenAI = require$$0$7;
62014
+ const OpenAI = require$$0$8;
61573
62015
  return new OpenAI({ apiKey: credentials.apiKey });
61574
62016
  });
61575
62017
 
@@ -61588,7 +62030,7 @@ const MAX_RECENTS = 20;
61588
62030
  let store = null;
61589
62031
  function getStore() {
61590
62032
  if (!store) {
61591
- const Store = require$$0$6;
62033
+ const Store = require$$0$7;
61592
62034
  store = new Store({ name: "dash-session" });
61593
62035
  }
61594
62036
  return store;
@@ -63044,8 +63486,8 @@ const dataApi$2 = {
63044
63486
  ipcRenderer$l.invoke(READ_JSON, { filepath, objectCount });
63045
63487
  },
63046
63488
 
63047
- readDataFromURL: (url, toFilepath, widgetId = null) => {
63048
- ipcRenderer$l.invoke(READ_DATA_URL, { url, toFilepath, widgetId });
63489
+ readDataFromURL: (url, toFilepath, widgetId = null, token = null) => {
63490
+ ipcRenderer$l.invoke(READ_DATA_URL, { url, toFilepath, widgetId, token });
63049
63491
  },
63050
63492
 
63051
63493
  /*
@@ -63053,13 +63495,21 @@ const dataApi$2 = {
63053
63495
  * @param {object} options { filename, extension }
63054
63496
  * @param {object} returnEmpty the return empty object
63055
63497
  */
63056
- saveData: (data, filename, append, returnEmpty, widgetId = null) =>
63498
+ saveData: (
63499
+ data,
63500
+ filename,
63501
+ append,
63502
+ returnEmpty,
63503
+ widgetId = null,
63504
+ token = null,
63505
+ ) =>
63057
63506
  ipcRenderer$l.invoke(DATA_SAVE_TO_FILE, {
63058
63507
  data,
63059
63508
  filename,
63060
63509
  append,
63061
63510
  returnEmpty,
63062
63511
  widgetId,
63512
+ token,
63063
63513
  }),
63064
63514
 
63065
63515
  /*
@@ -63071,11 +63521,12 @@ const dataApi$2 = {
63071
63521
  * for legacy callers (`enforceWidgetMcpPermissions` flag still
63072
63522
  * gates the gate itself).
63073
63523
  */
63074
- readData: (filename, returnEmpty = [], widgetId = null) =>
63524
+ readData: (filename, returnEmpty = [], widgetId = null, token = null) =>
63075
63525
  ipcRenderer$l.invoke(DATA_READ_FROM_FILE, {
63076
63526
  filename,
63077
63527
  returnEmpty,
63078
63528
  widgetId,
63529
+ token,
63079
63530
  }),
63080
63531
 
63081
63532
  /**
@@ -63848,6 +64299,7 @@ const mcpApi$2 = {
63848
64299
  allowedTools = null,
63849
64300
  widgetId = null,
63850
64301
  workspaceId = null,
64302
+ token = null,
63851
64303
  ) =>
63852
64304
  ipcRenderer$g.invoke(MCP_CALL_TOOL, {
63853
64305
  serverName,
@@ -63856,6 +64308,7 @@ const mcpApi$2 = {
63856
64308
  allowedTools,
63857
64309
  widgetId,
63858
64310
  workspaceId,
64311
+ token,
63859
64312
  }),
63860
64313
 
63861
64314
  /**
@@ -65031,8 +65484,8 @@ const webSocketApi$2 = {
65031
65484
  * @param {object} config { url, headers, subprotocols, credentials }
65032
65485
  * @returns {Promise<{ success, providerName, status } | { error, message }>}
65033
65486
  */
65034
- connect: (providerName, config, widgetId = null) =>
65035
- ipcRenderer$4.invoke(WS_CONNECT, { providerName, config, widgetId }),
65487
+ connect: (providerName, config, widgetId = null, token = null) =>
65488
+ ipcRenderer$4.invoke(WS_CONNECT, { providerName, config, widgetId, token }),
65036
65489
 
65037
65490
  /**
65038
65491
  * disconnect
@@ -66552,6 +67005,7 @@ const webSocketController = webSocketController_1;
66552
67005
  const extractionCacheController = extractionCacheController_1;
66553
67006
  const mcpDashServerController = mcpDashServerController_1;
66554
67007
  const widgetMcpGrantsController = widgetMcpGrantsController$1;
67008
+ const widgetMountTokenController = widgetMountTokenController$1;
66555
67009
  const jitConsent = jitConsent$1;
66556
67010
 
66557
67011
  // --- Errors ---
@@ -66655,6 +67109,7 @@ var electron = {
66655
67109
  extractionCacheController,
66656
67110
  mcpDashServerController,
66657
67111
  widgetMcpGrantsController,
67112
+ widgetMountTokenController,
66658
67113
  jitConsent,
66659
67114
 
66660
67115
  // Controller functions (flat) — spread for convenient destructuring