@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
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The standalone Windows runner's contract — shared between the widened
|
|
3
|
+
* profile (`windows-profile.ts`, in-process) and `windows-runner.ts` (a
|
|
4
|
+
* separate Node process).
|
|
5
|
+
*
|
|
6
|
+
* ZERO bare-specifier imports: `lib/windows-runner.js` executes outside the
|
|
7
|
+
* host's plugin package resolution, so every module it statically reaches may
|
|
8
|
+
* import `node:` builtins only. tests/windows-multi-root.spec.ts and
|
|
9
|
+
* scripts/check-pack.mjs both walk the built runner's module graph and fail on
|
|
10
|
+
* any non-`node:` specifier.
|
|
11
|
+
*
|
|
12
|
+
* @module @dsh-electron/dsh-plugin-multi-root-workspace/windows-runner-contract
|
|
13
|
+
*/
|
|
14
|
+
/** IPC budget, independent of the registry's configurable add limit. Historical
|
|
15
|
+
* over-limit stores retain their records. Beyond this native launch budget the
|
|
16
|
+
* whole ambient launch is refused before ACL edits; denies are never truncated.
|
|
17
|
+
* Keep the broker service's MaxDenyRoots in sync (covered by contract tests).
|
|
18
|
+
*/
|
|
19
|
+
export declare const MAX_WINDOWS_DENY_ROOTS = 256;
|
|
20
|
+
export declare function assertWindowsDenyCapacity(count: number): void;
|
|
21
|
+
/**
|
|
22
|
+
* Raised when a confined argv or a runner profile cannot be recognized or
|
|
23
|
+
* widened. The provider converts this into the fail-closed
|
|
24
|
+
* `SandboxUnavailableError`: an unrecognized dialect must never degrade into
|
|
25
|
+
* "the primary root was granted after all". Re-exported from `dialects.ts`.
|
|
26
|
+
*/
|
|
27
|
+
export declare class DialectUnrecognizedError extends Error {
|
|
28
|
+
constructor(message: string);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The canonical spelling of `path`, or `path` itself when it does not exist.
|
|
32
|
+
*
|
|
33
|
+
* Mirrors `canonicalPath` from `@deepseek-ai/dsh-sandbox`: the published
|
|
34
|
+
* package ships only its entry point, and this module may not import it at
|
|
35
|
+
* all (the runner resolves upstream packages from the upstream runner's own
|
|
36
|
+
* location, never from its own directory). Same precedent and justification
|
|
37
|
+
* as `src/containment.ts` (ADR-0002).
|
|
38
|
+
*/
|
|
39
|
+
export declare function canonicalPath(path: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Whether a path names a Windows network or device location: two leading
|
|
42
|
+
* slashes or backslashes in any mix (`\\server\share`, `//server/share`,
|
|
43
|
+
* `\\?\…`, `\\.\…`). Windows ACL confinement cannot be shown to cover these —
|
|
44
|
+
* the restricted token's capability SID and Low label are not what an SMB
|
|
45
|
+
* server evaluates, and merely resolving an attacker-chosen UNC path triggers
|
|
46
|
+
* SMB authentication — so they are refused lexically, before any filesystem
|
|
47
|
+
* access. Because `canonicalPath` resolves a mapped drive letter to its
|
|
48
|
+
* `\\server\share` form, checking the canonical spelling also catches mapped
|
|
49
|
+
* network drives.
|
|
50
|
+
* @param path - the path spelling to test.
|
|
51
|
+
* @returns whether it is a network or device path.
|
|
52
|
+
*/
|
|
53
|
+
export declare function isWindowsNetworkOrDevicePath(path: string): boolean;
|
|
54
|
+
/** A root-set capability: removing or adding a root changes the token's identity. */
|
|
55
|
+
export declare function workspaceScopeSid(roots: readonly string[]): string;
|
|
56
|
+
/** A standing deny identity, deliberately disjoint from all write capabilities. */
|
|
57
|
+
export declare function rootDenySid(root: string): string;
|
|
58
|
+
export interface WindowsProfile {
|
|
59
|
+
workspace: string;
|
|
60
|
+
temp: string;
|
|
61
|
+
/**
|
|
62
|
+
* `workspace-write` grants the workspace, the private temp, and every
|
|
63
|
+
* `--additional-root`. `read-only` is legal only on the widened form (the
|
|
64
|
+
* standalone runner): the runner then grants the additional roots ALONE — a
|
|
65
|
+
* workspace-write token over just those directories — which is how a fixed
|
|
66
|
+
* `read-write` additional root stays writable inside a read-only session
|
|
67
|
+
* (B9). An upstream `read-only` profile carries no `--additional-root`, so
|
|
68
|
+
* the mode only ever reaches the runner with grants attached.
|
|
69
|
+
*/
|
|
70
|
+
mode: 'read-only' | 'workspace-write' | 'danger-full-access';
|
|
71
|
+
additionalRoots: string[];
|
|
72
|
+
deniedRoots: string[];
|
|
73
|
+
/**
|
|
74
|
+
* The upstream runner's own file (`--upstream-runner`). Required when
|
|
75
|
+
* `allowAdditional` parses — our standalone runner resolves
|
|
76
|
+
* `@deepseek-ai/dsh-sandbox-windows-acl` by package self-reference FROM this
|
|
77
|
+
* location, the only place that package is guaranteed importable.
|
|
78
|
+
*/
|
|
79
|
+
upstreamRunner?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Diagnostics label only (`--protected-home`): which `--deny-root` is the
|
|
82
|
+
* protected `$DSH_HOME`. It changes no authorization — every deny root's
|
|
83
|
+
* policy is identical — it only selects the remediation wording in
|
|
84
|
+
* hardlink/scan diagnostics. Valid only on the standalone
|
|
85
|
+
* `danger-full-access` form.
|
|
86
|
+
*/
|
|
87
|
+
protectedHome?: string;
|
|
88
|
+
writeSid?: string;
|
|
89
|
+
tempWriteSid?: string;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Strictly recognize the upstream public runner contract, failing closed on
|
|
93
|
+
* new flags. `allowAdditional` is the standalone runner's own mode: it accepts
|
|
94
|
+
* repeated `--additional-root` and requires `--upstream-runner` exactly once
|
|
95
|
+
* naming an absolute, existing file — anything else exits 127 without a child.
|
|
96
|
+
*/
|
|
97
|
+
export declare function parseWindowsProfile(args: readonly string[], allowAdditional?: boolean): WindowsProfile;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { existsSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, win32 } from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
function assertWindowsDenyCapacity(count) {
|
|
5
|
+
if (!Number.isInteger(count) || count < 1 || count > 256) throw new DialectUnrecognizedError(`Windows ambient restriction requires 1–256 deny roots including the protected DSH home; got ${count}. Reduce the registered restrictions before launching; no deny roots were dropped`);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Raised when a confined argv or a runner profile cannot be recognized or
|
|
9
|
+
* widened. The provider converts this into the fail-closed
|
|
10
|
+
* `SandboxUnavailableError`: an unrecognized dialect must never degrade into
|
|
11
|
+
* "the primary root was granted after all". Re-exported from `dialects.ts`.
|
|
12
|
+
*/
|
|
13
|
+
var DialectUnrecognizedError = class extends Error {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "DialectUnrecognizedError";
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The canonical spelling of `path`, or `path` itself when it does not exist.
|
|
21
|
+
*
|
|
22
|
+
* Mirrors `canonicalPath` from `@deepseek-ai/dsh-sandbox`: the published
|
|
23
|
+
* package ships only its entry point, and this module may not import it at
|
|
24
|
+
* all (the runner resolves upstream packages from the upstream runner's own
|
|
25
|
+
* location, never from its own directory). Same precedent and justification
|
|
26
|
+
* as `src/containment.ts` (ADR-0002).
|
|
27
|
+
*/
|
|
28
|
+
function canonicalPath(path) {
|
|
29
|
+
try {
|
|
30
|
+
return realpathSync.native(path);
|
|
31
|
+
} catch {
|
|
32
|
+
return path;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Whether a path names a Windows network or device location: two leading
|
|
37
|
+
* slashes or backslashes in any mix (`\\server\share`, `//server/share`,
|
|
38
|
+
* `\\?\…`, `\\.\…`). Windows ACL confinement cannot be shown to cover these —
|
|
39
|
+
* the restricted token's capability SID and Low label are not what an SMB
|
|
40
|
+
* server evaluates, and merely resolving an attacker-chosen UNC path triggers
|
|
41
|
+
* SMB authentication — so they are refused lexically, before any filesystem
|
|
42
|
+
* access. Because `canonicalPath` resolves a mapped drive letter to its
|
|
43
|
+
* `\\server\share` form, checking the canonical spelling also catches mapped
|
|
44
|
+
* network drives.
|
|
45
|
+
* @param path - the path spelling to test.
|
|
46
|
+
* @returns whether it is a network or device path.
|
|
47
|
+
*/
|
|
48
|
+
function isWindowsNetworkOrDevicePath(path) {
|
|
49
|
+
return /^[\\/]{2}/.test(path);
|
|
50
|
+
}
|
|
51
|
+
/** A root-set capability: removing or adding a root changes the token's identity. */
|
|
52
|
+
function workspaceScopeSid(roots) {
|
|
53
|
+
const paths = [...new Set(roots.map((root) => root.toLowerCase()))].sort();
|
|
54
|
+
const hash = createHash("sha256").update("dsh-multi-root-workspace\0").update(JSON.stringify(paths)).digest();
|
|
55
|
+
return `S-1-4-${[
|
|
56
|
+
0,
|
|
57
|
+
4,
|
|
58
|
+
8,
|
|
59
|
+
12
|
|
60
|
+
].map((offset) => hash.readUInt32LE(offset) % (2 ** 30 - 1) + 1).join("-")}-2`;
|
|
61
|
+
}
|
|
62
|
+
/** A standing deny identity, deliberately disjoint from all write capabilities. */
|
|
63
|
+
function rootDenySid(root) {
|
|
64
|
+
const hash = createHash("sha256").update("dsh-multi-root-deny\0").update(root.toLowerCase()).digest();
|
|
65
|
+
return `S-1-4-${[
|
|
66
|
+
0,
|
|
67
|
+
4,
|
|
68
|
+
8,
|
|
69
|
+
12
|
|
70
|
+
].map((offset) => hash.readUInt32LE(offset) % (2 ** 30 - 1) + 1).join("-")}-3`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Strictly recognize the upstream public runner contract, failing closed on
|
|
74
|
+
* new flags. `allowAdditional` is the standalone runner's own mode: it accepts
|
|
75
|
+
* repeated `--additional-root` and requires `--upstream-runner` exactly once
|
|
76
|
+
* naming an absolute, existing file — anything else exits 127 without a child.
|
|
77
|
+
*/
|
|
78
|
+
function parseWindowsProfile(args, allowAdditional = false) {
|
|
79
|
+
const fields = /* @__PURE__ */ new Map();
|
|
80
|
+
const additionalRoots = [];
|
|
81
|
+
const deniedRoots = [];
|
|
82
|
+
let upstreamRunner;
|
|
83
|
+
let protectedHome;
|
|
84
|
+
const known = /* @__PURE__ */ new Set([
|
|
85
|
+
"--workspace",
|
|
86
|
+
"--temp",
|
|
87
|
+
"--mode",
|
|
88
|
+
"--write-sid",
|
|
89
|
+
"--temp-write-sid"
|
|
90
|
+
]);
|
|
91
|
+
for (let index = 0; index < args.length; index += 2) {
|
|
92
|
+
const key = args[index];
|
|
93
|
+
const value = args[index + 1];
|
|
94
|
+
if (key === void 0 || value === void 0 || value === "") throw new DialectUnrecognizedError("Windows ACL profile has a missing value");
|
|
95
|
+
if (allowAdditional && key === "--additional-root") {
|
|
96
|
+
additionalRoots.push(value);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (allowAdditional && key === "--deny-root") {
|
|
100
|
+
deniedRoots.push(value);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (allowAdditional && key === "--upstream-runner") {
|
|
104
|
+
if (upstreamRunner !== void 0) throw new DialectUnrecognizedError("Windows ACL profile repeats --upstream-runner");
|
|
105
|
+
upstreamRunner = value;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (allowAdditional && key === "--protected-home") {
|
|
109
|
+
if (protectedHome !== void 0) throw new DialectUnrecognizedError("Windows ACL profile repeats --protected-home");
|
|
110
|
+
protectedHome = value;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (!known.has(key) || fields.has(key)) throw new DialectUnrecognizedError(`Windows ACL profile has unknown or duplicate flag ${key}`);
|
|
114
|
+
fields.set(key, value);
|
|
115
|
+
}
|
|
116
|
+
if (allowAdditional && upstreamRunner === void 0) throw new DialectUnrecognizedError("Windows multi-root runner requires --upstream-runner");
|
|
117
|
+
if (upstreamRunner !== void 0 && (!isAbsolute(upstreamRunner) || !existsSync(upstreamRunner) || !statSync(upstreamRunner).isFile())) throw new DialectUnrecognizedError(`--upstream-runner is not an absolute existing file: ${upstreamRunner}`);
|
|
118
|
+
const workspace = fields.get("--workspace");
|
|
119
|
+
const temp = fields.get("--temp");
|
|
120
|
+
const mode = fields.get("--mode");
|
|
121
|
+
if (workspace === void 0 || temp === void 0 || mode !== "workspace-write" && mode !== "read-only" && !(allowAdditional && mode === "danger-full-access")) throw new DialectUnrecognizedError("Windows multi-root requires --workspace, --temp, and --mode read-only or workspace-write");
|
|
122
|
+
if (mode === "danger-full-access" ? deniedRoots.length === 0 || additionalRoots.length > 0 || fields.has("--write-sid") || fields.has("--temp-write-sid") : deniedRoots.length > 0) throw new DialectUnrecognizedError("Windows ambient restriction requires deny roots only, without write grants or capability SIDs");
|
|
123
|
+
if (allowAdditional && mode === "read-only" && additionalRoots.length === 0) throw new DialectUnrecognizedError("Windows multi-root read-only requires at least one --additional-root");
|
|
124
|
+
const writeSid = fields.get("--write-sid");
|
|
125
|
+
const tempWriteSid = fields.get("--temp-write-sid");
|
|
126
|
+
if (writeSid === void 0 !== (tempWriteSid === void 0)) throw new DialectUnrecognizedError("Windows ACL profile requires both capability SIDs or neither");
|
|
127
|
+
if (protectedHome !== void 0 && (mode !== "danger-full-access" || !win32.isAbsolute(protectedHome) || !deniedRoots.some((root) => root.toLowerCase() === protectedHome.toLowerCase()))) throw new DialectUnrecognizedError("--protected-home must be an absolute member of the deny roots on a standalone danger-full-access profile");
|
|
128
|
+
return {
|
|
129
|
+
workspace,
|
|
130
|
+
temp,
|
|
131
|
+
mode,
|
|
132
|
+
additionalRoots,
|
|
133
|
+
deniedRoots,
|
|
134
|
+
...upstreamRunner === void 0 ? {} : { upstreamRunner },
|
|
135
|
+
...protectedHome === void 0 ? {} : { protectedHome },
|
|
136
|
+
...writeSid === void 0 ? {} : { writeSid },
|
|
137
|
+
...tempWriteSid === void 0 ? {} : { tempWriteSid }
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
export { parseWindowsProfile as a, isWindowsNetworkOrDevicePath as i, assertWindowsDenyCapacity as n, rootDenySid as o, canonicalPath as r, workspaceScopeSid as s, DialectUnrecognizedError as t };
|