@trops/dash-core 0.1.508 → 0.1.509

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.
@@ -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$2 } = 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
  }
@@ -34803,13 +34803,20 @@ boolean.parseBooleanDef = parseBooleanDef;
34803
34803
 
34804
34804
  var branded = {};
34805
34805
 
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);
34806
+ var hasRequiredBranded;
34807
+
34808
+ function requireBranded () {
34809
+ if (hasRequiredBranded) return branded;
34810
+ hasRequiredBranded = 1;
34811
+ Object.defineProperty(branded, "__esModule", { value: true });
34812
+ branded.parseBrandedDef = void 0;
34813
+ const parseDef_js_1 = requireParseDef();
34814
+ function parseBrandedDef(_def, refs) {
34815
+ return (0, parseDef_js_1.parseDef)(_def.type._def, refs);
34816
+ }
34817
+ branded.parseBrandedDef = parseBrandedDef;
34818
+ return branded;
34811
34819
  }
34812
- branded.parseBrandedDef = parseBrandedDef;
34813
34820
 
34814
34821
  var _catch = {};
34815
34822
 
@@ -35392,7 +35399,7 @@ function requireRecord () {
35392
35399
  const v3_1 = v3;
35393
35400
  const parseDef_js_1 = requireParseDef();
35394
35401
  const string_js_1 = string;
35395
- const branded_js_1 = branded;
35402
+ const branded_js_1 = requireBranded();
35396
35403
  const any_js_1 = any;
35397
35404
  function parseRecordDef(def, refs) {
35398
35405
  if (refs.target === "openAi") {
@@ -36043,7 +36050,7 @@ function requireSelectParser () {
36043
36050
  const array_js_1 = requireArray();
36044
36051
  const bigint_js_1 = bigint;
36045
36052
  const boolean_js_1 = boolean;
36046
- const branded_js_1 = branded;
36053
+ const branded_js_1 = requireBranded();
36047
36054
  const catch_js_1 = require_catch();
36048
36055
  const date_js_1 = date;
36049
36056
  const default_js_1 = require_default();
@@ -36342,7 +36349,7 @@ zodToJsonSchema$1.zodToJsonSchema = zodToJsonSchema;
36342
36349
  __exportStar(requireArray(), exports);
36343
36350
  __exportStar(bigint, exports);
36344
36351
  __exportStar(boolean, exports);
36345
- __exportStar(branded, exports);
36352
+ __exportStar(requireBranded(), exports);
36346
36353
  __exportStar(require_catch(), exports);
36347
36354
  __exportStar(date, exports);
36348
36355
  __exportStar(require_default(), exports);
@@ -48733,7 +48740,7 @@ var jsonSchemaToZod_1 = { jsonSchemaToZod: jsonSchemaToZod$1, jsonSchemaProperty
48733
48740
 
48734
48741
  const https$1 = require$$11;
48735
48742
  const { randomUUID } = require$$3$4;
48736
- const { BrowserWindow: BrowserWindow$1 } = require$$0$1;
48743
+ const { BrowserWindow: BrowserWindow$2 } = require$$0$1;
48737
48744
  const { McpServer } = mcp;
48738
48745
  const {
48739
48746
  StreamableHTTPServerTransport,
@@ -48777,7 +48784,7 @@ function broadcastStateChanged(toolName, result) {
48777
48784
  /* leave null */
48778
48785
  }
48779
48786
  const payload = { toolName, result: parsed };
48780
- for (const win of BrowserWindow$1.getAllWindows()) {
48787
+ for (const win of BrowserWindow$2.getAllWindows()) {
48781
48788
  if (!win.isDestroyed()) {
48782
48789
  try {
48783
48790
  win.webContents.send("dash-mcp:state-changed", payload);
@@ -57558,7 +57565,7 @@ function requireWidgetPublishManifest () {
57558
57565
  * and registry interaction.
57559
57566
  */
57560
57567
  const path$4 = require$$1$1;
57561
- const { app: app$4, dialog: dialog$1 } = require$$0$1;
57568
+ const { app: app$4, dialog: dialog$2 } = require$$0$1;
57562
57569
  const AdmZip$2 = require$$3$2;
57563
57570
 
57564
57571
  const themeController$3 = themeController_1;
@@ -57757,7 +57764,7 @@ async function prepareThemeForPublish$1(win, appId, themeKey, options = {}) {
57757
57764
  const sanitizedName = sanitizeName(themeKey);
57758
57765
  const defaultFilename = `theme-${sanitizedName}-v${manifest.version}.zip`;
57759
57766
 
57760
- const saveResult = await dialog$1.showSaveDialog(win, {
57767
+ const saveResult = await dialog$2.showSaveDialog(win, {
57761
57768
  title: "Save Theme Package",
57762
57769
  defaultPath: defaultFilename,
57763
57770
  filters: [{ name: "ZIP Files", extensions: ["zip"] }],
@@ -58296,7 +58303,7 @@ var themeRegistryController$1 = {
58296
58303
  * applies event wiring. (Import is implemented in DASH-13.)
58297
58304
  */
58298
58305
 
58299
- const { app: app$3, dialog } = require$$0$1;
58306
+ const { app: app$3, dialog: dialog$1 } = require$$0$1;
58300
58307
  const path$3 = require$$1$1;
58301
58308
  const AdmZip$1 = require$$3$2;
58302
58309
  const { getFileContents: getFileContents$1 } = file;
@@ -58458,7 +58465,7 @@ async function exportDashboardConfig$1(
58458
58465
  .replace(/\s+/g, "-")
58459
58466
  .toLowerCase();
58460
58467
 
58461
- const { canceled, filePath } = await dialog.showSaveDialog(win, {
58468
+ const { canceled, filePath } = await dialog$1.showSaveDialog(win, {
58462
58469
  title: "Export Dashboard as ZIP",
58463
58470
  defaultPath: path$3.join(
58464
58471
  app$3.getPath("desktop"),
@@ -58512,7 +58519,7 @@ async function exportDashboardConfig$1(
58512
58519
  */
58513
58520
  async function selectDashboardFile$1(win) {
58514
58521
  try {
58515
- const { canceled, filePaths } = await dialog.showOpenDialog(win, {
58522
+ const { canceled, filePaths } = await dialog$1.showOpenDialog(win, {
58516
58523
  title: "Import Dashboard Configuration",
58517
58524
  filters: [{ name: "ZIP Archive", extensions: ["zip"] }],
58518
58525
  properties: ["openFile"],
@@ -58619,7 +58626,7 @@ async function importDashboardConfig$1(
58619
58626
  zipPath = options.filePath;
58620
58627
  } else {
58621
58628
  // Show file picker
58622
- const { canceled, filePaths } = await dialog.showOpenDialog(win, {
58629
+ const { canceled, filePaths } = await dialog$1.showOpenDialog(win, {
58623
58630
  title: "Import Dashboard Configuration",
58624
58631
  filters: [{ name: "ZIP Archive", extensions: ["zip"] }],
58625
58632
  properties: ["openFile"],
@@ -59967,7 +59974,7 @@ async function prepareDashboardForPublish$1(
59967
59974
 
59968
59975
  // 9. Show save dialog for the publish package
59969
59976
  const sanitizedName = manifest.name;
59970
- const { canceled, filePath } = await dialog.showSaveDialog(win, {
59977
+ const { canceled, filePath } = await dialog$1.showSaveDialog(win, {
59971
59978
  title: "Save Dashboard Package for Registry",
59972
59979
  defaultPath: path$3.join(
59973
59980
  app$3.getPath("desktop"),
@@ -61465,6 +61472,133 @@ function buildGrantsListing$1(
61465
61472
 
61466
61473
  var widgetMcpGrantsListing = { buildGrantsListing: buildGrantsListing$1 };
61467
61474
 
61475
+ /**
61476
+ * grantDiff.js
61477
+ *
61478
+ * Pure-function diff between two grant blobs. Used by the
61479
+ * `widget-mcp:set-grant` IPC handler to decide whether the change
61480
+ * needs OS-native confirmation (broadening permissions) or can pass
61481
+ * through silently (revocations / equal / narrowing).
61482
+ *
61483
+ * Returns { broadening: boolean, summary: string[] }. `summary` is a
61484
+ * list of human-readable additions used to populate the native
61485
+ * confirm dialog.
61486
+ *
61487
+ * Broadening dimensions checked:
61488
+ * - servers: new server name present in newGrant.servers but not in
61489
+ * currentGrant.servers
61490
+ * - server tools: new tool name in an existing server's `tools[]`
61491
+ * - server paths: new entry in `readPaths[]` or `writePaths[]`
61492
+ * (including `*` wildcard added)
61493
+ * - domains.fs: new block, or new `readPaths[]` / `writePaths[]`
61494
+ * entry within an existing block (including `*`)
61495
+ * - domains.network: new block, or new `hosts[]` entry (including
61496
+ * `*` and `*.<base>` wildcards)
61497
+ *
61498
+ * Reductions, equality, and "no permission" → "no permission"
61499
+ * transitions are NOT broadening.
61500
+ */
61501
+
61502
+ function _arr(x) {
61503
+ return Array.isArray(x) ? x : [];
61504
+ }
61505
+
61506
+ function _added(currentList, newList) {
61507
+ const cur = new Set(_arr(currentList));
61508
+ const out = [];
61509
+ for (const item of _arr(newList)) {
61510
+ if (!cur.has(item)) out.push(item);
61511
+ }
61512
+ return out;
61513
+ }
61514
+
61515
+ function _diffServer(serverName, currentSrv, newSrv) {
61516
+ const summary = [];
61517
+ const cur = currentSrv || {};
61518
+ const nxt = newSrv || {};
61519
+
61520
+ for (const tool of _added(cur.tools, nxt.tools)) {
61521
+ summary.push(`server "${serverName}" tool "${tool}"`);
61522
+ }
61523
+ for (const p of _added(cur.readPaths, nxt.readPaths)) {
61524
+ summary.push(`server "${serverName}" readPath "${p}"`);
61525
+ }
61526
+ for (const p of _added(cur.writePaths, nxt.writePaths)) {
61527
+ summary.push(`server "${serverName}" writePath "${p}"`);
61528
+ }
61529
+ return summary;
61530
+ }
61531
+
61532
+ function _diffServers(curServers, nxtServers) {
61533
+ const summary = [];
61534
+ const cur = curServers || {};
61535
+ const nxt = nxtServers || {};
61536
+ for (const name of Object.keys(nxt)) {
61537
+ if (!cur[name]) {
61538
+ // Whole new server entry → list each component as broadening.
61539
+ const srv = nxt[name];
61540
+ const tools = _arr(srv?.tools);
61541
+ const reads = _arr(srv?.readPaths);
61542
+ const writes = _arr(srv?.writePaths);
61543
+ if (tools.length === 0 && reads.length === 0 && writes.length === 0) {
61544
+ // Empty-shell server entry — no actual permissions added.
61545
+ // Skip; not a meaningful broadening.
61546
+ continue;
61547
+ }
61548
+ summary.push(`new server "${name}"`);
61549
+ for (const t of tools) summary.push(` tool "${t}"`);
61550
+ for (const p of reads) summary.push(` readPath "${p}"`);
61551
+ for (const p of writes) summary.push(` writePath "${p}"`);
61552
+ } else {
61553
+ summary.push(..._diffServer(name, cur[name], nxt[name]));
61554
+ }
61555
+ }
61556
+ return summary;
61557
+ }
61558
+
61559
+ function _diffDomainsFs(curFs, nxtFs) {
61560
+ const summary = [];
61561
+ const cur = curFs || {};
61562
+ const nxt = nxtFs || {};
61563
+ for (const p of _added(cur.readPaths, nxt.readPaths)) {
61564
+ summary.push(`fs readPath "${p}"`);
61565
+ }
61566
+ for (const p of _added(cur.writePaths, nxt.writePaths)) {
61567
+ summary.push(`fs writePath "${p}"`);
61568
+ }
61569
+ return summary;
61570
+ }
61571
+
61572
+ function _diffDomainsNetwork(curNet, nxtNet) {
61573
+ const summary = [];
61574
+ const cur = curNet || {};
61575
+ const nxt = nxtNet || {};
61576
+ for (const h of _added(cur.hosts, nxt.hosts)) {
61577
+ summary.push(`network host "${h}"`);
61578
+ }
61579
+ return summary;
61580
+ }
61581
+
61582
+ /**
61583
+ * @param {object|null|undefined} currentGrant
61584
+ * @param {object|null|undefined} newGrant
61585
+ * @returns {{ broadening: boolean, summary: string[] }}
61586
+ */
61587
+ function isBroadening$1(currentGrant, newGrant) {
61588
+ const cur = currentGrant || {};
61589
+ const nxt = newGrant || {};
61590
+
61591
+ const summary = [
61592
+ ..._diffServers(cur.servers, nxt.servers),
61593
+ ..._diffDomainsFs(cur?.domains?.fs, nxt?.domains?.fs),
61594
+ ..._diffDomainsNetwork(cur?.domains?.network, nxt?.domains?.network),
61595
+ ];
61596
+
61597
+ return { broadening: summary.length > 0, summary };
61598
+ }
61599
+
61600
+ var grantDiff = { isBroadening: isBroadening$1 };
61601
+
61468
61602
  /**
61469
61603
  * widgetMcpGrantsController.js
61470
61604
  *
@@ -61480,7 +61614,7 @@ var widgetMcpGrantsListing = { buildGrantsListing: buildGrantsListing$1 };
61480
61614
  * grant are also surfaced — those are the install-consent retroactive prompts.
61481
61615
  */
61482
61616
 
61483
- const { ipcMain: ipcMain$1 } = require$$0$1;
61617
+ const { ipcMain: ipcMain$1, dialog, BrowserWindow: BrowserWindow$1 } = require$$0$1;
61484
61618
  const {
61485
61619
  getGrant,
61486
61620
  setGrant,
@@ -61491,13 +61625,62 @@ const {
61491
61625
  const { getWidgetMcpPermissions } = widgetPermissions;
61492
61626
  const { getWidgetRegistry } = widgetRegistryExports;
61493
61627
  const { buildGrantsListing } = widgetMcpGrantsListing;
61628
+ const { isBroadening } = grantDiff;
61629
+
61630
+ // Native confirm dialog for any set-grant call that broadens the
61631
+ // widget's current permissions. The dialog runs at OS level — a
61632
+ // renderer (including a malicious widget) cannot dismiss it
61633
+ // programmatically. This is the defense-in-depth fix for the
61634
+ // `widget-mcp:set-grant` consent-bypass gap documented in the IPC
61635
+ // audit doc: a widget calling `mainApi.widgetMcp.setGrant("@self",
61636
+ // {wide-open perms})` now triggers a system-level prompt the user
61637
+ // must explicitly approve. Reductions / equality pass unprompted.
61638
+ async function _confirmBroadening(event, widgetId, summary) {
61639
+ const senderWindow =
61640
+ BrowserWindow$1.fromWebContents(event.sender) ||
61641
+ BrowserWindow$1.getFocusedWindow();
61642
+ // Cap the listed lines so the dialog body stays readable.
61643
+ const MAX_LINES = 20;
61644
+ const trimmed = summary.slice(0, MAX_LINES);
61645
+ const overflow =
61646
+ summary.length > MAX_LINES
61647
+ ? `\n …and ${summary.length - MAX_LINES} more`
61648
+ : "";
61649
+ const detail =
61650
+ "Widget '" +
61651
+ widgetId +
61652
+ "' will be granted the following NEW permissions:\n\n " +
61653
+ trimmed.join("\n ") +
61654
+ overflow +
61655
+ "\n\nIf you didn't initiate this from Settings → Privacy & Security, " +
61656
+ "click Cancel — a malicious widget may be trying to escalate its own " +
61657
+ "permissions.";
61658
+
61659
+ const result = await dialog.showMessageBox(senderWindow, {
61660
+ type: "warning",
61661
+ title: "Confirm permissions change",
61662
+ message: "Allow new permissions for " + widgetId + "?",
61663
+ detail,
61664
+ buttons: ["Cancel", "Allow"],
61665
+ defaultId: 0,
61666
+ cancelId: 0,
61667
+ noLink: true,
61668
+ });
61669
+ return result.response === 1;
61670
+ }
61494
61671
 
61495
61672
  function setupWidgetMcpGrantsHandlers() {
61496
61673
  ipcMain$1.handle("widget-mcp:get-grant", (event, widgetId) => {
61497
61674
  return getGrant(widgetId);
61498
61675
  });
61499
61676
 
61500
- ipcMain$1.handle("widget-mcp:set-grant", (event, widgetId, perms) => {
61677
+ ipcMain$1.handle("widget-mcp:set-grant", async (event, widgetId, perms) => {
61678
+ const current = getGrant(widgetId);
61679
+ const diff = isBroadening(current, perms);
61680
+ if (diff.broadening) {
61681
+ const approved = await _confirmBroadening(event, widgetId, diff.summary);
61682
+ if (!approved) return false;
61683
+ }
61501
61684
  return setGrant(widgetId, perms);
61502
61685
  });
61503
61686