@dsh-electron/dsh-plugin-multi-root-workspace 0.1.6 → 0.1.8
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/CHANGELOG.en.md +51 -1
- package/CHANGELOG.md +51 -1
- package/README.en.md +155 -92
- package/README.md +156 -91
- package/cordis.patch.yml +69 -4
- package/lib/client.js +2678 -276
- package/lib/client.js.map +1 -1
- package/lib/command-CESuwlVt.js +1306 -0
- package/lib/command.js +2 -2
- package/lib/compat-DR2LvmYZ.js +600 -0
- package/lib/compat.js +1 -1
- package/lib/contract-DZ4zczDT.js +238 -0
- package/lib/{fs-D_Sw3o5z.js → fs-Nlp2AiRX.js} +56 -24
- package/lib/fs.js +2 -1
- package/lib/index.js +10 -8
- package/lib/{instructions-DfzN-wVf.js → instructions-D19jRxN0.js} +3 -2
- package/lib/instructions.js +1 -1
- package/lib/lsp.js +7 -2
- package/lib/method-wrapper-CFY3p3j2.js +26 -0
- package/lib/process-fence.js +280 -0
- package/lib/providers.js +87 -0
- package/lib/{registry-DySMbjgW.js → registry-DCxC5Lay.js} +256 -35
- package/lib/registry.js +2 -2
- package/lib/{roots-DasWEBJk.js → roots-B-0l8mNG.js} +117 -20
- package/lib/sandbox-CVAcT_gy.js +743 -0
- package/lib/sandbox.js +2 -1
- package/lib/scope.js +229 -20
- package/lib/types/client/RootSwitcher.d.ts +63 -0
- package/lib/types/client/WorkspaceFoldersAction.d.ts +39 -1
- package/lib/types/client/files-tree/FilesBody.module.css.d.ts +24 -0
- package/lib/types/client/files-tree/FilesTree.d.ts +47 -0
- package/lib/types/client/files-tree/adapter.d.ts +94 -0
- package/lib/types/client/files-tree/directory-node.d.ts +62 -0
- package/lib/types/client/files-tree/face.d.ts +107 -0
- package/lib/types/client/files-tree/locales.d.ts +10 -0
- package/lib/types/client/files-tree/primitives.d.ts +33 -0
- package/lib/types/client/files-tree/store.d.ts +81 -0
- package/lib/types/client/locales.d.ts +56 -10
- package/lib/types/client/open-in-app/OpenInAppAction.d.ts +27 -0
- package/lib/types/client/open-in-app/OpenTargetButton.d.ts +52 -0
- package/lib/types/client/open-in-app/OpenTargetButton.module.css.d.ts +29 -0
- package/lib/types/client/open-in-app/adapter.d.ts +46 -0
- package/lib/types/client/open-in-app/applications.d.ts +4 -0
- package/lib/types/client/open-in-app/controller.d.ts +53 -0
- package/lib/types/client/open-in-app/locales.d.ts +10 -0
- package/lib/types/client/open-in-app/open-failure-toast.d.ts +11 -0
- package/lib/types/client/open-in-app/open-path.d.ts +13 -0
- package/lib/types/client/panel-client.d.ts +2 -1
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/command.d.ts +16 -3
- package/lib/types/compat/client-icons.d.ts +18 -0
- package/lib/types/compat/dsh-version.d.ts +57 -9
- package/lib/types/compat/open-in-app-routes.d.ts +38 -0
- package/lib/types/compat/process-entrypoints.d.ts +31 -0
- package/lib/types/compat/process-result.d.ts +14 -0
- package/lib/types/compat/pty-marker.d.ts +83 -0
- package/lib/types/compat/sandbox-confine.d.ts +2 -0
- package/lib/types/compat/storage-zod.d.ts +65 -0
- package/lib/types/compat/windows-deny-preflight.d.ts +21 -0
- package/lib/types/compat/windows-native.d.ts +68 -0
- package/lib/types/compat/windows-path-guard.d.ts +56 -0
- package/lib/types/compat/workspace-files.d.ts +2 -0
- package/lib/types/compat.d.ts +15 -5
- package/lib/types/contract.d.ts +31 -19
- package/lib/types/dialects.d.ts +99 -3
- package/lib/types/fs.d.ts +32 -10
- package/lib/types/index.d.ts +8 -3
- package/lib/types/process-fence.d.ts +54 -0
- package/lib/types/profile-isolation.d.ts +178 -0
- package/lib/types/providers.d.ts +70 -0
- package/lib/types/registry.d.ts +132 -31
- package/lib/types/roots.d.ts +100 -13
- package/lib/types/sandbox.d.ts +59 -3
- package/lib/types/scope.d.ts +200 -14
- package/lib/types/windows-broker.d.ts +17 -0
- package/lib/types/windows-deny.d.ts +1 -0
- package/lib/types/windows-profile.d.ts +10 -13
- package/lib/types/windows-runner-contract.d.ts +97 -0
- package/lib/windows-runner-contract-CEfmZzu0.js +141 -0
- package/lib/windows-runner.js +1256 -13
- package/lib/workspace-files.js +138 -3
- package/lib/workspace-routing-YPxaQ4V3.js +26 -0
- package/package.json +84 -62
- package/scripts/windows-broker/install.ps1 +89 -0
- package/scripts/windows-broker/rights.cs +58 -0
- package/scripts/windows-broker/service.cs +323 -0
- package/scripts/windows-broker/verify-install.ps1 +80 -0
- package/lib/command-CTdNvwt1.js +0 -791
- package/lib/compat-B6ZqbQBy.js +0 -295
- package/lib/method-wrapper-Zc-JlDP3.js +0 -51
- package/lib/sandbox-BRXl_FpF.js +0 -132
- package/lib/types/client/RootFileTree.d.ts +0 -12
- package/lib/types/panel-files.d.ts +0 -3
- package/lib/windows-profile-CiFsFYIV.js +0 -216
- package/lib/{registry-lease-win32-dy8TQMD9.js → registry-lease-win32-DTO6QpT9.js} +2 -2
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { canonicalPath } from "@deepseek-ai/dsh-sandbox";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
//#region src/dialects.ts
|
|
5
|
-
/**
|
|
6
|
-
* Raised when a confined argv cannot be recognized or widened. The provider
|
|
7
|
-
* converts this into the fail-closed `SandboxUnavailableError`: an unrecognized
|
|
8
|
-
* dialect must never degrade into "the primary root was granted after all".
|
|
9
|
-
*/
|
|
10
|
-
var DialectUnrecognizedError = class extends Error {
|
|
11
|
-
constructor(message) {
|
|
12
|
-
super(message);
|
|
13
|
-
this.name = "DialectUnrecognizedError";
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
/** The one SBPL form that carries the writable subpath grants. */
|
|
17
|
-
const SBPL_SUBPATH_FORM = /\(allow file-write\*\s*(?:\(subpath\s+"(?:[^"\\]|\\.)*"\)\s*)+\)/;
|
|
18
|
-
/** One SBPL `(subpath "…")` token inside that form. */
|
|
19
|
-
const SBPL_SUBPATH_TOKEN = /\(subpath\s+("(?:[^"\\]|\\.)*")\)/g;
|
|
20
|
-
/**
|
|
21
|
-
* Split a `confine()` result at the separator by arithmetic, then VERIFY the
|
|
22
|
-
* split: `confine` builds `[...profileArgs, '--', ...argv]`, so the separator
|
|
23
|
-
* sits exactly one element before the caller's argv. A mismatch means the
|
|
24
|
-
* provider no longer wraps argv the way this module assumes.
|
|
25
|
-
* @param confinedArgv - the argv `confine` returned.
|
|
26
|
-
* @param commandArgv - the argv the caller passed to `confine`.
|
|
27
|
-
* @returns the recognized dialect and the split parts.
|
|
28
|
-
*/
|
|
29
|
-
function splitConfined(confinedArgv, commandArgv) {
|
|
30
|
-
const separator = confinedArgv.length - commandArgv.length - 1;
|
|
31
|
-
if (separator < 1 || confinedArgv[separator] !== "--") throw new DialectUnrecognizedError(`confined argv is not "[...profile, --, ...argv]" (separator expected at ${separator}, got ${JSON.stringify(confinedArgv[separator])})`);
|
|
32
|
-
const commandArgs = confinedArgv.slice(separator + 1);
|
|
33
|
-
if (commandArgs.length !== commandArgv.length || commandArgs.some((argument, index) => argument !== commandArgv[index])) throw new DialectUnrecognizedError("confined argv does not end with the caller's argv verbatim");
|
|
34
|
-
const profileArgs = confinedArgv.slice(0, separator);
|
|
35
|
-
return {
|
|
36
|
-
dialect: detectDialect(profileArgs),
|
|
37
|
-
profileArgs,
|
|
38
|
-
separator,
|
|
39
|
-
commandArgs
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Recognize the runner dialect from the profile arguments alone. Exactly one
|
|
44
|
-
* marker must match; zero or several are both unrecognized.
|
|
45
|
-
* @param profileArgs - everything before the separator.
|
|
46
|
-
* @returns the dialect.
|
|
47
|
-
*/
|
|
48
|
-
function detectDialect(profileArgs) {
|
|
49
|
-
const matches = [];
|
|
50
|
-
if (profileArgs.length >= 2 && profileArgs[profileArgs.length - 2] === "-p") matches.push("seatbelt");
|
|
51
|
-
if (profileArgs.includes("--mode")) matches.push("windows-acl");
|
|
52
|
-
if (profileArgs.includes("--rw")) matches.push("landlock");
|
|
53
|
-
if (profileArgs.includes("--ro-bind")) matches.push("bwrap");
|
|
54
|
-
const [first, ...rest] = matches;
|
|
55
|
-
if (first === void 0) throw new DialectUnrecognizedError(`unrecognized sandbox profile arguments: ${JSON.stringify(profileArgs)}`);
|
|
56
|
-
if (rest.length > 0) throw new DialectUnrecognizedError(`ambiguous sandbox profile arguments (${matches.join(", ")}): ${JSON.stringify(profileArgs)}`);
|
|
57
|
-
return first;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Widen one dialect's profile arguments with the additional roots.
|
|
61
|
-
*
|
|
62
|
-
* Only `workspace-write` grants roots, mirroring the upstream mode meaning and
|
|
63
|
-
* the filesystem fence: `read-only` grants nothing, and `danger-full-access`
|
|
64
|
-
* never confines at all (bash and the PTY backend skip `confine` in that mode).
|
|
65
|
-
*
|
|
66
|
-
* A root the dialect ALREADY grants is not granted twice — that keeps this
|
|
67
|
-
* function aligned with the fence's own deduplication and, on bwrap, avoids
|
|
68
|
-
* binding the real `/tmp` over the ephemeral `--tmpfs /tmp` mount.
|
|
69
|
-
* @param dialect - the recognized dialect.
|
|
70
|
-
* @param profileArgs - the profile arguments to widen.
|
|
71
|
-
* @param policy - the per-call policy (its root is the policy root, never a new grant).
|
|
72
|
-
* @param additionalRoots - canonical additional roots, in scope order.
|
|
73
|
-
* @returns the widened profile arguments.
|
|
74
|
-
*/
|
|
75
|
-
function widenProfileArgs(dialect, profileArgs, policy, additionalRoots) {
|
|
76
|
-
if (policy.mode !== "workspace-write") throw new DialectUnrecognizedError(`dialect "${dialect}" grants write roots only under workspace-write, got mode "${policy.mode}"`);
|
|
77
|
-
if (additionalRoots.length === 0) return [...profileArgs];
|
|
78
|
-
switch (dialect) {
|
|
79
|
-
case "seatbelt": return widenSeatbelt(profileArgs, additionalRoots);
|
|
80
|
-
case "bwrap": return widenBwrap(profileArgs, policy.workspaceRoot, additionalRoots);
|
|
81
|
-
case "landlock": return widenLandlock(profileArgs, policy.workspaceRoot, additionalRoots);
|
|
82
|
-
case "windows-acl": throw new DialectUnrecognizedError("Windows ACL widening requires widenWindowsProfile");
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
/** Quote one path exactly the way the upstream Seatbelt profile builder does. */
|
|
86
|
-
function sbplString(path) {
|
|
87
|
-
return `"${path.replaceAll("\\", String.raw`\\`).replaceAll("\"", String.raw`\"`)}"`;
|
|
88
|
-
}
|
|
89
|
-
/** Unquote one SBPL string literal (including its surrounding quotes). */
|
|
90
|
-
function sbplValue(literal) {
|
|
91
|
-
return literal.slice(1, -1).replaceAll(String.raw`\"`, "\"").replaceAll(String.raw`\\`, "\\");
|
|
92
|
-
}
|
|
93
|
-
/** Append the additional roots to the SBPL form that carries the subpath grants. */
|
|
94
|
-
function widenSeatbelt(profileArgs, additionalRoots) {
|
|
95
|
-
const profile = profileArgs[profileArgs.length - 1];
|
|
96
|
-
if (profile === void 0 || profileArgs[profileArgs.length - 2] !== "-p") throw new DialectUnrecognizedError(`seatbelt profile is not "[<sandbox-exec>, …, -p, <SBPL>]": ${JSON.stringify(profileArgs)}`);
|
|
97
|
-
const form = SBPL_SUBPATH_FORM.exec(profile);
|
|
98
|
-
if (form === null) throw new DialectUnrecognizedError(`seatbelt profile has no "(allow file-write* (subpath …))" form to extend: ${JSON.stringify(profile)}`);
|
|
99
|
-
const matched = form[0];
|
|
100
|
-
const granted = new Set([...matched.matchAll(SBPL_SUBPATH_TOKEN)].map((token) => sbplValue(token[1])));
|
|
101
|
-
const added = additionalRoots.filter((root) => !granted.has(root)).map((root) => `(subpath ${sbplString(root)})`);
|
|
102
|
-
if (added.length === 0) return [...profileArgs];
|
|
103
|
-
const widened = profile.slice(0, form.index) + matched.slice(0, -1) + ` ${added.join(" ")})` + profile.slice(form.index + matched.length);
|
|
104
|
-
return [...profileArgs.slice(0, -1), widened];
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* The observed bwrap bind flag: the flag of the `[flag, <root>, <root>]` triple
|
|
108
|
-
* upstream built for the policy root.
|
|
109
|
-
*/
|
|
110
|
-
function bwrapBindFlag(profileArgs, workspaceRoot) {
|
|
111
|
-
for (let index = 2; index < profileArgs.length; index += 1) {
|
|
112
|
-
if (profileArgs[index] !== workspaceRoot || profileArgs[index - 1] !== workspaceRoot) continue;
|
|
113
|
-
const flag = profileArgs[index - 2];
|
|
114
|
-
if (flag === void 0 || !flag.startsWith("--") || flag.startsWith("--ro")) continue;
|
|
115
|
-
return flag;
|
|
116
|
-
}
|
|
117
|
-
throw new DialectUnrecognizedError(`bwrap profile has no writable bind of the policy root ${JSON.stringify(workspaceRoot)}: ${JSON.stringify(profileArgs)}`);
|
|
118
|
-
}
|
|
119
|
-
/** Append the additional roots as binds, after upstream's own root bind. */
|
|
120
|
-
function widenBwrap(profileArgs, workspaceRoot, additionalRoots) {
|
|
121
|
-
const flag = bwrapBindFlag(profileArgs, workspaceRoot);
|
|
122
|
-
const granted = /* @__PURE__ */ new Set();
|
|
123
|
-
for (let index = 0; index + 2 < profileArgs.length; index += 1) if (profileArgs[index] === flag && profileArgs[index + 1] === profileArgs[index + 2]) granted.add(profileArgs[index + 2]);
|
|
124
|
-
for (let index = 0; index + 1 < profileArgs.length; index += 1) if (profileArgs[index] === "--tmpfs") granted.add(profileArgs[index + 1]);
|
|
125
|
-
const added = additionalRoots.filter((root) => !granted.has(root)).flatMap((root) => [
|
|
126
|
-
flag,
|
|
127
|
-
root,
|
|
128
|
-
root
|
|
129
|
-
]);
|
|
130
|
-
return [...profileArgs, ...added];
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* The observed Landlock read-write flag: the flag preceding the policy root in
|
|
134
|
-
* the launcher's grant arguments.
|
|
135
|
-
*/
|
|
136
|
-
function landlockReadWriteFlag(profileArgs, workspaceRoot) {
|
|
137
|
-
for (let index = 1; index < profileArgs.length; index += 1) {
|
|
138
|
-
if (profileArgs[index] !== workspaceRoot) continue;
|
|
139
|
-
const flag = profileArgs[index - 1];
|
|
140
|
-
if (flag === void 0 || !flag.startsWith("--") || flag.startsWith("--ro")) continue;
|
|
141
|
-
return flag;
|
|
142
|
-
}
|
|
143
|
-
throw new DialectUnrecognizedError(`landlock profile has no read-write grant of the policy root ${JSON.stringify(workspaceRoot)}: ${JSON.stringify(profileArgs)}`);
|
|
144
|
-
}
|
|
145
|
-
/** Append the additional roots as read-write grants, after upstream's own root grant. */
|
|
146
|
-
function widenLandlock(profileArgs, workspaceRoot, additionalRoots) {
|
|
147
|
-
const flag = landlockReadWriteFlag(profileArgs, workspaceRoot);
|
|
148
|
-
const granted = /* @__PURE__ */ new Set();
|
|
149
|
-
for (let index = 0; index + 1 < profileArgs.length; index += 1) if (profileArgs[index] === flag) granted.add(profileArgs[index + 1]);
|
|
150
|
-
const added = additionalRoots.filter((root) => !granted.has(root)).flatMap((root) => [flag, root]);
|
|
151
|
-
return [...profileArgs, ...added];
|
|
152
|
-
}
|
|
153
|
-
//#endregion
|
|
154
|
-
//#region src/windows-profile.ts
|
|
155
|
-
/** Windows runner argv and capability identity. No Win32 calls at module load. */
|
|
156
|
-
/** A root-set capability: removing or adding a root changes the token's identity. */
|
|
157
|
-
function workspaceScopeSid(roots) {
|
|
158
|
-
const paths = [...new Set(roots.map((root) => root.toLowerCase()))].sort();
|
|
159
|
-
const hash = createHash("sha256").update("dsh-multi-root-workspace\0").update(JSON.stringify(paths)).digest();
|
|
160
|
-
return `S-1-4-${[
|
|
161
|
-
0,
|
|
162
|
-
4,
|
|
163
|
-
8,
|
|
164
|
-
12
|
|
165
|
-
].map((offset) => hash.readUInt32LE(offset) % (2 ** 30 - 1) + 1).join("-")}-2`;
|
|
166
|
-
}
|
|
167
|
-
/** Strictly recognize the upstream public runner contract, failing closed on new flags. */
|
|
168
|
-
function parseWindowsProfile(args, allowAdditional = false) {
|
|
169
|
-
const fields = /* @__PURE__ */ new Map();
|
|
170
|
-
const additionalRoots = [];
|
|
171
|
-
const known = /* @__PURE__ */ new Set([
|
|
172
|
-
"--workspace",
|
|
173
|
-
"--temp",
|
|
174
|
-
"--mode",
|
|
175
|
-
"--write-sid",
|
|
176
|
-
"--temp-write-sid"
|
|
177
|
-
]);
|
|
178
|
-
for (let index = 0; index < args.length; index += 2) {
|
|
179
|
-
const key = args[index];
|
|
180
|
-
const value = args[index + 1];
|
|
181
|
-
if (key === void 0 || value === void 0 || value === "") throw new DialectUnrecognizedError("Windows ACL profile has a missing value");
|
|
182
|
-
if (allowAdditional && key === "--additional-root") {
|
|
183
|
-
additionalRoots.push(value);
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
if (!known.has(key) || fields.has(key)) throw new DialectUnrecognizedError(`Windows ACL profile has unknown or duplicate flag ${key}`);
|
|
187
|
-
fields.set(key, value);
|
|
188
|
-
}
|
|
189
|
-
const workspace = fields.get("--workspace");
|
|
190
|
-
const temp = fields.get("--temp");
|
|
191
|
-
if (workspace === void 0 || temp === void 0 || fields.get("--mode") !== "workspace-write") throw new DialectUnrecognizedError("Windows multi-root requires --workspace, --temp and --mode workspace-write");
|
|
192
|
-
const writeSid = fields.get("--write-sid");
|
|
193
|
-
const tempWriteSid = fields.get("--temp-write-sid");
|
|
194
|
-
if (writeSid === void 0 !== (tempWriteSid === void 0)) throw new DialectUnrecognizedError("Windows ACL profile requires both capability SIDs or neither");
|
|
195
|
-
return {
|
|
196
|
-
workspace,
|
|
197
|
-
temp,
|
|
198
|
-
mode: "workspace-write",
|
|
199
|
-
additionalRoots,
|
|
200
|
-
...writeSid === void 0 ? {} : { writeSid },
|
|
201
|
-
...tempWriteSid === void 0 ? {} : { tempWriteSid }
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
function widenWindowsProfile(profile, roots, primaryRoot) {
|
|
205
|
-
if (profile.length < 2 || profile[0] === void 0 || profile[1]?.startsWith("--")) throw new DialectUnrecognizedError("Windows ACL runner invocation is not [node, runner, ...flags]");
|
|
206
|
-
const parsed = parseWindowsProfile(profile.slice(2));
|
|
207
|
-
if (primaryRoot !== void 0 && canonicalPath(parsed.workspace) !== primaryRoot) throw new DialectUnrecognizedError("Windows ACL profile workspace does not match the policy root");
|
|
208
|
-
return [
|
|
209
|
-
profile[0],
|
|
210
|
-
fileURLToPath(new URL("./windows-runner.js", import.meta.url)),
|
|
211
|
-
...profile.slice(2),
|
|
212
|
-
...roots.flatMap((root) => ["--additional-root", root])
|
|
213
|
-
];
|
|
214
|
-
}
|
|
215
|
-
//#endregion
|
|
216
|
-
export { splitConfined as a, DialectUnrecognizedError as i, widenWindowsProfile as n, widenProfileArgs as o, workspaceScopeSid as r, parseWindowsProfile as t };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
3
1
|
import { realpathSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
4
|
//#region src/registry-lease-win32.ts
|
|
5
5
|
/**
|
|
6
6
|
* Windows named-kernel-semaphore adapter for the registry authority lease.
|