@trops/dash-core 0.1.513 → 0.1.515
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 +177 -54
- package/dist/electron/index.js.map +1 -1
- package/dist/index.esm.js +13 -2
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/electron/index.js
CHANGED
|
@@ -1934,12 +1934,24 @@ function sanitizePerms(perms) {
|
|
|
1934
1934
|
? raw.writePaths.filter((p) => typeof p === "string")
|
|
1935
1935
|
: [],
|
|
1936
1936
|
};
|
|
1937
|
+
// Slice 4: per-action grant scoping. Persist `actions[]` only
|
|
1938
|
+
// when explicitly provided; the gate's legacy migration treats
|
|
1939
|
+
// its absence as "any action allowed" so pre-slice grants
|
|
1940
|
+
// continue to work.
|
|
1941
|
+
if (Array.isArray(raw.actions)) {
|
|
1942
|
+
domains.fs.actions = raw.actions.filter((a) => typeof a === "string");
|
|
1943
|
+
}
|
|
1937
1944
|
} else if (name === "network") {
|
|
1938
1945
|
domains.network = {
|
|
1939
1946
|
hosts: Array.isArray(raw.hosts)
|
|
1940
1947
|
? raw.hosts.filter((h) => typeof h === "string")
|
|
1941
1948
|
: [],
|
|
1942
1949
|
};
|
|
1950
|
+
if (Array.isArray(raw.actions)) {
|
|
1951
|
+
domains.network.actions = raw.actions.filter(
|
|
1952
|
+
(a) => typeof a === "string",
|
|
1953
|
+
);
|
|
1954
|
+
}
|
|
1943
1955
|
}
|
|
1944
1956
|
// Future domains plug in here. Unknown domain names are dropped.
|
|
1945
1957
|
}
|
|
@@ -2426,12 +2438,6 @@ function isFsWriteAction(action) {
|
|
|
2426
2438
|
return WRITE_ACTIONS.has(action);
|
|
2427
2439
|
}
|
|
2428
2440
|
|
|
2429
|
-
function _isNoGrantDenial$1(reason) {
|
|
2430
|
-
return (
|
|
2431
|
-
typeof reason === "string" && /no fs permissions granted/i.test(reason)
|
|
2432
|
-
);
|
|
2433
|
-
}
|
|
2434
|
-
|
|
2435
2441
|
function _filenameMatches(filename, allowedList) {
|
|
2436
2442
|
if (!Array.isArray(allowedList) || allowedList.length === 0) return false;
|
|
2437
2443
|
if (allowedList.includes("*")) return true;
|
|
@@ -2483,6 +2489,25 @@ function gateFsCall$1({ widgetId, token, action, args }) {
|
|
|
2483
2489
|
};
|
|
2484
2490
|
}
|
|
2485
2491
|
|
|
2492
|
+
// Slice 4: per-action allowlist. When `actions[]` is present and
|
|
2493
|
+
// non-empty, only listed actions are allowed (path scope still
|
|
2494
|
+
// applies). When absent / empty, fall through to legacy behavior
|
|
2495
|
+
// (any read/write-class action allowed against the path scope) so
|
|
2496
|
+
// pre-slice grants keep working — Option A migration.
|
|
2497
|
+
if (Array.isArray(fsPerms.actions) && fsPerms.actions.length > 0) {
|
|
2498
|
+
if (!fsPerms.actions.includes(action)) {
|
|
2499
|
+
return {
|
|
2500
|
+
allow: false,
|
|
2501
|
+
reason:
|
|
2502
|
+
"fs gate: action '" +
|
|
2503
|
+
action +
|
|
2504
|
+
"' not in actions allowlist for widget '" +
|
|
2505
|
+
widgetId +
|
|
2506
|
+
"'",
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2486
2511
|
const isWrite = isFsWriteAction(action);
|
|
2487
2512
|
|
|
2488
2513
|
if (isWrite) {
|
|
@@ -2543,7 +2568,7 @@ function _mergeFsGrant(current, addition) {
|
|
|
2543
2568
|
const additionFs = addition?.domains?.fs;
|
|
2544
2569
|
if (additionFs) {
|
|
2545
2570
|
const existingFs = out.domains.fs || { readPaths: [], writePaths: [] };
|
|
2546
|
-
|
|
2571
|
+
const merged = {
|
|
2547
2572
|
readPaths: [
|
|
2548
2573
|
...new Set([
|
|
2549
2574
|
...(existingFs.readPaths || []),
|
|
@@ -2559,30 +2584,80 @@ function _mergeFsGrant(current, addition) {
|
|
|
2559
2584
|
]),
|
|
2560
2585
|
],
|
|
2561
2586
|
};
|
|
2587
|
+
// Slice 4: union `actions[]`. Only emit the field when at least
|
|
2588
|
+
// one side declared it — preserves Option A migration (a legacy
|
|
2589
|
+
// grant being extended without an action allowlist on the
|
|
2590
|
+
// addition still has none after merge).
|
|
2591
|
+
const existingActions = Array.isArray(existingFs.actions)
|
|
2592
|
+
? existingFs.actions
|
|
2593
|
+
: null;
|
|
2594
|
+
const additionActions = Array.isArray(additionFs.actions)
|
|
2595
|
+
? additionFs.actions
|
|
2596
|
+
: null;
|
|
2597
|
+
if (existingActions || additionActions) {
|
|
2598
|
+
merged.actions = [
|
|
2599
|
+
...new Set([...(existingActions || []), ...(additionActions || [])]),
|
|
2600
|
+
];
|
|
2601
|
+
}
|
|
2602
|
+
out.domains.fs = merged;
|
|
2562
2603
|
}
|
|
2563
2604
|
return out;
|
|
2564
2605
|
}
|
|
2565
2606
|
|
|
2566
2607
|
/**
|
|
2567
|
-
* Async gate that escalates "
|
|
2568
|
-
* prompt when `opts.enableJit` is true.
|
|
2569
|
-
*
|
|
2608
|
+
* Async gate that escalates "missing in grant" fs denials to a JIT
|
|
2609
|
+
* consent prompt when `opts.enableJit` is true. Escalation signal is
|
|
2610
|
+
* STRUCTURAL, not message-based: if the requested action+filename
|
|
2611
|
+
* isn't covered by the widget's grant (action absent from `actions[]`,
|
|
2612
|
+
* or filename absent from the appropriate readPaths/writePaths), JIT
|
|
2613
|
+
* fires. Identity-resolution failures and malformed-args denials short-
|
|
2614
|
+
* circuit to the sync gate's verdict — those are abuse or caller bugs,
|
|
2615
|
+
* not consent gaps. Once the request IS covered by the grant, the sync
|
|
2616
|
+
* gate is authoritative (any denial it returns is structural).
|
|
2570
2617
|
*/
|
|
2571
2618
|
async function gateFsCallWithJit$1(req, opts = {}) {
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
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.
|
|
2619
|
+
if (!opts.enableJit) return gateFsCall$1(req);
|
|
2620
|
+
|
|
2621
|
+
// Identity must resolve to a concrete widgetId. Unknown tokens and
|
|
2622
|
+
// missing widgetId are returned by the sync gate — those denials
|
|
2623
|
+
// aren't recoverable via consent.
|
|
2580
2624
|
const resolved = _resolveIdentity$2({
|
|
2581
2625
|
token: req.token,
|
|
2582
2626
|
widgetId: req.widgetId,
|
|
2583
2627
|
});
|
|
2628
|
+
if (resolved.source === "token-unknown" || !resolved.widgetId) {
|
|
2629
|
+
return gateFsCall$1(req);
|
|
2630
|
+
}
|
|
2584
2631
|
const verifiedWidgetId = resolved.widgetId;
|
|
2585
|
-
|
|
2632
|
+
|
|
2633
|
+
// Args must be well-formed enough to derive a filename — malformed
|
|
2634
|
+
// calls are caller bugs, not consent gaps.
|
|
2635
|
+
const filename =
|
|
2636
|
+
req.args && typeof req.args === "object" ? req.args.filename : null;
|
|
2637
|
+
if (typeof filename !== "string" || !filename) {
|
|
2638
|
+
return gateFsCall$1(req);
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
// Structural escalation: when the existing grant covers this
|
|
2642
|
+
// (action, filename) pair, the sync gate's verdict is authoritative.
|
|
2643
|
+
// Otherwise the request is a consent gap; escalate. Mirrors
|
|
2644
|
+
// permissionGate.gateToolCallWithJit's "tool in grant?" check.
|
|
2645
|
+
const grant = getGrant$3(verifiedWidgetId);
|
|
2646
|
+
const fsPerms = grant && grant.domains && grant.domains.fs;
|
|
2647
|
+
if (fsPerms) {
|
|
2648
|
+
const actionsAllow =
|
|
2649
|
+
!Array.isArray(fsPerms.actions) ||
|
|
2650
|
+
fsPerms.actions.length === 0 ||
|
|
2651
|
+
fsPerms.actions.includes(req.action);
|
|
2652
|
+
const isWrite = isFsWriteAction(req.action);
|
|
2653
|
+
const allowedPaths = isWrite
|
|
2654
|
+
? fsPerms.writePaths || []
|
|
2655
|
+
: [...(fsPerms.readPaths || []), ...(fsPerms.writePaths || [])];
|
|
2656
|
+
const pathsAllow = _filenameMatches(filename, allowedPaths);
|
|
2657
|
+
if (actionsAllow && pathsAllow) {
|
|
2658
|
+
return gateFsCall$1(req);
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2586
2661
|
|
|
2587
2662
|
let decision;
|
|
2588
2663
|
try {
|
|
@@ -2598,11 +2673,7 @@ async function gateFsCallWithJit$1(req, opts = {}) {
|
|
|
2598
2673
|
} catch (e) {
|
|
2599
2674
|
return {
|
|
2600
2675
|
allow: false,
|
|
2601
|
-
reason:
|
|
2602
|
-
"JIT consent " +
|
|
2603
|
-
(e && e.message ? e.message : "failed") +
|
|
2604
|
-
"; original denial: " +
|
|
2605
|
-
initial.reason,
|
|
2676
|
+
reason: "JIT consent " + (e && e.message ? e.message : "failed"),
|
|
2606
2677
|
};
|
|
2607
2678
|
}
|
|
2608
2679
|
|
|
@@ -2618,8 +2689,8 @@ async function gateFsCallWithJit$1(req, opts = {}) {
|
|
|
2618
2689
|
};
|
|
2619
2690
|
}
|
|
2620
2691
|
|
|
2621
|
-
const
|
|
2622
|
-
const
|
|
2692
|
+
const fallbackFilename = req.args?.filename || "*";
|
|
2693
|
+
const fallbackIsWrite = isFsWriteAction(req.action);
|
|
2623
2694
|
const addition =
|
|
2624
2695
|
decision.granted && typeof decision.granted === "object"
|
|
2625
2696
|
? decision.granted
|
|
@@ -2627,8 +2698,9 @@ async function gateFsCallWithJit$1(req, opts = {}) {
|
|
|
2627
2698
|
grantOrigin: "live",
|
|
2628
2699
|
domains: {
|
|
2629
2700
|
fs: {
|
|
2630
|
-
|
|
2631
|
-
|
|
2701
|
+
actions: [req.action],
|
|
2702
|
+
readPaths: !fallbackIsWrite ? [fallbackFilename] : [],
|
|
2703
|
+
writePaths: fallbackIsWrite ? [fallbackFilename] : [],
|
|
2632
2704
|
},
|
|
2633
2705
|
},
|
|
2634
2706
|
};
|
|
@@ -2714,12 +2786,6 @@ function _resolveIdentity$1({ token, widgetId }) {
|
|
|
2714
2786
|
return { widgetId: widgetId || null, source: "legacy" };
|
|
2715
2787
|
}
|
|
2716
2788
|
|
|
2717
|
-
function _isNoGrantDenial(reason) {
|
|
2718
|
-
return (
|
|
2719
|
-
typeof reason === "string" && /no network permissions granted/i.test(reason)
|
|
2720
|
-
);
|
|
2721
|
-
}
|
|
2722
|
-
|
|
2723
2789
|
function _hostMatches(host, allowedList) {
|
|
2724
2790
|
if (!Array.isArray(allowedList) || allowedList.length === 0) return false;
|
|
2725
2791
|
if (allowedList.includes("*")) return true;
|
|
@@ -2805,6 +2871,23 @@ function gateNetworkCall$2({ widgetId, token, action, args }) {
|
|
|
2805
2871
|
};
|
|
2806
2872
|
}
|
|
2807
2873
|
|
|
2874
|
+
// Slice 4: per-action allowlist. Same Option A migration as fsGate —
|
|
2875
|
+
// missing/empty `actions[]` falls through to the legacy "any action
|
|
2876
|
+
// against allowed hosts" semantics so pre-slice grants keep working.
|
|
2877
|
+
if (Array.isArray(netPerms.actions) && netPerms.actions.length > 0) {
|
|
2878
|
+
if (!netPerms.actions.includes(action)) {
|
|
2879
|
+
return {
|
|
2880
|
+
allow: false,
|
|
2881
|
+
reason:
|
|
2882
|
+
"network gate: action '" +
|
|
2883
|
+
action +
|
|
2884
|
+
"' not in actions allowlist for widget '" +
|
|
2885
|
+
widgetId +
|
|
2886
|
+
"'",
|
|
2887
|
+
};
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2808
2891
|
if (_hostMatches(host, netPerms.hosts)) {
|
|
2809
2892
|
return { allow: true };
|
|
2810
2893
|
}
|
|
@@ -2832,7 +2915,7 @@ function _mergeNetworkGrant(current, addition) {
|
|
|
2832
2915
|
const additionNet = addition?.domains?.network;
|
|
2833
2916
|
if (additionNet) {
|
|
2834
2917
|
const existingNet = out.domains.network || { hosts: [] };
|
|
2835
|
-
|
|
2918
|
+
const merged = {
|
|
2836
2919
|
hosts: [
|
|
2837
2920
|
...new Set([
|
|
2838
2921
|
...(existingNet.hosts || []),
|
|
@@ -2840,30 +2923,73 @@ function _mergeNetworkGrant(current, addition) {
|
|
|
2840
2923
|
]),
|
|
2841
2924
|
],
|
|
2842
2925
|
};
|
|
2926
|
+
// Slice 4: union `actions[]`. Only emit when at least one side
|
|
2927
|
+
// declared it — preserves Option A migration.
|
|
2928
|
+
const existingActions = Array.isArray(existingNet.actions)
|
|
2929
|
+
? existingNet.actions
|
|
2930
|
+
: null;
|
|
2931
|
+
const additionActions = Array.isArray(additionNet.actions)
|
|
2932
|
+
? additionNet.actions
|
|
2933
|
+
: null;
|
|
2934
|
+
if (existingActions || additionActions) {
|
|
2935
|
+
merged.actions = [
|
|
2936
|
+
...new Set([...(existingActions || []), ...(additionActions || [])]),
|
|
2937
|
+
];
|
|
2938
|
+
}
|
|
2939
|
+
out.domains.network = merged;
|
|
2843
2940
|
}
|
|
2844
2941
|
return out;
|
|
2845
2942
|
}
|
|
2846
2943
|
|
|
2847
2944
|
/**
|
|
2848
|
-
* Async gate that escalates "
|
|
2849
|
-
* consent prompt when `opts.enableJit` is true.
|
|
2850
|
-
*
|
|
2945
|
+
* Async gate that escalates "missing in grant" network denials to a
|
|
2946
|
+
* JIT consent prompt when `opts.enableJit` is true. Escalation signal
|
|
2947
|
+
* is STRUCTURAL: if the requested action+host isn't covered by the
|
|
2948
|
+
* widget's grant (action absent from `actions[]`, or host absent from
|
|
2949
|
+
* `hosts[]`), JIT fires. Identity-resolution failures and malformed-
|
|
2950
|
+
* args / malformed-URL denials short-circuit to the sync verdict — not
|
|
2951
|
+
* recoverable via consent. Once the request IS covered, the sync gate
|
|
2952
|
+
* is authoritative.
|
|
2851
2953
|
*/
|
|
2852
2954
|
async function gateNetworkCallWithJit$2(req, opts = {}) {
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
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.
|
|
2955
|
+
if (!opts.enableJit) return gateNetworkCall$2(req);
|
|
2956
|
+
|
|
2957
|
+
// Identity must resolve. Unknown tokens / missing widgetId aren't
|
|
2958
|
+
// consent gaps.
|
|
2861
2959
|
const resolved = _resolveIdentity$1({
|
|
2862
2960
|
token: req.token,
|
|
2863
2961
|
widgetId: req.widgetId,
|
|
2864
2962
|
});
|
|
2963
|
+
if (resolved.source === "token-unknown" || !resolved.widgetId) {
|
|
2964
|
+
return gateNetworkCall$2(req);
|
|
2965
|
+
}
|
|
2865
2966
|
const verifiedWidgetId = resolved.widgetId;
|
|
2866
|
-
|
|
2967
|
+
|
|
2968
|
+
// Args must be well-formed enough to derive a host — malformed
|
|
2969
|
+
// calls aren't consent gaps.
|
|
2970
|
+
const url = req.args && typeof req.args === "object" ? req.args.url : null;
|
|
2971
|
+
if (typeof url !== "string" || !url) {
|
|
2972
|
+
return gateNetworkCall$2(req);
|
|
2973
|
+
}
|
|
2974
|
+
const host = _parseHost(url);
|
|
2975
|
+
if (!host) {
|
|
2976
|
+
return gateNetworkCall$2(req);
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
// Structural escalation: when the existing grant covers this
|
|
2980
|
+
// (action, host) pair, the sync gate's verdict is authoritative.
|
|
2981
|
+
const grant = getGrant$2(verifiedWidgetId);
|
|
2982
|
+
const netPerms = grant && grant.domains && grant.domains.network;
|
|
2983
|
+
if (netPerms) {
|
|
2984
|
+
const actionsAllow =
|
|
2985
|
+
!Array.isArray(netPerms.actions) ||
|
|
2986
|
+
netPerms.actions.length === 0 ||
|
|
2987
|
+
netPerms.actions.includes(req.action);
|
|
2988
|
+
const hostsAllow = _hostMatches(host, netPerms.hosts || []);
|
|
2989
|
+
if (actionsAllow && hostsAllow) {
|
|
2990
|
+
return gateNetworkCall$2(req);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2867
2993
|
|
|
2868
2994
|
let decision;
|
|
2869
2995
|
try {
|
|
@@ -2879,11 +3005,7 @@ async function gateNetworkCallWithJit$2(req, opts = {}) {
|
|
|
2879
3005
|
} catch (e) {
|
|
2880
3006
|
return {
|
|
2881
3007
|
allow: false,
|
|
2882
|
-
reason:
|
|
2883
|
-
"JIT consent " +
|
|
2884
|
-
(e && e.message ? e.message : "failed") +
|
|
2885
|
-
"; original denial: " +
|
|
2886
|
-
initial.reason,
|
|
3008
|
+
reason: "JIT consent " + (e && e.message ? e.message : "failed"),
|
|
2887
3009
|
};
|
|
2888
3010
|
}
|
|
2889
3011
|
|
|
@@ -2899,13 +3021,14 @@ async function gateNetworkCallWithJit$2(req, opts = {}) {
|
|
|
2899
3021
|
};
|
|
2900
3022
|
}
|
|
2901
3023
|
|
|
2902
|
-
const host = _parseHost(req.args?.url) || "*";
|
|
2903
3024
|
const addition =
|
|
2904
3025
|
decision.granted && typeof decision.granted === "object"
|
|
2905
3026
|
? decision.granted
|
|
2906
3027
|
: {
|
|
2907
3028
|
grantOrigin: "live",
|
|
2908
|
-
domains: {
|
|
3029
|
+
domains: {
|
|
3030
|
+
network: { actions: [req.action], hosts: [host] },
|
|
3031
|
+
},
|
|
2909
3032
|
};
|
|
2910
3033
|
addition.grantOrigin = "live";
|
|
2911
3034
|
|