@trops/dash-core 0.1.496 → 0.1.497
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/electron/index.js +411 -23
- package/dist/electron/index.js.map +1 -1
- package/dist/index.esm.js +20 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/electron/index.js
CHANGED
|
@@ -1873,7 +1873,7 @@ var safePath_1 = {
|
|
|
1873
1873
|
// it can mark the relative import as transitive-external and then fail
|
|
1874
1874
|
// to resolve back. Deferring the require breaks the static-analysis
|
|
1875
1875
|
// chain so the rollup build resolves cleanly.
|
|
1876
|
-
const DEFAULT_TIMEOUT_MS = 1000;
|
|
1876
|
+
const DEFAULT_TIMEOUT_MS$1 = 1000;
|
|
1877
1877
|
const DEFAULT_MEMORY_BYTES = 32 * 1024 * 1024; // 32 MB
|
|
1878
1878
|
|
|
1879
1879
|
let _modulePromise = null;
|
|
@@ -1936,7 +1936,7 @@ async function runOnce({
|
|
|
1936
1936
|
body,
|
|
1937
1937
|
args = [],
|
|
1938
1938
|
inputs = [],
|
|
1939
|
-
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
1939
|
+
timeoutMs = DEFAULT_TIMEOUT_MS$1,
|
|
1940
1940
|
memoryBytes = DEFAULT_MEMORY_BYTES,
|
|
1941
1941
|
}) {
|
|
1942
1942
|
if (typeof body !== "string" || !body.trim()) {
|
|
@@ -2007,7 +2007,7 @@ async function createCompiled({
|
|
|
2007
2007
|
let disposed = false;
|
|
2008
2008
|
|
|
2009
2009
|
return {
|
|
2010
|
-
run(inputs, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
2010
|
+
run(inputs, timeoutMs = DEFAULT_TIMEOUT_MS$1) {
|
|
2011
2011
|
if (disposed) {
|
|
2012
2012
|
return { error: "executor already disposed" };
|
|
2013
2013
|
}
|
|
@@ -2048,7 +2048,7 @@ async function createCompiled({
|
|
|
2048
2048
|
var safeJsExecutor$1 = {
|
|
2049
2049
|
runOnce,
|
|
2050
2050
|
createCompiled,
|
|
2051
|
-
DEFAULT_TIMEOUT_MS,
|
|
2051
|
+
DEFAULT_TIMEOUT_MS: DEFAULT_TIMEOUT_MS$1,
|
|
2052
2052
|
DEFAULT_MEMORY_BYTES,
|
|
2053
2053
|
};
|
|
2054
2054
|
|
|
@@ -21235,13 +21235,23 @@ function writeToDisk(data) {
|
|
|
21235
21235
|
fs$b.renameSync(tmp, p);
|
|
21236
21236
|
}
|
|
21237
21237
|
|
|
21238
|
-
// Recognized origins for a persisted grant.
|
|
21239
|
-
// approved against the developer's declared
|
|
21240
|
-
//
|
|
21241
|
-
//
|
|
21242
|
-
//
|
|
21238
|
+
// Recognized origins for a persisted grant.
|
|
21239
|
+
// "declared" — user approved against the developer's declared
|
|
21240
|
+
// dash.permissions.mcp block at install time.
|
|
21241
|
+
// "discovered" — install-time scanner produced a synthetic manifest
|
|
21242
|
+
// the user approved.
|
|
21243
|
+
// "manual" — user typed entries themselves in
|
|
21244
|
+
// Settings → Privacy & Security with no manifest backing.
|
|
21245
|
+
// "live" — user approved a just-in-time consent prompt at
|
|
21246
|
+
// runtime when a tool call hit the gate without a
|
|
21247
|
+
// matching grant.
|
|
21243
21248
|
// Other values are dropped on persist (legacy grants stay null).
|
|
21244
|
-
const ALLOWED_GRANT_ORIGINS = new Set([
|
|
21249
|
+
const ALLOWED_GRANT_ORIGINS = new Set([
|
|
21250
|
+
"declared",
|
|
21251
|
+
"discovered",
|
|
21252
|
+
"manual",
|
|
21253
|
+
"live",
|
|
21254
|
+
]);
|
|
21245
21255
|
|
|
21246
21256
|
/**
|
|
21247
21257
|
* Sanitize a perms object before persisting. Drops unknown keys, coerces
|
|
@@ -21289,7 +21299,7 @@ function getGrant$2(widgetId) {
|
|
|
21289
21299
|
return all[widgetId] || null;
|
|
21290
21300
|
}
|
|
21291
21301
|
|
|
21292
|
-
function setGrant$
|
|
21302
|
+
function setGrant$2(widgetId, perms) {
|
|
21293
21303
|
if (typeof widgetId !== "string" || !widgetId) return false;
|
|
21294
21304
|
const sanitized = sanitizePerms(perms);
|
|
21295
21305
|
if (!sanitized) return false;
|
|
@@ -21371,7 +21381,7 @@ function clearCache$1() {
|
|
|
21371
21381
|
|
|
21372
21382
|
var grantedPermissions = {
|
|
21373
21383
|
getGrant: getGrant$2,
|
|
21374
|
-
setGrant: setGrant$
|
|
21384
|
+
setGrant: setGrant$2,
|
|
21375
21385
|
revokeGrant: revokeGrant$1,
|
|
21376
21386
|
revokeServer: revokeServer$1,
|
|
21377
21387
|
listAllGrants: listAllGrants$1,
|
|
@@ -21379,6 +21389,218 @@ var grantedPermissions = {
|
|
|
21379
21389
|
ALLOWED_GRANT_ORIGINS,
|
|
21380
21390
|
};
|
|
21381
21391
|
|
|
21392
|
+
/**
|
|
21393
|
+
* jitConsent.js
|
|
21394
|
+
*
|
|
21395
|
+
* Just-in-time permission consent for widget→backend calls.
|
|
21396
|
+
*
|
|
21397
|
+
* When a widget hits a gate without an existing grant for the requested
|
|
21398
|
+
* (domain, action, args), the gate calls `requestApproval` which:
|
|
21399
|
+
* 1. Synchronously emits `widget:permission-required` to all
|
|
21400
|
+
* BrowserWindows with a unique requestId.
|
|
21401
|
+
* 2. Returns a Promise that resolves on user response or rejects on
|
|
21402
|
+
* timeout.
|
|
21403
|
+
* 3. Coalesces requests with the same coalescing key so a widget
|
|
21404
|
+
* bursting identical calls produces one prompt, not many.
|
|
21405
|
+
*
|
|
21406
|
+
* The renderer's JitConsentModal subscribes to the event, presents the
|
|
21407
|
+
* user with granularity options (this once / this tool / this tool +
|
|
21408
|
+
* parent dir), and replies via `widget:permission-response` with
|
|
21409
|
+
* `{ requestId, decision }`. main.js wires the IPC handler back to
|
|
21410
|
+
* `_handleResponse`.
|
|
21411
|
+
*
|
|
21412
|
+
* The module is intentionally domain-agnostic in shape — the request
|
|
21413
|
+
* payload carries `domain` so future plug-ins (fs, algolia, llm) reuse
|
|
21414
|
+
* the same machinery. Phase 1 only emits with `domain: "mcp"`.
|
|
21415
|
+
*
|
|
21416
|
+
* Public surface:
|
|
21417
|
+
* requestApproval(req, opts) → Promise<{ approve, scope?, ... }>
|
|
21418
|
+
* _handleResponse({ requestId, decision }) → void (called from main.js IPC)
|
|
21419
|
+
* _resetForTest() → void (test-only)
|
|
21420
|
+
*/
|
|
21421
|
+
|
|
21422
|
+
const { BrowserWindow: BrowserWindow$2, ipcMain: ipcMain$2 } = require$$0$1;
|
|
21423
|
+
|
|
21424
|
+
const REQUEST_CHANNEL = "widget:permission-required";
|
|
21425
|
+
const RESPONSE_CHANNEL = "widget:permission-response";
|
|
21426
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
21427
|
+
|
|
21428
|
+
// requestId → { resolve, reject, timeout, coalesceKey, joinedResolvers }
|
|
21429
|
+
const _pending = new Map();
|
|
21430
|
+
// coalesceKey → requestId (so duplicate requests join the live one)
|
|
21431
|
+
const _coalesce = new Map();
|
|
21432
|
+
let _idCounter = 0;
|
|
21433
|
+
|
|
21434
|
+
function nextRequestId() {
|
|
21435
|
+
_idCounter += 1;
|
|
21436
|
+
return `jit-${Date.now()}-${_idCounter}`;
|
|
21437
|
+
}
|
|
21438
|
+
|
|
21439
|
+
/**
|
|
21440
|
+
* Build a coalescing key from the request. Two requests share the same
|
|
21441
|
+
* key iff they're "the same prompt" — same widget, same domain+action,
|
|
21442
|
+
* same target server/tool. Args beyond that (e.g. exact path) DON'T
|
|
21443
|
+
* differentiate; if the user is being asked about read_file already,
|
|
21444
|
+
* approving handles all current paths.
|
|
21445
|
+
*/
|
|
21446
|
+
function coalesceKeyOf(req) {
|
|
21447
|
+
if (req.domain === "mcp") {
|
|
21448
|
+
const innerArgs = req.args || {};
|
|
21449
|
+
return [
|
|
21450
|
+
req.widgetId,
|
|
21451
|
+
"mcp",
|
|
21452
|
+
innerArgs.serverName || "",
|
|
21453
|
+
innerArgs.toolName || "",
|
|
21454
|
+
].join("::");
|
|
21455
|
+
}
|
|
21456
|
+
// Default: domain + action + serialized top-level args
|
|
21457
|
+
return [
|
|
21458
|
+
req.widgetId,
|
|
21459
|
+
req.domain,
|
|
21460
|
+
req.action,
|
|
21461
|
+
JSON.stringify(req.args || {}),
|
|
21462
|
+
].join("::");
|
|
21463
|
+
}
|
|
21464
|
+
|
|
21465
|
+
function emitEvent(payload) {
|
|
21466
|
+
let wins = [];
|
|
21467
|
+
try {
|
|
21468
|
+
wins = BrowserWindow$2.getAllWindows() || [];
|
|
21469
|
+
} catch {
|
|
21470
|
+
wins = [];
|
|
21471
|
+
}
|
|
21472
|
+
for (const w of wins) {
|
|
21473
|
+
try {
|
|
21474
|
+
w?.webContents?.send?.(REQUEST_CHANNEL, payload);
|
|
21475
|
+
} catch {
|
|
21476
|
+
// best-effort broadcast
|
|
21477
|
+
}
|
|
21478
|
+
}
|
|
21479
|
+
}
|
|
21480
|
+
|
|
21481
|
+
function validateRequest(req) {
|
|
21482
|
+
if (!req || typeof req !== "object") return "invalid request: not an object";
|
|
21483
|
+
if (typeof req.widgetId !== "string" || !req.widgetId)
|
|
21484
|
+
return "invalid request: widgetId required";
|
|
21485
|
+
if (typeof req.domain !== "string" || !req.domain)
|
|
21486
|
+
return "invalid request: domain required";
|
|
21487
|
+
if (typeof req.action !== "string" || !req.action)
|
|
21488
|
+
return "invalid request: action required";
|
|
21489
|
+
return null;
|
|
21490
|
+
}
|
|
21491
|
+
|
|
21492
|
+
/**
|
|
21493
|
+
* Request user approval for an out-of-grant call. Returns a promise
|
|
21494
|
+
* that resolves with the user's decision or rejects on timeout / bad
|
|
21495
|
+
* input.
|
|
21496
|
+
*
|
|
21497
|
+
* decision shape (resolved value):
|
|
21498
|
+
* { approve: true, scope: "once" | "tool" | "parent" | "custom", ...extras }
|
|
21499
|
+
* { approve: false, reason?: string }
|
|
21500
|
+
*
|
|
21501
|
+
* `scope` informs the caller how to write the resulting grant.
|
|
21502
|
+
*/
|
|
21503
|
+
function requestApproval$1(req, opts = {}) {
|
|
21504
|
+
const validation = validateRequest(req);
|
|
21505
|
+
if (validation) {
|
|
21506
|
+
return Promise.reject(new Error(validation));
|
|
21507
|
+
}
|
|
21508
|
+
|
|
21509
|
+
const timeoutMs = Number.isFinite(opts.timeoutMs)
|
|
21510
|
+
? opts.timeoutMs
|
|
21511
|
+
: DEFAULT_TIMEOUT_MS;
|
|
21512
|
+
|
|
21513
|
+
// If a prompt for the same coalesce key is already pending, join it.
|
|
21514
|
+
const key = coalesceKeyOf(req);
|
|
21515
|
+
if (_coalesce.has(key)) {
|
|
21516
|
+
const existingId = _coalesce.get(key);
|
|
21517
|
+
const existing = _pending.get(existingId);
|
|
21518
|
+
if (existing) {
|
|
21519
|
+
return new Promise((resolve, reject) => {
|
|
21520
|
+
existing.joinedResolvers.push({ resolve, reject });
|
|
21521
|
+
});
|
|
21522
|
+
}
|
|
21523
|
+
// Stale coalesce entry; drop and fall through to a fresh request.
|
|
21524
|
+
_coalesce.delete(key);
|
|
21525
|
+
}
|
|
21526
|
+
|
|
21527
|
+
return new Promise((resolve, reject) => {
|
|
21528
|
+
const requestId = nextRequestId();
|
|
21529
|
+
const timeout = setTimeout(() => {
|
|
21530
|
+
const entry = _pending.get(requestId);
|
|
21531
|
+
if (!entry) return;
|
|
21532
|
+
_pending.delete(requestId);
|
|
21533
|
+
_coalesce.delete(entry.coalesceKey);
|
|
21534
|
+
const err = new Error(
|
|
21535
|
+
`JIT consent timed out for ${req.widgetId} (${req.domain}/${req.action}) after ${timeoutMs}ms`,
|
|
21536
|
+
);
|
|
21537
|
+
reject(err);
|
|
21538
|
+
for (const j of entry.joinedResolvers) j.reject(err);
|
|
21539
|
+
}, timeoutMs);
|
|
21540
|
+
|
|
21541
|
+
_pending.set(requestId, {
|
|
21542
|
+
resolve,
|
|
21543
|
+
reject,
|
|
21544
|
+
timeout,
|
|
21545
|
+
coalesceKey: key,
|
|
21546
|
+
joinedResolvers: [],
|
|
21547
|
+
});
|
|
21548
|
+
_coalesce.set(key, requestId);
|
|
21549
|
+
|
|
21550
|
+
emitEvent({
|
|
21551
|
+
requestId,
|
|
21552
|
+
widgetId: req.widgetId,
|
|
21553
|
+
domain: req.domain,
|
|
21554
|
+
action: req.action,
|
|
21555
|
+
args: req.args || {},
|
|
21556
|
+
});
|
|
21557
|
+
});
|
|
21558
|
+
}
|
|
21559
|
+
|
|
21560
|
+
function _handleResponse({ requestId, decision } = {}) {
|
|
21561
|
+
if (!requestId || typeof requestId !== "string") return;
|
|
21562
|
+
const entry = _pending.get(requestId);
|
|
21563
|
+
if (!entry) return; // unknown request — drop silently
|
|
21564
|
+
clearTimeout(entry.timeout);
|
|
21565
|
+
_pending.delete(requestId);
|
|
21566
|
+
_coalesce.delete(entry.coalesceKey);
|
|
21567
|
+
const safe =
|
|
21568
|
+
decision && typeof decision === "object" ? decision : { approve: false };
|
|
21569
|
+
entry.resolve(safe);
|
|
21570
|
+
for (const j of entry.joinedResolvers) j.resolve(safe);
|
|
21571
|
+
}
|
|
21572
|
+
|
|
21573
|
+
function _resetForTest() {
|
|
21574
|
+
for (const entry of _pending.values()) clearTimeout(entry.timeout);
|
|
21575
|
+
_pending.clear();
|
|
21576
|
+
_coalesce.clear();
|
|
21577
|
+
_idCounter = 0;
|
|
21578
|
+
}
|
|
21579
|
+
|
|
21580
|
+
let _handlersRegistered = false;
|
|
21581
|
+
/**
|
|
21582
|
+
* Wire the renderer→main response IPC. Idempotent.
|
|
21583
|
+
* Call once from main.js alongside other ipcMain setup.
|
|
21584
|
+
*/
|
|
21585
|
+
function setupJitConsentHandlers() {
|
|
21586
|
+
if (_handlersRegistered) return;
|
|
21587
|
+
if (!ipcMain$2 || typeof ipcMain$2.on !== "function") return;
|
|
21588
|
+
ipcMain$2.on(RESPONSE_CHANNEL, (_event, payload) => {
|
|
21589
|
+
_handleResponse(payload);
|
|
21590
|
+
});
|
|
21591
|
+
_handlersRegistered = true;
|
|
21592
|
+
}
|
|
21593
|
+
|
|
21594
|
+
var jitConsent$1 = {
|
|
21595
|
+
requestApproval: requestApproval$1,
|
|
21596
|
+
setupJitConsentHandlers,
|
|
21597
|
+
_handleResponse,
|
|
21598
|
+
_resetForTest,
|
|
21599
|
+
REQUEST_CHANNEL,
|
|
21600
|
+
RESPONSE_CHANNEL,
|
|
21601
|
+
DEFAULT_TIMEOUT_MS,
|
|
21602
|
+
};
|
|
21603
|
+
|
|
21382
21604
|
/**
|
|
21383
21605
|
* permissionGate.js
|
|
21384
21606
|
*
|
|
@@ -21418,8 +21640,9 @@ var grantedPermissions = {
|
|
|
21418
21640
|
* dispatch goes through this gate.
|
|
21419
21641
|
*/
|
|
21420
21642
|
|
|
21421
|
-
const { getGrant: getGrant$1 } = grantedPermissions;
|
|
21643
|
+
const { getGrant: getGrant$1, setGrant: setGrant$1 } = grantedPermissions;
|
|
21422
21644
|
const { safePath: safePath$1 } = safePath_1;
|
|
21645
|
+
const { requestApproval } = jitConsent$1;
|
|
21423
21646
|
|
|
21424
21647
|
// Argument keys that look like paths. Different MCP servers use
|
|
21425
21648
|
// different conventions; this list covers the common filesystem-style
|
|
@@ -21535,8 +21758,148 @@ function gateToolCall$1({ widgetId, serverName, toolName, args }) {
|
|
|
21535
21758
|
return { allow: true };
|
|
21536
21759
|
}
|
|
21537
21760
|
|
|
21761
|
+
/**
|
|
21762
|
+
* Heuristic — "no grant" is the only denial reason JIT can recover.
|
|
21763
|
+
* Other denials (missing widgetId, malformed args, server-not-declared
|
|
21764
|
+
* post-grant, path-traversal-rejected) are bugs or attempted abuse,
|
|
21765
|
+
* not consent gaps; the user shouldn't be prompted about those.
|
|
21766
|
+
*/
|
|
21767
|
+
function _isNoGrantDenial(reason) {
|
|
21768
|
+
return (
|
|
21769
|
+
typeof reason === "string" && /no MCP permissions granted/i.test(reason)
|
|
21770
|
+
);
|
|
21771
|
+
}
|
|
21772
|
+
|
|
21773
|
+
/**
|
|
21774
|
+
* Merge `addition` (a grant blob) into the widget's existing grant. Used
|
|
21775
|
+
* by the JIT path to extend an existing grant with a new tool/path
|
|
21776
|
+
* without clobbering grants for other servers.
|
|
21777
|
+
*/
|
|
21778
|
+
function _mergeGrant(current, addition) {
|
|
21779
|
+
const out = {
|
|
21780
|
+
grantOrigin: addition.grantOrigin || current?.grantOrigin || null,
|
|
21781
|
+
servers: { ...(current?.servers || {}) },
|
|
21782
|
+
};
|
|
21783
|
+
for (const [name, perms] of Object.entries(addition.servers || {})) {
|
|
21784
|
+
const existing = out.servers[name] || {
|
|
21785
|
+
tools: [],
|
|
21786
|
+
readPaths: [],
|
|
21787
|
+
writePaths: [],
|
|
21788
|
+
};
|
|
21789
|
+
out.servers[name] = {
|
|
21790
|
+
tools: [...new Set([...(existing.tools || []), ...(perms.tools || [])])],
|
|
21791
|
+
readPaths: [
|
|
21792
|
+
...new Set([...(existing.readPaths || []), ...(perms.readPaths || [])]),
|
|
21793
|
+
],
|
|
21794
|
+
writePaths: [
|
|
21795
|
+
...new Set([
|
|
21796
|
+
...(existing.writePaths || []),
|
|
21797
|
+
...(perms.writePaths || []),
|
|
21798
|
+
]),
|
|
21799
|
+
],
|
|
21800
|
+
};
|
|
21801
|
+
}
|
|
21802
|
+
return out;
|
|
21803
|
+
}
|
|
21804
|
+
|
|
21805
|
+
/**
|
|
21806
|
+
* Async wrapper around gateToolCall that escalates "no grant" denials
|
|
21807
|
+
* to a just-in-time consent prompt when `opts.enableJit` is true.
|
|
21808
|
+
* On approval, the user's chosen grant shape (carried on
|
|
21809
|
+
* `decision.granted`) is merged into the persisted grant and the gate
|
|
21810
|
+
* re-evaluates. On denial / timeout / disabled-flag, returns the
|
|
21811
|
+
* synchronous decision unchanged.
|
|
21812
|
+
*
|
|
21813
|
+
* @returns {Promise<{ allow: true } | { allow: false, reason: string }>}
|
|
21814
|
+
*/
|
|
21815
|
+
async function gateToolCallWithJit$1(req, opts = {}) {
|
|
21816
|
+
const initial = gateToolCall$1(req);
|
|
21817
|
+
if (initial.allow) return initial;
|
|
21818
|
+
if (!opts.enableJit) return initial;
|
|
21819
|
+
if (!_isNoGrantDenial(initial.reason)) return initial;
|
|
21820
|
+
|
|
21821
|
+
let decision;
|
|
21822
|
+
try {
|
|
21823
|
+
decision = await requestApproval(
|
|
21824
|
+
{
|
|
21825
|
+
widgetId: req.widgetId,
|
|
21826
|
+
domain: "mcp",
|
|
21827
|
+
action: "callTool",
|
|
21828
|
+
args: {
|
|
21829
|
+
serverName: req.serverName,
|
|
21830
|
+
toolName: req.toolName,
|
|
21831
|
+
args: req.args || {},
|
|
21832
|
+
},
|
|
21833
|
+
},
|
|
21834
|
+
{ timeoutMs: opts.timeoutMs },
|
|
21835
|
+
);
|
|
21836
|
+
} catch (e) {
|
|
21837
|
+
return {
|
|
21838
|
+
allow: false,
|
|
21839
|
+
reason:
|
|
21840
|
+
"JIT consent " +
|
|
21841
|
+
(e && e.message ? e.message : "failed") +
|
|
21842
|
+
"; original denial: " +
|
|
21843
|
+
initial.reason,
|
|
21844
|
+
};
|
|
21845
|
+
}
|
|
21846
|
+
|
|
21847
|
+
if (!decision || decision.approve !== true) {
|
|
21848
|
+
return {
|
|
21849
|
+
allow: false,
|
|
21850
|
+
reason:
|
|
21851
|
+
"user declined JIT consent for widget '" +
|
|
21852
|
+
req.widgetId +
|
|
21853
|
+
"' calling '" +
|
|
21854
|
+
req.toolName +
|
|
21855
|
+
"' on '" +
|
|
21856
|
+
req.serverName +
|
|
21857
|
+
"'",
|
|
21858
|
+
};
|
|
21859
|
+
}
|
|
21860
|
+
|
|
21861
|
+
// The renderer is expected to carry the chosen grant shape on
|
|
21862
|
+
// decision.granted. Fall back to a minimal tool-level grant if the
|
|
21863
|
+
// shape is missing — never silently grant paths the user didn't
|
|
21864
|
+
// explicitly approve.
|
|
21865
|
+
const addition =
|
|
21866
|
+
decision.granted && typeof decision.granted === "object"
|
|
21867
|
+
? decision.granted
|
|
21868
|
+
: {
|
|
21869
|
+
grantOrigin: "live",
|
|
21870
|
+
servers: {
|
|
21871
|
+
[req.serverName]: {
|
|
21872
|
+
tools: [req.toolName],
|
|
21873
|
+
readPaths: [],
|
|
21874
|
+
writePaths: [],
|
|
21875
|
+
},
|
|
21876
|
+
},
|
|
21877
|
+
};
|
|
21878
|
+
// Force grantOrigin: "live" regardless of what the renderer sent.
|
|
21879
|
+
addition.grantOrigin = "live";
|
|
21880
|
+
|
|
21881
|
+
try {
|
|
21882
|
+
const current = getGrant$1(req.widgetId);
|
|
21883
|
+
const merged = _mergeGrant(current, addition);
|
|
21884
|
+
setGrant$1(req.widgetId, merged);
|
|
21885
|
+
} catch (e) {
|
|
21886
|
+
return {
|
|
21887
|
+
allow: false,
|
|
21888
|
+
reason:
|
|
21889
|
+
"JIT consent: failed to persist grant: " +
|
|
21890
|
+
(e && e.message ? e.message : String(e)),
|
|
21891
|
+
};
|
|
21892
|
+
}
|
|
21893
|
+
|
|
21894
|
+
// Re-evaluate against the freshly-persisted grant. If the user's
|
|
21895
|
+
// grant shape didn't actually cover the requested call (e.g. they
|
|
21896
|
+
// approved a different tool or path), the gate denies as usual.
|
|
21897
|
+
return gateToolCall$1(req);
|
|
21898
|
+
}
|
|
21899
|
+
|
|
21538
21900
|
var permissionGate = {
|
|
21539
21901
|
gateToolCall: gateToolCall$1,
|
|
21902
|
+
gateToolCallWithJit: gateToolCallWithJit$1,
|
|
21540
21903
|
isWriteTool,
|
|
21541
21904
|
PATH_ARG_KEYS,
|
|
21542
21905
|
};
|
|
@@ -21742,7 +22105,7 @@ const path$e = require$$1$2;
|
|
|
21742
22105
|
const fs$a = require$$0$2;
|
|
21743
22106
|
const os$2 = require$$2$1;
|
|
21744
22107
|
const responseCache$2 = responseCache_1;
|
|
21745
|
-
const { gateToolCall } = permissionGate;
|
|
22108
|
+
const { gateToolCall, gateToolCallWithJit } = permissionGate;
|
|
21746
22109
|
const { serverKey, parseServerKey } = mcpServerKey;
|
|
21747
22110
|
const { applyPathScopeToCredentials } = mcpScopeResolver;
|
|
21748
22111
|
const { app: app$7 } = require$$0$1;
|
|
@@ -21767,6 +22130,26 @@ function isWidgetPermissionEnforcementEnabled() {
|
|
|
21767
22130
|
}
|
|
21768
22131
|
}
|
|
21769
22132
|
|
|
22133
|
+
// Just-in-time consent feature flag (Phase 1 of the JIT consent slice).
|
|
22134
|
+
// When ON and the gate would deny for "no grant", we pause the call
|
|
22135
|
+
// and prompt the user via the JitConsentModal. Default OFF — the gate
|
|
22136
|
+
// fails closed as before until the user opts in.
|
|
22137
|
+
function isJitConsentEnabled() {
|
|
22138
|
+
try {
|
|
22139
|
+
const settingsPath = path$e.join(
|
|
22140
|
+
app$7.getPath("userData"),
|
|
22141
|
+
"Dashboard",
|
|
22142
|
+
"settings.json",
|
|
22143
|
+
);
|
|
22144
|
+
if (!fs$a.existsSync(settingsPath)) return false;
|
|
22145
|
+
const raw = fs$a.readFileSync(settingsPath, "utf8");
|
|
22146
|
+
const settings = JSON.parse(raw);
|
|
22147
|
+
return Boolean(settings?.security?.enableJitConsent);
|
|
22148
|
+
} catch (_e) {
|
|
22149
|
+
return false;
|
|
22150
|
+
}
|
|
22151
|
+
}
|
|
22152
|
+
|
|
21770
22153
|
/**
|
|
21771
22154
|
* Tool name prefixes considered safe to cache (read-only).
|
|
21772
22155
|
* Writes/mutations are NOT cached so they always hit the source.
|
|
@@ -22530,16 +22913,19 @@ const mcpController$3 = {
|
|
|
22530
22913
|
|
|
22531
22914
|
// Per-widget manifest gate. Activated by the
|
|
22532
22915
|
// security.enforceWidgetMcpPermissions setting. When enabled
|
|
22533
|
-
// and a widgetId is supplied, the widget's
|
|
22534
|
-
//
|
|
22535
|
-
//
|
|
22916
|
+
// and a widgetId is supplied, the widget's persisted grant
|
|
22917
|
+
// determines what tools and paths are allowed.
|
|
22918
|
+
//
|
|
22919
|
+
// JIT consent: when security.enableJitConsent is also on, the
|
|
22920
|
+
// gate escalates "no grant" denials into a runtime prompt
|
|
22921
|
+
// (jitConsent.requestApproval → renderer modal → grant write +
|
|
22922
|
+
// re-evaluate). Other denial reasons (path traversal, malformed
|
|
22923
|
+
// args, etc.) stay synchronous.
|
|
22536
22924
|
if (isWidgetPermissionEnforcementEnabled() && widgetId) {
|
|
22537
|
-
const
|
|
22538
|
-
|
|
22539
|
-
|
|
22540
|
-
|
|
22541
|
-
args,
|
|
22542
|
-
});
|
|
22925
|
+
const gateReq = { widgetId, serverName, toolName, args };
|
|
22926
|
+
const gate = isJitConsentEnabled()
|
|
22927
|
+
? await gateToolCallWithJit(gateReq, { enableJit: true })
|
|
22928
|
+
: gateToolCall(gateReq);
|
|
22543
22929
|
if (!gate.allow) {
|
|
22544
22930
|
throw new Error(`Widget permission gate: ${gate.reason}`);
|
|
22545
22931
|
}
|
|
@@ -65573,6 +65959,7 @@ const webSocketController = webSocketController_1;
|
|
|
65573
65959
|
const extractionCacheController = extractionCacheController_1;
|
|
65574
65960
|
const mcpDashServerController = mcpDashServerController_1;
|
|
65575
65961
|
const widgetMcpGrantsController = widgetMcpGrantsController$1;
|
|
65962
|
+
const jitConsent = jitConsent$1;
|
|
65576
65963
|
|
|
65577
65964
|
// --- Errors ---
|
|
65578
65965
|
const themeFromUrlErrors = themeFromUrlErrors$1;
|
|
@@ -65678,6 +66065,7 @@ var electron = {
|
|
|
65678
66065
|
extractionCacheController,
|
|
65679
66066
|
mcpDashServerController,
|
|
65680
66067
|
widgetMcpGrantsController,
|
|
66068
|
+
jitConsent,
|
|
65681
66069
|
|
|
65682
66070
|
// Controller functions (flat) — spread for convenient destructuring
|
|
65683
66071
|
...controllers,
|