@dsh-electron/dsh-plugin-multi-root-workspace 0.1.5 → 0.1.7

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.
Files changed (101) hide show
  1. package/CHANGELOG.en.md +56 -1
  2. package/CHANGELOG.md +56 -1
  3. package/README.en.md +157 -92
  4. package/README.md +158 -93
  5. package/cordis.patch.yml +61 -0
  6. package/lib/client.js +2819 -115
  7. package/lib/client.js.map +1 -1
  8. package/lib/command-CESuwlVt.js +1306 -0
  9. package/lib/command.js +2 -2
  10. package/lib/{compat-82Y27Iom.js → compat-DF_yL1bB.js} +180 -4
  11. package/lib/compat.js +1 -1
  12. package/lib/contract-DZ4zczDT.js +238 -0
  13. package/lib/{fs-D_Sw3o5z.js → fs-9JIzGaf3.js} +55 -19
  14. package/lib/fs.js +2 -1
  15. package/lib/index.js +10 -8
  16. package/lib/{instructions-Cn1Jj5Xi.js → instructions-D19jRxN0.js} +109 -28
  17. package/lib/instructions.js +1 -1
  18. package/lib/lsp.js +43 -0
  19. package/lib/method-wrapper-CFY3p3j2.js +26 -0
  20. package/lib/process-fence.js +280 -0
  21. package/lib/providers.js +87 -0
  22. package/lib/{registry-DySMbjgW.js → registry-DCxC5Lay.js} +256 -35
  23. package/lib/registry.js +2 -2
  24. package/lib/{roots-DasWEBJk.js → roots-B-0l8mNG.js} +117 -20
  25. package/lib/sandbox-Kup4_ZIb.js +747 -0
  26. package/lib/sandbox.js +2 -1
  27. package/lib/scope.js +240 -25
  28. package/lib/session-directory-BLOstut1.js +9 -0
  29. package/lib/tool-context-CpgIFcCB.js +6 -0
  30. package/lib/types/client/RootSwitcher.d.ts +63 -0
  31. package/lib/types/client/WorkspaceFoldersAction.d.ts +41 -0
  32. package/lib/types/client/files-tree/FilesBody.module.css.d.ts +24 -0
  33. package/lib/types/client/files-tree/FilesTree.d.ts +47 -0
  34. package/lib/types/client/files-tree/adapter.d.ts +94 -0
  35. package/lib/types/client/files-tree/directory-node.d.ts +62 -0
  36. package/lib/types/client/files-tree/face.d.ts +107 -0
  37. package/lib/types/client/files-tree/locales.d.ts +10 -0
  38. package/lib/types/client/files-tree/primitives.d.ts +33 -0
  39. package/lib/types/client/files-tree/store.d.ts +81 -0
  40. package/lib/types/client/index.d.ts +13 -2
  41. package/lib/types/client/locales.d.ts +60 -0
  42. package/lib/types/client/open-in-app/OpenInAppAction.d.ts +27 -0
  43. package/lib/types/client/open-in-app/OpenTargetButton.d.ts +52 -0
  44. package/lib/types/client/open-in-app/OpenTargetButton.module.css.d.ts +29 -0
  45. package/lib/types/client/open-in-app/adapter.d.ts +46 -0
  46. package/lib/types/client/open-in-app/applications.d.ts +4 -0
  47. package/lib/types/client/open-in-app/controller.d.ts +53 -0
  48. package/lib/types/client/open-in-app/locales.d.ts +10 -0
  49. package/lib/types/client/open-in-app/open-failure-toast.d.ts +11 -0
  50. package/lib/types/client/open-in-app/open-path.d.ts +13 -0
  51. package/lib/types/client/panel-client.d.ts +2 -1
  52. package/lib/types/client/styles.d.ts +1 -1
  53. package/lib/types/command.d.ts +16 -3
  54. package/lib/types/compat/client-icons.d.ts +18 -0
  55. package/lib/types/compat/client-session.d.ts +2 -0
  56. package/lib/types/compat/dsh-version.d.ts +5 -3
  57. package/lib/types/compat/instruction-overlap.d.ts +7 -0
  58. package/lib/types/compat/open-in-app-routes.d.ts +38 -0
  59. package/lib/types/compat/process-entrypoints.d.ts +31 -0
  60. package/lib/types/compat/process-result.d.ts +14 -0
  61. package/lib/types/compat/pty-marker.d.ts +83 -0
  62. package/lib/types/compat/sandbox-confine.d.ts +2 -0
  63. package/lib/types/compat/session-directory.d.ts +4 -0
  64. package/lib/types/compat/storage-zod.d.ts +65 -0
  65. package/lib/types/compat/tool-context.d.ts +9 -0
  66. package/lib/types/compat/windows-deny-preflight.d.ts +21 -0
  67. package/lib/types/compat/windows-native.d.ts +68 -0
  68. package/lib/types/compat/windows-path-guard.d.ts +56 -0
  69. package/lib/types/compat/workspace-files.d.ts +7 -0
  70. package/lib/types/contract.d.ts +35 -4
  71. package/lib/types/dialects.d.ts +99 -3
  72. package/lib/types/fs.d.ts +32 -10
  73. package/lib/types/index.d.ts +5 -0
  74. package/lib/types/lsp.d.ts +4 -0
  75. package/lib/types/method-wrapper.d.ts +2 -0
  76. package/lib/types/process-fence.d.ts +54 -0
  77. package/lib/types/profile-isolation.d.ts +178 -0
  78. package/lib/types/providers.d.ts +70 -0
  79. package/lib/types/registry.d.ts +132 -31
  80. package/lib/types/roots.d.ts +100 -13
  81. package/lib/types/sandbox.d.ts +59 -12
  82. package/lib/types/scope.d.ts +203 -14
  83. package/lib/types/windows-broker.d.ts +17 -0
  84. package/lib/types/windows-deny.d.ts +1 -0
  85. package/lib/types/windows-profile.d.ts +10 -0
  86. package/lib/types/windows-runner-contract.d.ts +97 -0
  87. package/lib/types/windows-runner.d.ts +1 -0
  88. package/lib/types/workspace-files.d.ts +4 -0
  89. package/lib/types/workspace-routing.d.ts +11 -0
  90. package/lib/windows-runner-contract-CEfmZzu0.js +141 -0
  91. package/lib/windows-runner.js +1311 -0
  92. package/lib/workspace-files.js +203 -0
  93. package/lib/workspace-routing-YPxaQ4V3.js +26 -0
  94. package/package.json +116 -51
  95. package/scripts/windows-broker/install.ps1 +89 -0
  96. package/scripts/windows-broker/rights.cs +58 -0
  97. package/scripts/windows-broker/service.cs +323 -0
  98. package/scripts/windows-broker/verify-install.ps1 +80 -0
  99. package/lib/command-DyOWujPo.js +0 -690
  100. package/lib/sandbox-D8Fw95FM.js +0 -290
  101. 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 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ /** Extend the existing workspaceFiles namespace without replacing its codecs or read caps. */
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ export declare const inject: string[];
4
+ export declare function apply(ctx: Context): void;
@@ -0,0 +1,11 @@
1
+ /** Shared, canonical root selection for read-only workspace consumers. */
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ import type { FsTarget } from '@deepseek-ai/dsh-fs';
4
+ export interface WorkspaceRoute {
5
+ primaryRoot: string;
6
+ workspaceRoot: string;
7
+ target: FsTarget;
8
+ absolutePath: string;
9
+ }
10
+ /** Resolve relative paths against the primary cwd; never search other roots by basename. */
11
+ export declare function routeWorkspacePath(ctx: Context, primaryRoot: string, path: string, signal?: AbortSignal, cwd?: string): Promise<WorkspaceRoute | undefined>;
@@ -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 };