@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
package/lib/types/roots.d.ts
CHANGED
|
@@ -22,13 +22,23 @@
|
|
|
22
22
|
* 6. `duplicate` — another registered root canonicalizes to the same path.
|
|
23
23
|
* 7. `nested` — the candidate lies under a registered root, or contains one.
|
|
24
24
|
* Same reasoning as rule 5, applied to the additional roots.
|
|
25
|
+
* 8. `unsupported-path` — on Windows, a network or device path (`\\server`,
|
|
26
|
+
* `\\?\…`, `\\.\…`): the ACL confinement cannot be shown to cover it, so it
|
|
27
|
+
* is rejected lexically before any filesystem touch.
|
|
25
28
|
*
|
|
26
29
|
* The stored side of the same rules lives in {@link classifyStoredRoots}, which
|
|
27
30
|
* must never throw: a registration that stopped being usable is REPORTED, not
|
|
28
|
-
* deleted, so the operator can see it and remove it deliberately.
|
|
31
|
+
* deleted, so the operator can see it and remove it deliberately. It runs in
|
|
32
|
+
* two passes — every record's own structural trust and availability first,
|
|
33
|
+
* then overlap between the RECORDED boundaries of all structurally sound
|
|
34
|
+
* records — so the answer never depends on registry order, and a pair of
|
|
35
|
+
* records whose grants collide (`membership-conflict`) keeps its trustworthy
|
|
36
|
+
* negative boundary even while its membership is withheld.
|
|
29
37
|
*
|
|
30
38
|
* @module @dsh-electron/dsh-plugin-multi-root-workspace/roots
|
|
31
39
|
*/
|
|
40
|
+
import { isWindowsNetworkOrDevicePath } from './windows-runner-contract.ts';
|
|
41
|
+
export { isWindowsNetworkOrDevicePath };
|
|
32
42
|
/**
|
|
33
43
|
* Stable identity of one registered additional root. A generated uuid, never
|
|
34
44
|
* the path: a path is re-canonicalized on every read and a display alias may
|
|
@@ -44,6 +54,19 @@ export type AdditionalRootId = string & {
|
|
|
44
54
|
* @returns the branded id.
|
|
45
55
|
*/
|
|
46
56
|
export declare function additionalRootId(value: string): AdditionalRootId;
|
|
57
|
+
/**
|
|
58
|
+
* The write authority one registration carries (B9, ADR-0017).
|
|
59
|
+
*
|
|
60
|
+
* - `inherit` — the root follows the session mode: writable under
|
|
61
|
+
* `workspace-write` and unrestricted under `danger-full-access`, read-only
|
|
62
|
+
* under `read-only`. This is what legacy `recordedPath` records mean.
|
|
63
|
+
* - `read-only` — the root stays a workspace member (instructions, LSP,
|
|
64
|
+
* workspace-file routing, and reads all still see it) but a write is refused
|
|
65
|
+
* under EVERY mode, including `danger-full-access`.
|
|
66
|
+
* - `read-write` — the root is writable under EVERY mode, including a global
|
|
67
|
+
* `read-only` session; it does not widen anything beyond its own directory.
|
|
68
|
+
*/
|
|
69
|
+
export type RootAccess = 'inherit' | 'read-write' | 'read-only';
|
|
47
70
|
/** One registered additional root exactly as it is persisted. */
|
|
48
71
|
export interface RegisteredRoot {
|
|
49
72
|
/** Stable registry identity. */
|
|
@@ -51,13 +74,35 @@ export interface RegisteredRoot {
|
|
|
51
74
|
/** Canonical absolute directory (the `realpath` captured at registration). */
|
|
52
75
|
readonly path: string;
|
|
53
76
|
/**
|
|
54
|
-
* The
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* `
|
|
77
|
+
* The write authority this registration carries. In memory every record has
|
|
78
|
+
* exactly one value; on disk it is expressed by WHICH grant field is
|
|
79
|
+
* present — `recordedPath` means `inherit`, `readOnlyRecordedPath` means
|
|
80
|
+
* `read-only`, `readWriteRecordedPath` means `read-write` — never by a
|
|
81
|
+
* separate `access` key (see `registry.ts` for the downgrade safety that
|
|
82
|
+
* split buys).
|
|
83
|
+
*/
|
|
84
|
+
readonly access: RootAccess;
|
|
85
|
+
/**
|
|
86
|
+
* The canonical directory this INHERIT registration was GRANTED for: the
|
|
87
|
+
* `realpath` observed when the operator registered it (or the last time the
|
|
88
|
+
* operator restored it). Authorization is `canonicalPath(path) ===
|
|
89
|
+
* recordedPath` — a root whose current resolution moved somewhere else is
|
|
90
|
+
* reported as `redirected` and withheld, never silently granted elsewhere.
|
|
59
91
|
*/
|
|
60
92
|
readonly recordedPath?: string;
|
|
93
|
+
/**
|
|
94
|
+
* The canonical directory this READ-ONLY registration was granted for. It
|
|
95
|
+
* plays exactly the role `recordedPath` plays for an inherit record, kept
|
|
96
|
+
* under a separate key so the single persisted layout stays downgrade-safe.
|
|
97
|
+
*/
|
|
98
|
+
readonly readOnlyRecordedPath?: string;
|
|
99
|
+
/**
|
|
100
|
+
* The canonical directory this fixed READ-WRITE registration was granted
|
|
101
|
+
* for. Same role as the other two grant fields, under its own key: an older
|
|
102
|
+
* plugin strips the unknown field and classifies the record `invalid`
|
|
103
|
+
* rather than granting it as a legacy `recordedPath` write.
|
|
104
|
+
*/
|
|
105
|
+
readonly readWriteRecordedPath?: string;
|
|
61
106
|
/** Optional display alias; absent when the operator cleared it. */
|
|
62
107
|
readonly alias?: string;
|
|
63
108
|
/** ISO-8601 instant of registration. */
|
|
@@ -73,7 +118,9 @@ export interface RegisteredRoot {
|
|
|
73
118
|
* registered for (it was replaced by a symlink, or a symlink in the chain
|
|
74
119
|
* changed); kept, withheld, and `recheck` restores it once the original
|
|
75
120
|
* directory is back.
|
|
76
|
-
* - `invalid` — the record violates a rule and can only be removed.
|
|
121
|
+
* - `invalid` — the record violates a rule and can only be removed. Whether the
|
|
122
|
+
* violation also voids the record's negative boundary is told apart by
|
|
123
|
+
* {@link RootStatus.invalidReason}.
|
|
77
124
|
*/
|
|
78
125
|
export type RootState = 'available' | 'missing' | 'redirected' | 'invalid';
|
|
79
126
|
/** One registered root as the surfaces report it. */
|
|
@@ -82,6 +129,20 @@ export interface RootStatus extends RegisteredRoot {
|
|
|
82
129
|
readonly state: RootState;
|
|
83
130
|
/** Why the root is not `available`; absent otherwise. */
|
|
84
131
|
readonly detail?: string;
|
|
132
|
+
/**
|
|
133
|
+
* Distinguishes WHY an `invalid` record is unusable. Internal only: it is
|
|
134
|
+
* neither persisted nor sent over the panel wire — `state` and `detail`
|
|
135
|
+
* carry everything a surface needs.
|
|
136
|
+
*
|
|
137
|
+
* `'membership-conflict'` means the record passed every structural rule —
|
|
138
|
+
* including an intact, absolute recorded grant — but its recorded boundary
|
|
139
|
+
* overlaps another record's. Neither membership may be granted while the
|
|
140
|
+
* conflict stands, yet the boundary remains a trustworthy negative
|
|
141
|
+
* restriction, so {@link reservedRootBoundary} keeps it reserved and the
|
|
142
|
+
* registry publishes the root as a withheld, deny-only entry. Every other
|
|
143
|
+
* `invalid` verdict left the grant unverifiable, so it contributes nothing.
|
|
144
|
+
*/
|
|
145
|
+
readonly invalidReason?: 'membership-conflict';
|
|
85
146
|
}
|
|
86
147
|
/** One status together with the 1-based ordinal the surfaces accept for it. */
|
|
87
148
|
export interface IndexedRootStatus {
|
|
@@ -108,7 +169,7 @@ export type RootRef = {
|
|
|
108
169
|
readonly addedAt: string;
|
|
109
170
|
};
|
|
110
171
|
/** Every way root handling can fail, as a stable code the surfaces localize. */
|
|
111
|
-
export type RootValidationCode = 'not-absolute' | 'missing' | 'not-a-directory' | 'equals-primary' | 'primary-overlap' | 'duplicate' | 'nested' | 'invalid-alias' | 'not-found' | 'invalid-ref' | 'storage-unavailable' | 'registry-contended' | 'reveal-unavailable' | 'session-not-found';
|
|
172
|
+
export type RootValidationCode = 'not-absolute' | 'missing' | 'not-a-directory' | 'equals-primary' | 'primary-overlap' | 'duplicate' | 'nested' | 'invalid-alias' | 'not-found' | 'invalid-ref' | 'storage-unavailable' | 'registry-contended' | 'root-limit-reached' | 'config-invalid' | 'reveal-unavailable' | 'session-not-found' | 'unsupported-path' | 'root-unavailable';
|
|
112
173
|
/** A rejected root operation; `code` is the stable contract, `message` the fallback prose. */
|
|
113
174
|
export declare class RootValidationError extends Error {
|
|
114
175
|
readonly code: RootValidationCode;
|
|
@@ -163,10 +224,19 @@ export interface RootCandidateContext {
|
|
|
163
224
|
*/
|
|
164
225
|
readonly home?: string;
|
|
165
226
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
|
|
227
|
+
* The platform the rules run for; defaults to `process.platform`. A caller
|
|
228
|
+
* (a test, a probe) may exercise another platform's rules — notably the
|
|
229
|
+
* Windows network/device-path refusal — on any host.
|
|
230
|
+
*/
|
|
231
|
+
readonly platform?: NodeJS.Platform;
|
|
232
|
+
/**
|
|
233
|
+
* The recorded boundaries already reserved: {@link reservedRootBoundary}
|
|
234
|
+
* applied to every other status — available, missing, redirected, and
|
|
235
|
+
* membership-conflict records alike. A withheld root grants nothing, but
|
|
236
|
+
* its recorded boundary is the negative restriction a new writable root
|
|
237
|
+
* must not swallow while the operator has not removed it. Only a
|
|
238
|
+
* structurally invalid record contributes nothing, and the row being
|
|
239
|
+
* revived exempts only itself.
|
|
170
240
|
*/
|
|
171
241
|
readonly existing: readonly string[];
|
|
172
242
|
}
|
|
@@ -188,15 +258,32 @@ export declare function validateRootCandidate(raw: string, context: RootCandidat
|
|
|
188
258
|
* `invalid` (kept, unusable).
|
|
189
259
|
* @param primaryRoot - the session workspace root the records belong to.
|
|
190
260
|
* @param records - the stored records, in registry order.
|
|
261
|
+
* @param platform - the platform the rules run for; defaults to `process.platform`.
|
|
191
262
|
* @returns one status per record, in the same order.
|
|
192
263
|
*/
|
|
193
|
-
export declare function classifyStoredRoots(primaryRoot: string, records: readonly RegisteredRoot[]): RootStatus[];
|
|
264
|
+
export declare function classifyStoredRoots(primaryRoot: string, records: readonly RegisteredRoot[], platform?: NodeJS.Platform): RootStatus[];
|
|
194
265
|
/**
|
|
195
266
|
* The canonical roots a status list grants, in registry order.
|
|
196
267
|
* @param statuses - the statuses to project.
|
|
197
268
|
* @returns the canonical paths of every `available` root.
|
|
198
269
|
*/
|
|
199
270
|
export declare function availableRoots(statuses: readonly RootStatus[]): string[];
|
|
271
|
+
/**
|
|
272
|
+
* The recorded boundary one status still RESERVES: the grant it was
|
|
273
|
+
* registered for, which a new or revived registration may not overlap merely
|
|
274
|
+
* because the root is currently unusable.
|
|
275
|
+
*
|
|
276
|
+
* `available`, `missing`, `redirected`, and membership-conflict statuses all
|
|
277
|
+
* carry an intact, trustworthy recorded grant; every other `invalid` verdict
|
|
278
|
+
* means the grant could not be parsed at all, so it reserves — and denies —
|
|
279
|
+
* nothing. This is not an independent authorization table: it derives the
|
|
280
|
+
* boundary from the status list {@link classifyStoredRoots} produced, with
|
|
281
|
+
* the same grant-extraction semantics, so its answer can never disagree with
|
|
282
|
+
* classification.
|
|
283
|
+
* @param status - one classified registration.
|
|
284
|
+
* @returns the absolute recorded boundary, or `undefined` when there is none.
|
|
285
|
+
*/
|
|
286
|
+
export declare function reservedRootBoundary(status: RootStatus): string | undefined;
|
|
200
287
|
/**
|
|
201
288
|
* Pair every status with the 1-based ordinal the surfaces accept for it. The
|
|
202
289
|
* ordinal belongs to the POSITION, so it stays correct even for records whose
|
package/lib/types/sandbox.d.ts
CHANGED
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
*
|
|
28
28
|
* @module @dsh-electron/dsh-plugin-multi-root-workspace/sandbox
|
|
29
29
|
*/
|
|
30
|
-
import type {
|
|
30
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
31
|
+
import type { SandboxExecutionPolicy, SandboxPolicy } from '@deepseek-ai/dsh-sandbox';
|
|
31
32
|
import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local';
|
|
32
33
|
import type { Config } from '@deepseek-ai/dsh-sandbox-local';
|
|
33
34
|
import type { UpstreamConfined } from './compat/sandbox-confine.ts';
|
|
@@ -39,6 +40,7 @@ export type { Config };
|
|
|
39
40
|
*/
|
|
40
41
|
export declare class MultiRootSandboxProvider extends LocalSandboxProvider {
|
|
41
42
|
static inject: string[];
|
|
43
|
+
constructor(ctx: Context, config: Config);
|
|
42
44
|
/**
|
|
43
45
|
* Wrap `argv` so it executes confined under `policy` on this host, granting
|
|
44
46
|
* the scope's additional roots inside the SAME dialect profile.
|
|
@@ -60,17 +62,71 @@ export declare class MultiRootSandboxProvider extends LocalSandboxProvider {
|
|
|
60
62
|
*/
|
|
61
63
|
confine(argv: readonly string[], policy: SandboxPolicy, signal?: AbortSignal): UpstreamConfined;
|
|
62
64
|
/**
|
|
63
|
-
* Graft the scope's
|
|
65
|
+
* Graft the scope's effective grants and denies onto one upstream `confine`
|
|
66
|
+
* result.
|
|
64
67
|
*
|
|
65
68
|
* Pure with respect to the upstream call: it only ever reads the scope and
|
|
66
69
|
* rewrites `argv`. `enforcement`, `denialSignatures` and `runnerFailureRules`
|
|
67
70
|
* are passed through untouched, because the bash executor derives its denial
|
|
68
71
|
* and enforcement reporting from them.
|
|
72
|
+
*
|
|
73
|
+
* Mode behavior (B9, R2): `workspace-write` widens the grant with the
|
|
74
|
+
* effective writable roots AND denies any restricted root the upstream
|
|
75
|
+
* grants could reach (a `read-only` root registered under the platform
|
|
76
|
+
* temp). `read-only` grafts the fixed `read-write` roots back onto the
|
|
77
|
+
* deny-all baseline — the three-state matrix's standout cell — and then
|
|
78
|
+
* applies the SAME deny pass: a grafted writable parent must not silently
|
|
79
|
+
* swallow a denied child (Seatbelt appends the deny after the allow, bwrap
|
|
80
|
+
* adds a mandatory `--ro-bind`, Landlock / windows-acl refuse the
|
|
81
|
+
* unrepresentable subtraction). `danger-full-access` never reaches
|
|
82
|
+
* here (consumers bypass `confine`); its deny semantics live in
|
|
83
|
+
* {@link restrictedArgvPrefix}.
|
|
69
84
|
* @param confined - the upstream result for this call.
|
|
70
85
|
* @param argv - the caller's argv, needed to re-split the wrapped form.
|
|
71
86
|
* @param policy - the per-call policy.
|
|
72
|
-
* @
|
|
87
|
+
* @param scope - the authorization snapshot taken at dispatch.
|
|
88
|
+
* @returns the upstream result element for element, or one with a rewritten argv.
|
|
73
89
|
*/
|
|
74
90
|
private widenForScope;
|
|
91
|
+
/**
|
|
92
|
+
* Build the runner-prefix that confines an ambient-writable execution to
|
|
93
|
+
* everything EXCEPT `deniedRoots` — the `danger-full-access` + explicit
|
|
94
|
+
* `read-only` cell, which the shell, PTY and PTC consumers never route
|
|
95
|
+
* through `confine()` at all.
|
|
96
|
+
*
|
|
97
|
+
* The restricted profile does not depend on the callee's argv, so the prefix
|
|
98
|
+
* is computed once per call here and the synchronous `ctx.subprocess.spawn`
|
|
99
|
+
* seam only has to prepend it. The upstream profile builders stay private:
|
|
100
|
+
* this probes `confine` under `read-only` for the runner's argument skeleton
|
|
101
|
+
* and rewrites the file-effect part per dialect. Windows selects the
|
|
102
|
+
* standalone ambient deny-token runner. Landlock cannot express
|
|
103
|
+
* "everything except", so this combination selects bubblewrap; unusable
|
|
104
|
+
* runners fail without spawning an unconfined command.
|
|
105
|
+
*
|
|
106
|
+
* The return preserves the installed `confine` shape: a synchronous result
|
|
107
|
+
* on a synchronous release, a promise on an asynchronous one.
|
|
108
|
+
* @param policy - the caller's policy (mode must be `danger-full-access`).
|
|
109
|
+
* @param deniedRoots - canonical roots this execution must not write.
|
|
110
|
+
* @param signal - cancellation, forwarded to releases that accept it.
|
|
111
|
+
* @param protectedHome - diagnostics label: which deny root is the
|
|
112
|
+
* protected `$DSH_HOME`. Forwarded to the Windows runner only when it is
|
|
113
|
+
* a member of `deniedRoots`; it never changes authorization.
|
|
114
|
+
* @returns the argv prefix, ending at the `--` separator.
|
|
115
|
+
*/
|
|
116
|
+
restrictedArgvPrefix(policy: SandboxExecutionPolicy, deniedRoots: readonly string[], signal?: AbortSignal, protectedHome?: string): readonly string[] | Promise<readonly string[]>;
|
|
117
|
+
/** Full-access restriction and the actual selected backend's diagnostics. */
|
|
118
|
+
restrictedExecution(policy: SandboxExecutionPolicy, deniedRoots: readonly string[], signal?: AbortSignal, protectedHome?: string): UpstreamConfined;
|
|
119
|
+
/**
|
|
120
|
+
* Limit an execution with unknown registry authority to the primary root.
|
|
121
|
+
* Shared temp grants are unsafe: another process may have registered a
|
|
122
|
+
* read-only root beneath them. Harvest the upstream read-only skeleton and
|
|
123
|
+
* graft only the primary root, retaining diagnostics and the sync/async shape.
|
|
124
|
+
* Explicit denies still win; an unrepresentable overlap refuses launch.
|
|
125
|
+
* @param policy - the caller's execution policy.
|
|
126
|
+
* @param deniedRoots - known negative boundaries, including protected home.
|
|
127
|
+
* @param signal - cancellation forwarded to upstream.
|
|
128
|
+
* @returns the confined prefix ending at `--`, without the caller's command.
|
|
129
|
+
*/
|
|
130
|
+
clampedExecution(policy: SandboxExecutionPolicy, deniedRoots: readonly string[], signal?: AbortSignal): UpstreamConfined;
|
|
75
131
|
}
|
|
76
132
|
export default MultiRootSandboxProvider;
|
package/lib/types/scope.d.ts
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
* @module @dsh-electron/dsh-plugin-multi-root-workspace/scope
|
|
17
17
|
*/
|
|
18
18
|
import { Context, Service } from '@deepseek-ai/cordis';
|
|
19
|
-
import type { SandboxExecutionPolicy } from '@deepseek-ai/dsh-sandbox';
|
|
19
|
+
import type { SandboxExecutionPolicy, SandboxMode } from '@deepseek-ai/dsh-sandbox';
|
|
20
|
+
import type { RootAccess } from './roots.ts';
|
|
20
21
|
declare module '@deepseek-ai/cordis' {
|
|
21
22
|
interface Context {
|
|
22
23
|
multiRootScope: MultiRootScopeService;
|
|
@@ -37,19 +38,82 @@ export interface AdditionalWorkspaceRoot {
|
|
|
37
38
|
* ever registered.
|
|
38
39
|
*/
|
|
39
40
|
recordedPath: string;
|
|
41
|
+
/**
|
|
42
|
+
* The write authority the registration carries (B9 three-state).
|
|
43
|
+
* `read-only` roots remain workspace members — instructions, LSP,
|
|
44
|
+
* workspace-file routing, and reads all still see them — but they are never
|
|
45
|
+
* part of `writableRoots` and never reach a write grant, under ANY mode.
|
|
46
|
+
* `inherit` follows the session mode; fixed `read-write` stays writable even
|
|
47
|
+
* under a read-only session.
|
|
48
|
+
*/
|
|
49
|
+
access: RootAccess;
|
|
50
|
+
/** Registry withheld the positive grant; retain its recorded deny boundary. */
|
|
51
|
+
withheld?: boolean;
|
|
40
52
|
/** Optional display alias. */
|
|
41
53
|
alias?: string;
|
|
42
54
|
}
|
|
55
|
+
/** One granted workspace member, with the authority fact the enforcement reads. */
|
|
56
|
+
export interface ScopeRoot {
|
|
57
|
+
/** Canonical absolute directory. */
|
|
58
|
+
readonly path: string;
|
|
59
|
+
/** The write authority the registration carries. */
|
|
60
|
+
readonly access: RootAccess;
|
|
61
|
+
/** Optional display alias. */
|
|
62
|
+
readonly alias?: string;
|
|
63
|
+
/** Stable registry identity. */
|
|
64
|
+
readonly id: string;
|
|
65
|
+
}
|
|
43
66
|
/**
|
|
44
67
|
* The resolved scope of one confined call. `primaryRoot` equals the policy's
|
|
45
|
-
* workspace root; `additionalRoots` are canonical, deduplicated, never equal
|
|
46
|
-
* the primary root
|
|
68
|
+
* workspace root; `additionalRoots` are canonical, deduplicated, never equal
|
|
69
|
+
* to the primary root.
|
|
47
70
|
*/
|
|
48
71
|
export interface FilesystemScope {
|
|
49
72
|
/** The session's workspace root (`session.header.cwd`, canonical). */
|
|
50
73
|
primaryRoot: string;
|
|
51
|
-
/**
|
|
74
|
+
/** Every granted member — ANY access level — canonical, in registry order. */
|
|
52
75
|
additionalRoots: readonly string[];
|
|
76
|
+
/** The granted members with their access, in registry order. */
|
|
77
|
+
roots: readonly ScopeRoot[];
|
|
78
|
+
/**
|
|
79
|
+
* The members a write may reach under THIS call — the effective access
|
|
80
|
+
* (registration policy × session mode) narrowed by the tool cap.
|
|
81
|
+
*/
|
|
82
|
+
writableRoots: readonly string[];
|
|
83
|
+
/**
|
|
84
|
+
* Registered boundaries THIS call must not write, including missing and
|
|
85
|
+
* redirected registrations. Negative restrictions never create a grant.
|
|
86
|
+
* It is the deny set enforcement reads: explicit
|
|
87
|
+
* `read-only` roots under `danger-full-access` (and wherever another grant,
|
|
88
|
+
* such as the platform temp area, would otherwise cover the same path), and
|
|
89
|
+
* every member when the call is tool-capped. Read access is unaffected —
|
|
90
|
+
* these are all still workspace members.
|
|
91
|
+
*/
|
|
92
|
+
deniedRoots: readonly string[];
|
|
93
|
+
/**
|
|
94
|
+
* Whether this process holds the registry authority for the store. While it
|
|
95
|
+
* is `unavailable` — contended, storage-failed, config-invalid, or torn
|
|
96
|
+
* down — the empty root table is NOT evidence that nothing is restricted:
|
|
97
|
+
* the registrations this process cannot see may carry explicit `read-only`
|
|
98
|
+
* boundaries, so both writable modes limit writes to the primary root
|
|
99
|
+
* instead of trusting the empty deny set.
|
|
100
|
+
* A composition with no registry at all never reports `unavailable` — it can
|
|
101
|
+
* never have registered roots.
|
|
102
|
+
*/
|
|
103
|
+
authority: 'available' | 'unavailable';
|
|
104
|
+
/**
|
|
105
|
+
* The canonical DSH home directory, present exactly when this scope appended
|
|
106
|
+
* it to `deniedRoots` — i.e. under `danger-full-access` while restrictions
|
|
107
|
+
* apply (a non-empty deny set or an unavailable authority). `$DSH_HOME`
|
|
108
|
+
* holds the store, the lease, the profiles and the user's own config: an
|
|
109
|
+
* ambient Full-access process must not edit them, so once the workspace
|
|
110
|
+
* carries ANY deny boundary the home joins it. Zero additional roots with a
|
|
111
|
+
* healthy authority never set this — the empty scope stays upstream-exact.
|
|
112
|
+
* Never set under `workspace-write` or `read-only`: those modes already
|
|
113
|
+
* confine writes by containment, so denying home adds nothing but the cost
|
|
114
|
+
* of a stale `FsTarget` re-resolution on every write.
|
|
115
|
+
*/
|
|
116
|
+
protectedHome?: string;
|
|
53
117
|
}
|
|
54
118
|
/**
|
|
55
119
|
* Canonicalize and sanitize one root list against a primary root: drop the
|
|
@@ -76,15 +140,65 @@ export declare function sanitizeAdditionalRoots(primaryRoot: string, roots: read
|
|
|
76
140
|
*/
|
|
77
141
|
export declare const MULTI_ROOT_CONTEXT_NAME = "multi-root:scope";
|
|
78
142
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
*
|
|
143
|
+
* The deterministic sentence the model hears while the registry authority is
|
|
144
|
+
* unavailable under either writable mode — enforcement is then the
|
|
145
|
+
* primary-root-only one, which the prompt must not leave implicit.
|
|
146
|
+
*/
|
|
147
|
+
export declare const REGISTRY_UNAVAILABLE_NOTE: string;
|
|
148
|
+
/**
|
|
149
|
+
* Render the workspace topology the model is told about. Pure: the text
|
|
150
|
+
* depends only on its inputs, so two consecutive assemblies of the same
|
|
151
|
+
* session state produce the same contribution byte for byte. Only granted
|
|
152
|
+
* members are ever listed — withheld, missing, or invalid roots never reach
|
|
153
|
+
* `roots` in the first place — and no host internals (ids, SIDs, store or
|
|
154
|
+
* lease paths) are ever named.
|
|
155
|
+
* @param options.platform - the host platform (`process.platform` of the contribution).
|
|
156
|
+
* @param options.mode - the session's sandbox mode.
|
|
157
|
+
* @param options.primaryRoot - the canonical session workspace root.
|
|
158
|
+
* @param options.roots - the granted additional members, in registry order.
|
|
159
|
+
* @param options.cappedTools - sorted tool names that can never write an additional root.
|
|
160
|
+
* @param options.toolAccessInvalid - invalid host config caps every additional-root write.
|
|
161
|
+
* @param options.authority - the registry-authority fact; `unavailable` under
|
|
162
|
+
* `danger-full-access` and `workspace-write` limit writes to the primary root, which the
|
|
163
|
+
* model must hear even when there is no topology to describe.
|
|
164
|
+
* @param options.protectedHome - the canonical DSH home when the scope appended
|
|
165
|
+
* it to the deny set (`danger-full-access` with restrictions); stated by path
|
|
166
|
+
* so the model can place the refusal.
|
|
167
|
+
* @returns the stable topology text; empty when there are no additional roots
|
|
168
|
+
* and no downgrade to report.
|
|
86
169
|
*/
|
|
87
|
-
export declare function renderWorkspaceRootsContext(
|
|
170
|
+
export declare function renderWorkspaceRootsContext(options: {
|
|
171
|
+
readonly platform: string;
|
|
172
|
+
readonly mode: SandboxMode;
|
|
173
|
+
readonly primaryRoot: string;
|
|
174
|
+
readonly roots: readonly ScopeRoot[];
|
|
175
|
+
readonly cappedTools: readonly string[];
|
|
176
|
+
readonly toolAccessInvalid?: boolean;
|
|
177
|
+
readonly authority?: 'available' | 'unavailable';
|
|
178
|
+
readonly protectedHome?: string;
|
|
179
|
+
}): string;
|
|
180
|
+
/**
|
|
181
|
+
* Plugin configuration for the `multi-root-scope` row.
|
|
182
|
+
*/
|
|
183
|
+
export interface Config {
|
|
184
|
+
/**
|
|
185
|
+
* Per-tool write caps: a listed tool may never write ANY additional root
|
|
186
|
+
* (the primary root and upstream behaviour are unchanged). `read-only` is
|
|
187
|
+
* the only legal value — a cap restricts, it can never widen past the
|
|
188
|
+
* root's own access.
|
|
189
|
+
*/
|
|
190
|
+
readonly toolAccess?: Record<string, 'read-only'>;
|
|
191
|
+
/**
|
|
192
|
+
* The resolved DSH home directory (`$DSH_HOME` / `~/.dsh`), which the patch
|
|
193
|
+
* row fills from `dshHomePath()`. Under `danger-full-access` it joins the
|
|
194
|
+
* deny set exactly while restrictions apply — a non-empty derived deny set
|
|
195
|
+
* or an unavailable registry authority — so an ambient agent process cannot
|
|
196
|
+
* overwrite the store, the lease file, profiles, or the user's own config.
|
|
197
|
+
* Absent or invalid (non-string, empty, relative) means the boundary simply
|
|
198
|
+
* does not exist: bare test compositions carry no home.
|
|
199
|
+
*/
|
|
200
|
+
readonly dshHome?: string;
|
|
201
|
+
}
|
|
88
202
|
/**
|
|
89
203
|
* The `ctx.multiRootScope` service. Providers ask it — never the filesystem —
|
|
90
204
|
* what the current scope is, which is what keeps the fs fence and every kernel
|
|
@@ -94,9 +208,37 @@ export declare class MultiRootScopeService extends Service {
|
|
|
94
208
|
/** Additional roots keyed by canonical primary root; empty until roots are registered. */
|
|
95
209
|
private readonly rootsByPrimary;
|
|
96
210
|
private readonly listeners;
|
|
211
|
+
/**
|
|
212
|
+
* The registry-authority fact the registry row publishes. Defaults to
|
|
213
|
+
* `available`: a composition without a registry row can never have
|
|
214
|
+
* registered roots, so nothing is unknown there. The registry sets it false
|
|
215
|
+
* the moment it cannot prove the store is its own — before the lease is
|
|
216
|
+
* acquired, while contended or storage-failed, and at teardown — and true
|
|
217
|
+
* only after the accepted records have been published.
|
|
218
|
+
*/
|
|
219
|
+
private registryAuthority;
|
|
220
|
+
/** Validated `toolAccess` row config; empty when no tool is capped. */
|
|
221
|
+
private readonly caps;
|
|
222
|
+
/** A malformed `toolAccess` refuses every additional-root write (fail closed). */
|
|
223
|
+
private readonly capsInvalid;
|
|
224
|
+
/**
|
|
225
|
+
* The configured DSH home boundary; `undefined` when the row carries no
|
|
226
|
+
* `dshHome` (bare test compositions) or one that is not an absolute string —
|
|
227
|
+
* an invalid value is logged at construction and ignored rather than
|
|
228
|
+
* treated as evidence.
|
|
229
|
+
*/
|
|
230
|
+
private readonly dshHome;
|
|
97
231
|
/** Observe registry publications; consumers still resolve authority from scopeOf(). */
|
|
98
232
|
subscribe(listener: () => void): () => void;
|
|
99
|
-
|
|
233
|
+
/**
|
|
234
|
+
* The sorted names of the tools `toolAccess` caps — read-only for the
|
|
235
|
+
* prompt contribution. An invalid config is not enumerated (it caps every
|
|
236
|
+
* call but is not a listable tool set).
|
|
237
|
+
*/
|
|
238
|
+
toolCaps(): readonly string[];
|
|
239
|
+
/** Whether malformed toolAccess caps every call, including unknown tools. */
|
|
240
|
+
get toolAccessInvalid(): boolean;
|
|
241
|
+
constructor(ctx: Context, config?: Config);
|
|
100
242
|
/**
|
|
101
243
|
* Resolve the scope of one confined call.
|
|
102
244
|
* @param policy - the upstream policy whose workspace root is the primary root.
|
|
@@ -104,11 +246,45 @@ export declare class MultiRootScopeService extends Service {
|
|
|
104
246
|
*/
|
|
105
247
|
resolve(policy: SandboxExecutionPolicy): FilesystemScope;
|
|
106
248
|
/**
|
|
107
|
-
*
|
|
249
|
+
* Whether THIS call's additional-root writes are capped. Inside a tool
|
|
250
|
+
* dispatch the AsyncLocalStorage store answers; outside any dispatch (user
|
|
251
|
+
* PTY, panel, an unknown async boundary) the answer is fail-closed: capped
|
|
252
|
+
* whenever `toolAccess` names any tool, because an operation whose tool
|
|
253
|
+
* identity is unknown gets the most restrictive configured cap. Membership
|
|
254
|
+
* is never affected — only writability.
|
|
255
|
+
*/
|
|
256
|
+
private capped;
|
|
257
|
+
/**
|
|
258
|
+
* The additional roots registered for one canonical primary root — every
|
|
259
|
+
* granted member, ANY access level (a read-only root is still a member of
|
|
260
|
+
* the workspace for instructions, LSP, routing, and reads).
|
|
108
261
|
* @param primaryRoot - canonical primary root (or any spelling thereof).
|
|
109
262
|
* @returns the canonical additional roots; empty when none are registered.
|
|
110
263
|
*/
|
|
111
264
|
scopeOf(primaryRoot: string): readonly string[];
|
|
265
|
+
/**
|
|
266
|
+
* The additional roots a WRITE may reach for one primary root under
|
|
267
|
+
* `workspace-write` — the members whose effective access is writable there
|
|
268
|
+
* (`inherit` and fixed `read-write`). Tool caps are per-call and do not
|
|
269
|
+
* apply to this diagnostic projection.
|
|
270
|
+
* @param primaryRoot - canonical primary root (or any spelling thereof).
|
|
271
|
+
* @returns the canonical writable roots; empty when none qualify.
|
|
272
|
+
*/
|
|
273
|
+
writableOf(primaryRoot: string): readonly string[];
|
|
274
|
+
/**
|
|
275
|
+
* Whether ANY registered primary currently publishes an explicit `read-only`
|
|
276
|
+
* member. The PTY seam resolves its caller through the agent-PTY
|
|
277
|
+
* environment markers; when that lookup fails this flag is the difference
|
|
278
|
+
* between pass-through (nothing to deny anywhere on the store) and fail
|
|
279
|
+
* closed. An unavailable registry authority answers `true` by itself: a
|
|
280
|
+
* terminal whose session cannot be resolved must not inherit a deny set
|
|
281
|
+
* this process cannot see.
|
|
282
|
+
* @returns whether at least one primary holds a restricted root, or the
|
|
283
|
+
* registry cannot prove none does.
|
|
284
|
+
*/
|
|
285
|
+
hasActiveDenyRoots(): boolean;
|
|
286
|
+
/** The sanitized members of one primary root, in registry order. */
|
|
287
|
+
private membersOf;
|
|
112
288
|
/**
|
|
113
289
|
* Replace the roots registered for one primary root.
|
|
114
290
|
*
|
|
@@ -119,5 +295,15 @@ export declare class MultiRootScopeService extends Service {
|
|
|
119
295
|
* @param roots - the additional roots, in display order.
|
|
120
296
|
*/
|
|
121
297
|
setAdditionalRoots(primaryRoot: string, roots: readonly AdditionalWorkspaceRoot[]): void;
|
|
298
|
+
/**
|
|
299
|
+
* Publish the registry-authority fact. This is the REGISTRY's write port —
|
|
300
|
+
* the counterpart of {@link setAdditionalRoots}: the registry marks the
|
|
301
|
+
* scope `unavailable` the moment it stops being the store's authority
|
|
302
|
+
* (before acquisition, on contention or storage failure, at teardown) and
|
|
303
|
+
* `available` only once its stored records have been published. Nothing
|
|
304
|
+
* else may call it, and it never touches the root table.
|
|
305
|
+
* @param available - whether this process holds the registry authority.
|
|
306
|
+
*/
|
|
307
|
+
setRegistryAuthority(available: boolean): void;
|
|
122
308
|
}
|
|
123
309
|
export default MultiRootScopeService;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare function brokerPipeName(userSid: string): string;
|
|
2
|
+
/** The broker must acknowledge before any ACL edits or process creation. The
|
|
3
|
+
* caller independently reads the deny-only groups from the token;
|
|
4
|
+
* even an impersonated pipe server cannot fabricate that kernel state.
|
|
5
|
+
*/
|
|
6
|
+
export declare function requestDenyToken(userSid: string, tokenHandle: bigint, deniedIds: readonly string[]): Promise<bigint>;
|
|
7
|
+
/** Synchronous, bounded RPC inside the public suspended-create binding. The
|
|
8
|
+
* caller verifies the child's kernel token before the helper can resume it.
|
|
9
|
+
*/
|
|
10
|
+
export declare function assignDenyToken(userSid: string, processHandle: bigint, threadHandle: bigint, deniedIds: readonly string[], io: {
|
|
11
|
+
open(pipe: string): unknown;
|
|
12
|
+
write(pipe: unknown, bytes: Buffer): void;
|
|
13
|
+
available(pipe: unknown): number;
|
|
14
|
+
read(pipe: unknown, count: number): Buffer;
|
|
15
|
+
pause(): void;
|
|
16
|
+
close(pipe: unknown): void;
|
|
17
|
+
}): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runWindowsDenied(upstreamRunner: string, roots: readonly string[], argv: readonly string[], protectedHome?: string): Promise<number>;
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/** Strictly recognize the upstream public runner contract, failing closed on new flags. */
|
|
12
|
-
export declare function parseWindowsProfile(args: readonly string[], allowAdditional?: boolean): WindowsProfile;
|
|
13
|
-
export declare function widenWindowsProfile(profile: readonly string[], roots: readonly string[], primaryRoot?: string): string[];
|
|
1
|
+
export { parseWindowsProfile, workspaceScopeSid, type WindowsProfile } from './windows-runner-contract.ts';
|
|
2
|
+
/** Recognize the upstream runner, then select our ambient deny-token path.
|
|
3
|
+
*
|
|
4
|
+
* `protectedHome` is a diagnostics label only: when it names one of the deny
|
|
5
|
+
* roots it is forwarded as `--protected-home` so the runner can word its
|
|
6
|
+
* hardlink/scan remediation for the protected `$DSH_HOME`. It never changes
|
|
7
|
+
* which roots are denied or how.
|
|
8
|
+
*/
|
|
9
|
+
export declare function restrictWindowsProfile(profile: readonly string[], deniedRoots: readonly string[], protectedHome?: string): string[];
|
|
10
|
+
export declare function widenWindowsProfile(profile: readonly string[], roots: readonly string[], deniedRoots: readonly string[], primaryRoot?: string): string[];
|