@argszero/cordis-plugin-sandbox-grant-advisor 0.9.0 → 0.10.0
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/README.md +134 -6
- package/cordis.patch.yml +71 -9
- package/lib/advice.js +150 -11
- package/lib/types/advice.d.ts +52 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -44,6 +44,11 @@ inherited `Authenticated Users: Modify` is the whole of their access). In each o
|
|
|
44
44
|
every sandboxed command fails the same way, before it runs, and the error names
|
|
45
45
|
neither the missing right nor a remedy.
|
|
46
46
|
|
|
47
|
+
Two further reports — [discussion #8312] and [discussion #8314] — describe the
|
|
48
|
+
other end of the same backend: what its grant leaves behind *after* it applies.
|
|
49
|
+
The advisory states that too (see [What the grant leaves behind](#what-the-grant-leaves-behind-added-in-0100)),
|
|
50
|
+
because the advisory is the thing handing over the command that applies the grant.
|
|
51
|
+
|
|
47
52
|
`#8232` contributes two facts about the *shape* of the failure rather than its
|
|
48
53
|
cause, and both are in the advisory now. One is that the failure belongs to the
|
|
49
54
|
**workspace, not the command**: there a `Get-Date` failed exactly like anything
|
|
@@ -229,7 +234,27 @@ What will NOT fix it on its own — both look like the right move, and both were
|
|
|
229
234
|
makes you the owner, and ownership's implicit rights are READ_CONTROL and WRITE_DAC only — so it
|
|
230
235
|
supplies the DACL half and still not WRITE_OWNER, the right this call needs.
|
|
231
236
|
icacls "D:\ws" /reset /T /C
|
|
232
|
-
restores inheritance, and inheritance is what supplied the Modify-only ACE above.
|
|
237
|
+
restores inheritance, and inheritance is what supplied the Modify-only ACE above. Where it strips the last
|
|
238
|
+
entry naming you, the directory ends up in exactly the state the diagnosis above describes — its only access
|
|
239
|
+
the inherited `Authenticated Users:(M)` (#8314 measured that follow-on failure).
|
|
240
|
+
|
|
241
|
+
What the grant leaves behind, once it applies — worth knowing before you run the command above, because the
|
|
242
|
+
backend does not take it back:
|
|
243
|
+
The three entries are STANDING, deliberately, and nothing revokes them. ... They outlive the session and the
|
|
244
|
+
harness exiting (`sandbox-windows-acl/src/grant.ts`, `src/index.ts`).
|
|
245
|
+
The Low integrity label is INHERITABLE (`(OI|CI)`) and it lives in the SACL — which is why the `icacls /reset`
|
|
246
|
+
above does not take it off: that command rebuilds the DACL. Windows starts a process at the minimum of the
|
|
247
|
+
user's and the program's integrity, so anything started from a tree the harness has written to runs at LOW
|
|
248
|
+
integrity, and none of the symptoms names DSH (#8312 collects them): ... (#7709), ... (#8175), ... (#7735).
|
|
249
|
+
It can also leave the workspace. An NTFS hard link is a SECOND NAME for one file object, so both names share
|
|
250
|
+
one security descriptor — and a pnpm workspace is largely hard links (`node_modules` pointing into a
|
|
251
|
+
content-addressed store on the same volume). ... `vite build` unable to remove its own temp file, `pnpm
|
|
252
|
+
install` unable to replace a hook (#8314 measured the whole chain). ...
|
|
253
|
+
It is also why the label is not simply removable here: ... This advisory hands over no removal command: the
|
|
254
|
+
maintainers' own skill does not, and an unverified one would be the defect this plugin exists to answer.
|
|
255
|
+
None of this makes the command above the wrong move — without it, nothing sandboxed runs in this workspace. It
|
|
256
|
+
is what the harness does to a directory it has been pointed at, and it is worth knowing before rather than
|
|
257
|
+
discovering it as a broken build in some other project later.
|
|
233
258
|
|
|
234
259
|
One thing to know before asking for a weaker grant, because that is the next idea after this diagnosis —
|
|
235
260
|
and it is not a smaller version of the same grant:
|
|
@@ -256,6 +281,54 @@ unconditional one-liner was printed, with a sentence noting it assumed
|
|
|
256
281
|
ownership; that would have sent the second environment to a command that is
|
|
257
282
|
denied — the same defect this plugin exists to answer.
|
|
258
283
|
|
|
284
|
+
#### What the grant leaves behind (added in 0.10.0)
|
|
285
|
+
|
|
286
|
+
[Discussion #8312] and [discussion #8314] report the other half of this backend,
|
|
287
|
+
and the advisory now states it — before the reader runs the command it is being
|
|
288
|
+
handed, because that command is what makes the backend's grant apply:
|
|
289
|
+
|
|
290
|
+
- **The three entries are standing, by design, and nothing revokes them.** The
|
|
291
|
+
workspace grant is a *reuse cache*: the backend's dispose path revokes the
|
|
292
|
+
revocable (temp) grants and leaves the workspace edits, and its fail-closed
|
|
293
|
+
cleanup says the same in plainer words — standing ACEs "are NOT revoked — they
|
|
294
|
+
are the intended end state (the reuse cache), not an error artifact"
|
|
295
|
+
(`src/grant.ts`, `src/index.ts`). They outlive the session and the harness
|
|
296
|
+
exiting. ([#8312] arrived at this from the README's own description of the
|
|
297
|
+
cache; the source is quoted in the advisory.)
|
|
298
|
+
- **The Low integrity label is inheritable, and it lives in the SACL.** That is
|
|
299
|
+
why `icacls /reset` — already listed as a non-fix for a different reason — does
|
|
300
|
+
not remove it: the command rebuilds the DACL. Windows starts a process at
|
|
301
|
+
`min(user, image)` integrity, so anything started from a tree the harness has
|
|
302
|
+
written to runs at Low integrity, and none of the symptoms points at DSH:
|
|
303
|
+
an Electron/Chromium app exiting `0x80000003` with no output ([#7709]),
|
|
304
|
+
msbuild / dotnet / npm refusing or warning about the files as if they came from
|
|
305
|
+
the Internet when no `Zone.Identifier` exists ([#8175]), a double-clicked
|
|
306
|
+
`.exe` / `.cmd` reporting "publisher could not be verified" ([#7735]).
|
|
307
|
+
- **It can leave the workspace.** An NTFS hard link is a second name for one
|
|
308
|
+
file object, so both names share one security descriptor — and a pnpm workspace
|
|
309
|
+
is largely hard links (`node_modules` pointing into a content-addressed store
|
|
310
|
+
on the same volume). The inheritable label therefore lands on the *store's*
|
|
311
|
+
objects and stays there, after which every project building from that store
|
|
312
|
+
gets executables that start at Low integrity and failures that name the build
|
|
313
|
+
tool ([#8314] measured the whole chain: `vite build` unable to remove its own
|
|
314
|
+
temp file, `pnpm install` unable to replace a hook). The backend's own suite
|
|
315
|
+
pins the reach as a known boundary — *"a workspace hard link lets the grant
|
|
316
|
+
reach an external file object"* (`tests/runner.spec.ts`) — and its README calls
|
|
317
|
+
refusing multiply-linked files unviable for ordinary pnpm installs, which
|
|
318
|
+
leaves the out-of-tree reach open rather than unknown.
|
|
319
|
+
- **No removal command is shipped.** The maintainers' own
|
|
320
|
+
`diagnose-windows-sandbox-acl` skill *reports* `LOW_LABEL` and by design does
|
|
321
|
+
not remove it, and removing an integrity label needs `WRITE_OWNER` — the same
|
|
322
|
+
right this whole failure is about. This project has no Windows host to verify a
|
|
323
|
+
line on, so shipping one would be exactly the defect the rest of this module
|
|
324
|
+
exists to answer; the section says where it stops instead.
|
|
325
|
+
|
|
326
|
+
The section is emitted for **every** ACL class, like the version boundary and for
|
|
327
|
+
the same reason: it is a fact about the package's grant, and that grant is the
|
|
328
|
+
remedy the advisory hands over in all three classes. It also closes with what the
|
|
329
|
+
fact does *not* mean — the command is still the right move, because without it
|
|
330
|
+
nothing sandboxed runs at all.
|
|
331
|
+
|
|
259
332
|
**What is deliberately *not* shipped**: `icacls ... /grant "<user>:(OI)(CI)(WD,WO)"`,
|
|
260
333
|
the two needed rights named explicitly. It is the tighter form and it is
|
|
261
334
|
plausibly correct syntax, but this project has no Windows host to run it on, and
|
|
@@ -287,6 +360,27 @@ built with the **resolved** mode the failing call actually ran under — from
|
|
|
287
360
|
`ctx.sandboxPolicy.resolve({ session })`, the same resolver the terminal layer
|
|
288
361
|
calls before spawning, with the same session.
|
|
289
362
|
|
|
363
|
+
**Inside a confining mode, the host binary decides** (added in 0.10.0).
|
|
364
|
+
[Discussion #8322] ran the control one level deeper — same runner, same ConPTY,
|
|
365
|
+
every arm — and separated what the mode alone does not: with the runner hosted by
|
|
366
|
+
a plain console-subsystem `node.exe`, the confined shell starts and its prompt and
|
|
367
|
+
shell-integration marks are correct; with the runner hosted by the packaged
|
|
368
|
+
desktop's GUI-subsystem Electron executable, the child dies **silently** — zero
|
|
369
|
+
bytes on stdout *and* stderr, and the non-interactive arm exits 0 with everything
|
|
370
|
+
it printed lost. It is the same rule the `0xC0000142` family states for its own
|
|
371
|
+
case: under the restricted token a console can be **inherited but not created**,
|
|
372
|
+
so the binary that owns one (or owns none) is what the arms turn on. That is also
|
|
373
|
+
why the same version behaves differently depending on how it was started: the
|
|
374
|
+
desktop app fails where the Web UI launched from a terminal — whose
|
|
375
|
+
`process.execPath` is a real `node.exe` — is reported working under the same
|
|
376
|
+
confining mode ([#8313], whose sibling report is the `0xC0000142` shape of the
|
|
377
|
+
same host difference). The advisory therefore names the host alongside the mode,
|
|
378
|
+
says the outcome is **deterministic per (session mode × host)** rather than
|
|
379
|
+
intermittent, and adds that the mode which counts is the one the **session
|
|
380
|
+
records**, not the one the environment now holds — a session that recorded the
|
|
381
|
+
confining mode keeps failing after the app is restarted with another mode in its
|
|
382
|
+
environment, while switching it inside the session takes effect at once.
|
|
383
|
+
|
|
290
384
|
The advisory that follows is addressed to **two different readers**:
|
|
291
385
|
|
|
292
386
|
```
|
|
@@ -299,6 +393,15 @@ The `bash` tool is a PERSISTENT PTY session (a shell that stays alive between ca
|
|
|
299
393
|
`workspace-write` — not `danger-full-access`. A confining mode spawns the shell through the sandbox, and there the
|
|
300
394
|
terminal backend cannot create the pseudo-console at all, so the child exits before its first prompt. ...
|
|
301
395
|
|
|
396
|
+
Which sessions fail inside that combination is not chance — it is deterministic per (session mode × the host
|
|
397
|
+
binary carrying the sandbox runner) ... Measured against the desktop build with the same runner and the same
|
|
398
|
+
ConPTY in every arm (#8322):
|
|
399
|
+
- runner hosted by a plain console-subsystem `node.exe` → the confined shell starts ...;
|
|
400
|
+
- runner hosted by the packaged desktop's GUI-subsystem Electron executable ... → the child dies silently ...
|
|
401
|
+
The rule behind both this and the `0xC0000142` family ... is the one stated there for its own case: under the
|
|
402
|
+
restricted token a console can be INHERITED but not CREATED ... And the mode that decides is the one the SESSION
|
|
403
|
+
records, not the one the environment now holds ...
|
|
404
|
+
|
|
302
405
|
Do NOT retry, and do not look for a command that fixes it: every attempt will fail identically, and there is no
|
|
303
406
|
shell to run a command in. Use your file read/write tools instead, and hand the choice below to the user.
|
|
304
407
|
|
|
@@ -309,8 +412,11 @@ What unblocks the session — the user's decision, not the model's:
|
|
|
309
412
|
`$DSH_HOME/cordis.patch.yml` for every profile — replacing its `persistent-shell` group with
|
|
310
413
|
`@deepseek-ai/dsh-tool-pwsh` (a one-shot subprocess, no PTY); the patch layer is yours, so an upgrade
|
|
311
414
|
will not overwrite it; or
|
|
312
|
-
3. run the session
|
|
313
|
-
|
|
415
|
+
3. run the session from a host that owns a console instead of the packaged desktop app — the Web UI started
|
|
416
|
+
from a terminal (`process.execPath` is a real `node.exe` there) was reported working under the same
|
|
417
|
+
confining mode and the same version (#8313); or
|
|
418
|
+
4. run the session with `danger-full-access`, which drops the very confinement the sandbox exists to give.
|
|
419
|
+
Prefer 1 to 3.
|
|
314
420
|
```
|
|
315
421
|
|
|
316
422
|
The model's instruction is to **stop** — not to run a command (there is no shell
|
|
@@ -331,9 +437,10 @@ the advisory never names the dead directory.
|
|
|
331
437
|
|
|
332
438
|
### 3. A confined child that never started (`native-init`)
|
|
333
439
|
|
|
334
|
-
|
|
335
|
-
app)
|
|
336
|
-
the
|
|
440
|
+
Five reports of one exit code: [`#7876`] and [`#8193`] (the packaged desktop
|
|
441
|
+
app), [`#8313`] (the same version and the same mode run as the desktop app versus
|
|
442
|
+
the Web UI launched from a terminal, which works) and [`#7877`] (MSYS2 / Git
|
|
443
|
+
Bash) — and [`#8208`], which found the mechanism the console cases share. All are `0xC0000142`
|
|
337
444
|
`STATUS_DLL_INIT_FAILED` — the Windows
|
|
338
445
|
loader terminated the process while it was initializing its native images, i.e.
|
|
339
446
|
**before the program's entry point**. A command that ran and then failed exits
|
|
@@ -648,6 +755,17 @@ than one that stays silent.
|
|
|
648
755
|
one-line experiment and the reporter's own control, and both advisories say
|
|
649
756
|
where they stop. The native-init family is the one that needs no Windows to be
|
|
650
757
|
faithful, because what it reads is a number inside a JSON value.
|
|
758
|
+
- **The standing-grant section is source-level, and the out-of-tree reach is the
|
|
759
|
+
reporter's measurement.** What this section states about the backend's own
|
|
760
|
+
behaviour — that the workspace grant is standing, that nothing in the dispose or
|
|
761
|
+
fail-closed path revokes it, that the Low label is inheritable and lives in the
|
|
762
|
+
SACL — is read off the shipped source and the backend's own suite, and is quoted
|
|
763
|
+
as such. What it states about a hard link carrying the label onto a
|
|
764
|
+
content-addressed store is [#8314]'s measurement on their machine, which is why
|
|
765
|
+
the advisory attributes it instead of asserting it as a property of every
|
|
766
|
+
workspace, and why **no removal command is shipped**: this project has no
|
|
767
|
+
Windows host on which to verify one, and an unverified removal command is the
|
|
768
|
+
same defect this plugin exists to answer.
|
|
651
769
|
- **It repairs nothing and elevates nothing.** If the directory really is
|
|
652
770
|
Full-control for the caller, the remaining ACL hypothesis is
|
|
653
771
|
`SeSecurityPrivilege` — i.e. the backend's documented prerequisite would be
|
|
@@ -778,3 +896,13 @@ the current runtime cannot distinguish rather than counting it as a pass.
|
|
|
778
896
|
[#8193]: https://github.com/deepseek-ai/deepseek-harness/discussions/8193
|
|
779
897
|
[#8174]: https://github.com/deepseek-ai/deepseek-harness/discussions/8174
|
|
780
898
|
[#8208]: https://github.com/deepseek-ai/deepseek-harness/discussions/8208
|
|
899
|
+
[discussion #8312]: https://github.com/deepseek-ai/deepseek-harness/discussions/8312
|
|
900
|
+
[discussion #8314]: https://github.com/deepseek-ai/deepseek-harness/discussions/8314
|
|
901
|
+
[discussion #8322]: https://github.com/deepseek-ai/deepseek-harness/discussions/8322
|
|
902
|
+
[#7709]: https://github.com/deepseek-ai/deepseek-harness/discussions/7709
|
|
903
|
+
[#7735]: https://github.com/deepseek-ai/deepseek-harness/discussions/7735
|
|
904
|
+
[#8175]: https://github.com/deepseek-ai/deepseek-harness/discussions/8175
|
|
905
|
+
[#8312]: https://github.com/deepseek-ai/deepseek-harness/discussions/8312
|
|
906
|
+
[#8313]: https://github.com/deepseek-ai/deepseek-harness/discussions/8313
|
|
907
|
+
[#8314]: https://github.com/deepseek-ai/deepseek-harness/discussions/8314
|
|
908
|
+
[#8322]: https://github.com/deepseek-ai/deepseek-harness/discussions/8322
|
package/cordis.patch.yml
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# The @argszero/cordis-plugin-sandbox-grant-advisor bundle patch: mount and go.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
3
|
+
# Twelve reports (#7538, #7622, #7646, #7720, #7750, #7735, #7771, #7804,
|
|
4
|
+
# #7816, #8232, #8272, #8275) describe one Windows failure with no way forward:
|
|
5
|
+
# the host-side write grant for a sandboxed workspace cannot be applied, so
|
|
6
|
+
# every sandboxed command fails before it runs with
|
|
6
7
|
#
|
|
7
8
|
# SetNamedSecurityInfoW failed (Win32 5): grantWrite(<workspace>)
|
|
8
9
|
#
|
|
@@ -24,8 +25,39 @@
|
|
|
24
25
|
# (the manifest half of the merged DACL+SACL write is what needs it);
|
|
25
26
|
# - the directory's own ACL is the discriminator (`icacls <dir>`, looking for
|
|
26
27
|
# an ACE that names your SID with (F));
|
|
27
|
-
# - the remedy is one unelevated line
|
|
28
|
-
#
|
|
28
|
+
# - the remedy is one unelevated line, forked on ownership because one command
|
|
29
|
+
# cannot serve both rights situations — the advisory hands over the check
|
|
30
|
+
# (`(Get-Acl "<dir>").Owner`) rather than guessing:
|
|
31
|
+
# icacls "<dir>" /grant "$env:USERNAME:(OI)(CI)(WO)"
|
|
32
|
+
# with Full control (`F`) offered second as the same line with more than it
|
|
33
|
+
# needs, and an elevated grant for a directory the caller does not own (where
|
|
34
|
+
# `icacls /grant` is itself refused for want of WRITE_DAC);
|
|
35
|
+
# - two version boundaries the error cannot carry: the mandatory label — and
|
|
36
|
+
# with it the SACL — arrives in `0.1.7-alpha.1` (up to `0.1.6-alpha.x` the
|
|
37
|
+
# apply touched the DACL only), and the backend's own
|
|
38
|
+
# `diagnose-windows-sandbox-acl` repair arrives with the `0.2.0` line. Neither
|
|
39
|
+
# makes downgrading a fix, and repairing the `0.1.7` file list is not a thing
|
|
40
|
+
# that can be done — there is nothing in that release to list.
|
|
41
|
+
#
|
|
42
|
+
# The advisory also says why a "weaker grant" is not a smaller version of the
|
|
43
|
+
# same one, because that is the next idea the diagnosis invites: the label rides
|
|
44
|
+
# the SAME SetNamedSecurityInfoW as the DACL (no DACL-only apply path exists), and
|
|
45
|
+
# the confined token is itself lowered to Low, so the directory's Low label is
|
|
46
|
+
# what lets that child write there at all.
|
|
47
|
+
#
|
|
48
|
+
# And it says WHAT THE GRANT LEAVES BEHIND once it applies (#8312, #8314): the
|
|
49
|
+
# three entries are STANDING and nothing revokes them (the backend's own dispose
|
|
50
|
+
# and fail-closed paths leave them — "the intended end state (the reuse cache)"),
|
|
51
|
+
# the Low integrity label is INHERITABLE and lives in the SACL (so `icacls /reset`
|
|
52
|
+
# does not remove it — that rebuilds the DACL), a process starts at
|
|
53
|
+
# min(user, image) integrity, and an NTFS hard link shares one security descriptor
|
|
54
|
+
# with the file object it names — so in a pnpm workspace, whose node_modules are
|
|
55
|
+
# hard links into a content-addressed store, the label can reach OUTSIDE the tree
|
|
56
|
+
# and stay on the store's objects, after which unrelated builds fail in ways that
|
|
57
|
+
# name the build tool and not DSH. The section is stated before the reader runs
|
|
58
|
+
# the command, and it ships NO removal command: the maintainers' own diagnosis
|
|
59
|
+
# skill reports the label and does not remove it, and removing an integrity label
|
|
60
|
+
# needs WRITE_OWNER.
|
|
29
61
|
#
|
|
30
62
|
# Optional config:
|
|
31
63
|
#
|
|
@@ -46,15 +78,23 @@
|
|
|
46
78
|
# this environment, and it is bounded by `maxDenials`, because a plugin that can
|
|
47
79
|
# stop command execution must never be the reason a session cannot finish.
|
|
48
80
|
#
|
|
49
|
-
# A SECOND family is recognized on the same seam (#7638): with the
|
|
50
|
-
# preset and a confining sandbox mode, every shell call dies instantly
|
|
81
|
+
# A SECOND family is recognized on the same seam (#7638, #8322): with the
|
|
82
|
+
# `minimal` preset and a confining sandbox mode, every shell call dies instantly
|
|
83
|
+
# with
|
|
51
84
|
#
|
|
52
85
|
# PTY shell exited during startup
|
|
53
86
|
#
|
|
54
87
|
# The terminal backend cannot create the pseudo-console inside the sandbox, so
|
|
55
88
|
# the child exits before its first prompt; retrying never helps and `minimal`
|
|
56
|
-
# mounts no fallback shell tool.
|
|
57
|
-
#
|
|
89
|
+
# mounts no fallback shell tool. Inside that combination the HOST BINARY decides
|
|
90
|
+
# (#8322, one runner and one ConPTY in every arm): a console-subsystem `node.exe`
|
|
91
|
+
# host starts the confined shell, while the packaged desktop's GUI-subsystem
|
|
92
|
+
# Electron host kills it silently — the same console rule the third family
|
|
93
|
+
# states, where a restricted token can inherit a console but not create one. The
|
|
94
|
+
# outcome is deterministic per (session mode x runner host), and the mode that
|
|
95
|
+
# counts is the one the session records. That advisory states the resolved mode,
|
|
96
|
+
# tells the model to STOP rather than retry, and hands the user the choices — a
|
|
97
|
+
# preset swap, a profile-patch row, or a host that owns a console (#8313); it
|
|
58
98
|
# never names a command to run (there is no shell to run it in) and never names
|
|
59
99
|
# a shell tool the failing composition does not mount. It is sent only when the
|
|
60
100
|
# mode the call actually ran under confines; otherwise the failure is left
|
|
@@ -63,6 +103,28 @@
|
|
|
63
103
|
# above deliberately does NOT cover this family: its remedy is a patch-layer
|
|
64
104
|
# change the user makes between sessions, not a command that repairs the
|
|
65
105
|
# running one.
|
|
106
|
+
#
|
|
107
|
+
# A THIRD family is recognized from a value, not from text (#7876, #7877,
|
|
108
|
+
# #8193, #8208, #8313):
|
|
109
|
+
#
|
|
110
|
+
# [exit code: -1073741502] (0xC0000142 STATUS_DLL_INIT_FAILED)
|
|
111
|
+
#
|
|
112
|
+
# A confined Windows child died while its native images were loading, i.e.
|
|
113
|
+
# before its entry point. It reaches the tool result as an ordinary nonzero
|
|
114
|
+
# exit code — upstream's runner-failure rules admit only exit 127 with the
|
|
115
|
+
# `windows-acl-run: ` signature, so this code is never reclassified and never
|
|
116
|
+
# arrives as an error — which is why a plugin reading only error results is
|
|
117
|
+
# structurally blind to it, and why the model retries a command that cannot
|
|
118
|
+
# start. The read is the shell tool's own canonical value
|
|
119
|
+
# (`ToolExecutionSuccess.value`, `kind: 'foreground'`), so no line of rendered
|
|
120
|
+
# text can be mistaken for it. The advisory states the resolved mode, says the
|
|
121
|
+
# process never reached its entry point, enumerates the measured producers
|
|
122
|
+
# (an MSYS2 / Git-Bash program that cannot create its signal pipe under the
|
|
123
|
+
# restricted token; the sandbox runner hosted by a GUI-subsystem Electron binary
|
|
124
|
+
# or spawned without a console), carries the one conversion the model can make
|
|
125
|
+
# itself (rewrite the work as PowerShell or `cmd`), names the remedy #8193
|
|
126
|
+
# measured — a real node.exe host, which the desktop ships — and says plainly
|
|
127
|
+
# which producers it does not know. Like the second family, it is mode-gated.
|
|
66
128
|
|
|
67
129
|
- insert:
|
|
68
130
|
- id: sandbox-grant-advisor
|
package/lib/advice.js
CHANGED
|
@@ -56,13 +56,35 @@
|
|
|
56
56
|
* keeps the token lowering yields a workspace the confined child cannot write
|
|
57
57
|
* to. That is stated instead of a bare "no", because a reader told only "no"
|
|
58
58
|
* reaches for the workaround without knowing what else it would have to change.
|
|
59
|
+
*
|
|
60
|
+
* **It also states what the grant leaves behind** (`#8312`, `#8314`), because
|
|
61
|
+
* this is the module that hands over the command applying that grant, and these
|
|
62
|
+
* are facts a reader needs at that moment rather than from a broken build in
|
|
63
|
+
* another project later. The three entries are standing by design — the
|
|
64
|
+
* backend's dispose path leaves them, and its own failure-cleanup comment calls
|
|
65
|
+
* them "the intended end state (the reuse cache)" — the Low label is
|
|
66
|
+
* inheritable and lives in the SACL (so resetting the DACL does not remove it),
|
|
67
|
+
* and an NTFS hard link is a second name for one file object, so a pnpm
|
|
68
|
+
* workspace's `node_modules` → content-addressed-store links carry that label
|
|
69
|
+
* out of the tree and leave it on objects other projects build from. The
|
|
70
|
+
* section offers no removal command: the maintainers' own diagnosis skill
|
|
71
|
+
* reports the label and leaves it, removing one needs `WRITE_OWNER`, and this
|
|
72
|
+
* project has no Windows host on which to verify a line.
|
|
59
73
|
* - **The persistent-shell failure** (`pty-startup`) is *not* fixable by the
|
|
60
74
|
* caller — least of all by the model, which has no shell to run anything in.
|
|
61
75
|
* So its advice says so and stops: the remedy is a user-side preset choice,
|
|
62
76
|
* and the model's instruction is to stop retrying and use its file tools.
|
|
63
77
|
* Handing the model a command here would be advice to run something that
|
|
64
78
|
* cannot run, and naming a one-shot shell tool would be advice to call a tool
|
|
65
|
-
* the failing composition does not mount.
|
|
79
|
+
* the failing composition does not mount. **Inside a confining mode the host
|
|
80
|
+
* binary decides**, which `#8322` separated with one runner and one ConPTY in
|
|
81
|
+
* every arm: a console-subsystem `node.exe` host starts the confined shell,
|
|
82
|
+
* while the packaged desktop's GUI-subsystem Electron host kills it silently.
|
|
83
|
+
* That is the same console rule the native-init family states — under the
|
|
84
|
+
* restricted token a console can be inherited but not created — so the advisory
|
|
85
|
+
* names the host alongside the mode, says the outcome is deterministic per
|
|
86
|
+
* (session mode × host) rather than intermittent, and offers the console-owning
|
|
87
|
+
* host as a user-side option `#8313` measured working.
|
|
66
88
|
* - **The native-init death** (`native-init`) is the one whose remedy is **split**:
|
|
67
89
|
* the *class* is not the model's to fix, but one of its two measured producers
|
|
68
90
|
* is. A confined child that died with `STATUS_DLL_INIT_FAILED` never ran
|
|
@@ -108,12 +130,35 @@
|
|
|
108
130
|
* @module
|
|
109
131
|
*/
|
|
110
132
|
import { failureLine, STATUS_DLL_INIT_FAILED } from './signature.js';
|
|
111
|
-
/**
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
133
|
+
/**
|
|
134
|
+
* The upstream threads the ACL advisory is a stopgap for.
|
|
135
|
+
*
|
|
136
|
+
* The first twelve report the provisioning failure itself (the merged DACL +
|
|
137
|
+
* label write being refused). The last two, `#8312` and `#8314`, report the
|
|
138
|
+
* other end of the same backend — what its grant leaves behind once it
|
|
139
|
+
* *succeeds* — which the advisory states because it is the fact a reader needs
|
|
140
|
+
* at the moment it hands them the command that applies that grant.
|
|
141
|
+
*/
|
|
142
|
+
export const ACL_DISCUSSIONS = '#7538 / #7622 / #7646 / #7720 / #7750 / #7735 / #7771 / #7804 / #7816 / #8232 / #8272 / #8275 / #8312 / #8314';
|
|
143
|
+
/**
|
|
144
|
+
* The upstream threads the persistent-shell advisory is a stopgap for.
|
|
145
|
+
*
|
|
146
|
+
* `#7638` is the failure and its three-arm control (the mode is the
|
|
147
|
+
* discriminator); `#8322` is the one that separated the arms inside a confining
|
|
148
|
+
* mode and found the sandbox runner's host binary — same runner, same ConPTY,
|
|
149
|
+
* console-subsystem `node.exe` host works where a GUI-subsystem one dies
|
|
150
|
+
* silently — which is why the advisory names the host as well as the mode.
|
|
151
|
+
*/
|
|
152
|
+
export const PTY_DISCUSSIONS = '#7638 / #8322';
|
|
153
|
+
/**
|
|
154
|
+
* The upstream threads the native-init-death advisory is a stopgap for.
|
|
155
|
+
*
|
|
156
|
+
* `#8313` is the fifth report of the same code and the one that states the host
|
|
157
|
+
* difference from the outside: the desktop build fails where the same version
|
|
158
|
+
* launched from a terminal does not, which is the same variable the PTY family
|
|
159
|
+
* now names.
|
|
160
|
+
*/
|
|
161
|
+
export const NATIVE_INIT_DISCUSSIONS = '#7876 / #7877 / #8193 / #8208 / #8313';
|
|
117
162
|
/** The documented prerequisite, quoted from the backend's README. */
|
|
118
163
|
export const PREREQUISITE = 'granted directories must be caller-owned and grant `WRITE_OWNER`';
|
|
119
164
|
/**
|
|
@@ -232,7 +277,9 @@ function nonFixes(failure, path) {
|
|
|
232
277
|
' supplies the DACL half and still not WRITE_OWNER, the right this call needs. In the second branch above',
|
|
233
278
|
' it is a legitimate first step with elevation; it is never the fix by itself.',
|
|
234
279
|
` icacls "${path}" /reset /T /C`,
|
|
235
|
-
' restores inheritance, and inheritance is what supplied the Modify-only ACE above.',
|
|
280
|
+
' restores inheritance, and inheritance is what supplied the Modify-only ACE above. Where it strips the last',
|
|
281
|
+
' entry naming you, the directory ends up in exactly the state the diagnosis above describes — its only access',
|
|
282
|
+
' the inherited `Authenticated Users:(M)` (#8314 measured that follow-on failure).',
|
|
236
283
|
'',
|
|
237
284
|
];
|
|
238
285
|
}
|
|
@@ -307,6 +354,76 @@ function degradedGrant() {
|
|
|
307
354
|
'This is not offered here as a fix, and neither is `danger-full-access`.',
|
|
308
355
|
];
|
|
309
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* What the grant leaves behind once it applies, and how far its label travels.
|
|
359
|
+
*
|
|
360
|
+
* The advisory hands the reader a command that makes the backend's workspace
|
|
361
|
+
* grant succeed. This section says what that grant *is* from the other side —
|
|
362
|
+
* not as a warning against running it (without it nothing sandboxed runs at
|
|
363
|
+
* all) but because the effect outlives the session and reaches outside the
|
|
364
|
+
* workspace, and a reader who learns that from a broken build three projects
|
|
365
|
+
* later has learned it too late. `#8312` collected those far-away symptoms;
|
|
366
|
+
* `#8314` measured how the label gets there.
|
|
367
|
+
*
|
|
368
|
+
* Every claim is read off the shipped source rather than repeated from the
|
|
369
|
+
* reports: the standing edits and the dispose path that leaves them
|
|
370
|
+
* (`src/grant.ts`, whose `dispose` doc says the standing edits are skipped by
|
|
371
|
+
* design, and `src/index.ts`, whose fail-closed cleanup says the same in
|
|
372
|
+
* plainer words); the label's `(OI|CI)` inheritance and its home in the SACL
|
|
373
|
+
* (`src/acl.ts`, the merged security-information flags); and the hard-link
|
|
374
|
+
* boundary, which the backend's own suite pins as a known reach
|
|
375
|
+
* (`tests/runner.spec.ts`, "a workspace hard link lets the grant reach an
|
|
376
|
+
* external file object").
|
|
377
|
+
*
|
|
378
|
+
* Two things it deliberately does **not** do. It does not offer a removal
|
|
379
|
+
* command: the maintainers' own diagnosis skill reports the label and leaves it,
|
|
380
|
+
* removing one needs `WRITE_OWNER`, and this project has no Windows host to
|
|
381
|
+
* verify a line on — shipping an unverified removal command would be the same
|
|
382
|
+
* defect the rest of this module exists to answer. And it does not present the
|
|
383
|
+
* out-of-tree reach as universally true: it is what happens when the workspace
|
|
384
|
+
* contains hard links into a store on the same volume, which is what a pnpm
|
|
385
|
+
* install produces and what the report measured.
|
|
386
|
+
*
|
|
387
|
+
* Emitted for every class, like {@link versionBoundary} and for the same reason:
|
|
388
|
+
* the fact is about the package's grant (and the remedy the advisory hands over
|
|
389
|
+
* in every class is that grant), not about which of its two calls failed.
|
|
390
|
+
* @returns the section's lines, ending with the blank separator line.
|
|
391
|
+
*/
|
|
392
|
+
function standingEdits() {
|
|
393
|
+
return [
|
|
394
|
+
'What the grant leaves behind, once it applies — worth knowing before you run the command above, because the',
|
|
395
|
+
'backend does not take it back:',
|
|
396
|
+
' The three entries are STANDING, deliberately, and nothing revokes them. The workspace grant is a reuse cache:',
|
|
397
|
+
' the dispose path is documented to revoke the revocable (temp) grants and leave "the standing workspace edits',
|
|
398
|
+
' in place", and the failure-cleanup path says it in plainer words — standing ACEs "are NOT revoked — they are',
|
|
399
|
+
' the intended end state (the reuse cache), not an error artifact". They outlive the session and the harness',
|
|
400
|
+
' exiting (`sandbox-windows-acl/src/grant.ts`, `src/index.ts`).',
|
|
401
|
+
' The Low integrity label is INHERITABLE (`(OI|CI)`) and it lives in the SACL — which is why the `icacls',
|
|
402
|
+
' /reset` above does not take it off: that command rebuilds the DACL. Windows starts a process at the minimum',
|
|
403
|
+
' of the user\'s and the program\'s integrity, so anything started from a tree the harness has written to runs at',
|
|
404
|
+
' LOW integrity, and none of the symptoms names DSH (#8312 collects them): an Electron/Chromium app exiting',
|
|
405
|
+
' `0x80000003` at startup with no output (#7709), msbuild / dotnet / npm refusing or warning about the files as',
|
|
406
|
+
' if they came from the Internet when no `Zone.Identifier` exists (#8175), a double-clicked `.exe` / `.cmd`',
|
|
407
|
+
' reporting "publisher could not be verified" (#7735).',
|
|
408
|
+
' It can also leave the workspace. An NTFS hard link is a SECOND NAME for one file object, so both names share',
|
|
409
|
+
' one security descriptor — and a pnpm workspace is largely hard links (`node_modules` pointing into a',
|
|
410
|
+
' content-addressed store on the same volume). An inheritable label written inside the tree therefore lands on',
|
|
411
|
+
' the STORE\'s objects and stays there, after which every project using that store builds with executables that',
|
|
412
|
+
' start at Low integrity, and the failure surfaces as the build tool rather than the sandbox: `vite build` unable',
|
|
413
|
+
' to remove its own temp file, `pnpm install` unable to replace a hook (#8314 measured the whole chain). The',
|
|
414
|
+
' backend\'s own suite pins the link reach as a known boundary — "a workspace hard link lets the grant reach an',
|
|
415
|
+
' external file object" — and its README calls refusing multiply-linked files unviable for ordinary pnpm',
|
|
416
|
+
' installs, which leaves the out-of-tree reach open rather than unknown.',
|
|
417
|
+
' It is also why the label is not simply removable here: the built-in `diagnose-windows-sandbox-acl` skill',
|
|
418
|
+
' (0.2.0 and later) reports `LOW_LABEL` and by design does not remove it, and removing an integrity label needs',
|
|
419
|
+
' WRITE_OWNER — the same right this whole failure is about. This advisory hands over no removal command: the',
|
|
420
|
+
' maintainers\' own skill does not, and an unverified one would be the defect this plugin exists to answer.',
|
|
421
|
+
'None of this makes the command above the wrong move — without it, nothing sandboxed runs in this workspace. It',
|
|
422
|
+
'is what the harness does to a directory it has been pointed at, and it is worth knowing before rather than',
|
|
423
|
+
'discovering it as a broken build in some other project later.',
|
|
424
|
+
'',
|
|
425
|
+
];
|
|
426
|
+
}
|
|
310
427
|
/**
|
|
311
428
|
* Build the advisory attached to the failing tool result.
|
|
312
429
|
*
|
|
@@ -395,6 +512,7 @@ function aclAdvisory(failure, href) {
|
|
|
395
512
|
'inherits Full control for you — and open the session on that one.',
|
|
396
513
|
'',
|
|
397
514
|
...nonFixes(failure, path),
|
|
515
|
+
...standingEdits(),
|
|
398
516
|
...(failure.klass === 'apply-denied' ? [...degradedGrant(), ''] : []),
|
|
399
517
|
'How to read this: the harness documents the prerequisite (' + PREREQUISITE + ') and this',
|
|
400
518
|
'error does not name it yet, so the advice is delivered here instead. This is a stopgap, ' + where + '.',
|
|
@@ -542,7 +660,7 @@ function nativeInitAdvisory(failure, mode, onElectron, href) {
|
|
|
542
660
|
* @returns the user-role notice text.
|
|
543
661
|
*/
|
|
544
662
|
function ptyAdvisory(failure, mode, tool, href) {
|
|
545
|
-
const where = href === undefined ? `tracked upstream (
|
|
663
|
+
const where = href === undefined ? `tracked upstream (discussions ${PTY_DISCUSSIONS})` : `tracked upstream: ${href}`;
|
|
546
664
|
const call = tool === undefined ? 'This tool' : `The \`${tool}\` tool`;
|
|
547
665
|
return [
|
|
548
666
|
'Persistent shell failed to start — command execution is unavailable in this session, and retrying cannot fix it.',
|
|
@@ -556,6 +674,24 @@ function ptyAdvisory(failure, mode, tool, href) {
|
|
|
556
674
|
'shell works under `danger-full-access`, and the one-shot shell tool works under the same confining mode:',
|
|
557
675
|
'persistent PTY × confining sandbox is the combination that fails.',
|
|
558
676
|
'',
|
|
677
|
+
'Which sessions fail inside that combination is not chance — it is deterministic per (session mode × the host',
|
|
678
|
+
'binary carrying the sandbox runner), so a "working now" attempt in the same session is not evidence of flakiness.',
|
|
679
|
+
'Measured against the desktop build with the same runner and the same ConPTY in every arm (#8322):',
|
|
680
|
+
' - runner hosted by a plain console-subsystem `node.exe` → the confined shell starts, prompt and shell-integration',
|
|
681
|
+
' marks correct;',
|
|
682
|
+
' - runner hosted by the packaged desktop\'s GUI-subsystem Electron executable (started with',
|
|
683
|
+
' `ELECTRON_RUN_AS_NODE=1`) → the child dies silently: zero bytes on stdout AND stderr, and the non-interactive',
|
|
684
|
+
' arm exits 0 with everything it printed lost.',
|
|
685
|
+
'The rule behind both this and the `0xC0000142` family is the one stated there for its own case: under the',
|
|
686
|
+
'restricted token a console can be INHERITED but not CREATED — so the host binary, the thing that owns a console',
|
|
687
|
+
'or owns none, is what the arms above turn on. It is also why the same build behaves differently depending on how',
|
|
688
|
+
'it was started: the same version run as the desktop app fails, while the Web UI started from a terminal —',
|
|
689
|
+
'whose `process.execPath` is a real `node.exe` — is reported working under the same confining mode (#8313).',
|
|
690
|
+
'And the mode that decides is the one the SESSION records, not the one the environment now holds: a session',
|
|
691
|
+
'whose stream recorded the confining mode keeps failing',
|
|
692
|
+
'after the app is restarted with a different mode in the environment, while switching it inside that session',
|
|
693
|
+
'takes effect immediately (#8322).',
|
|
694
|
+
'',
|
|
559
695
|
'Do NOT retry, and do not look for a command that fixes it: every attempt will fail identically, and there is no',
|
|
560
696
|
'shell to run a command in. Use your file read/write tools instead, and hand the choice below to the user.',
|
|
561
697
|
'',
|
|
@@ -566,8 +702,11 @@ function ptyAdvisory(failure, mode, tool, href) {
|
|
|
566
702
|
` \`${GLOBAL_PATCH}\` for every profile — replacing its \`persistent-shell\` group with`,
|
|
567
703
|
` \`${ONE_SHOT_SHELL}\` (a one-shot subprocess, no PTY); the patch layer is yours, so an upgrade`,
|
|
568
704
|
' will not overwrite it; or',
|
|
569
|
-
' 3. run the session
|
|
570
|
-
'
|
|
705
|
+
' 3. run the session from a host that owns a console instead of the packaged desktop app — the Web UI started',
|
|
706
|
+
' from a terminal (`process.execPath` is a real `node.exe` there) was reported working under the same',
|
|
707
|
+
' confining mode and the same version (#8313); or',
|
|
708
|
+
' 4. run the session with `danger-full-access`, which drops the very confinement the sandbox exists to give.',
|
|
709
|
+
' Prefer 1 to 3.',
|
|
571
710
|
'',
|
|
572
711
|
'How to read this: the failure names no cause and points at no remedy, so the diagnosis is delivered here instead.',
|
|
573
712
|
'This is a stopgap, ' + where + '. Unless the mode is `danger-full-access`, this plugin stays silent, because a',
|
package/lib/types/advice.d.ts
CHANGED
|
@@ -56,13 +56,35 @@
|
|
|
56
56
|
* keeps the token lowering yields a workspace the confined child cannot write
|
|
57
57
|
* to. That is stated instead of a bare "no", because a reader told only "no"
|
|
58
58
|
* reaches for the workaround without knowing what else it would have to change.
|
|
59
|
+
*
|
|
60
|
+
* **It also states what the grant leaves behind** (`#8312`, `#8314`), because
|
|
61
|
+
* this is the module that hands over the command applying that grant, and these
|
|
62
|
+
* are facts a reader needs at that moment rather than from a broken build in
|
|
63
|
+
* another project later. The three entries are standing by design — the
|
|
64
|
+
* backend's dispose path leaves them, and its own failure-cleanup comment calls
|
|
65
|
+
* them "the intended end state (the reuse cache)" — the Low label is
|
|
66
|
+
* inheritable and lives in the SACL (so resetting the DACL does not remove it),
|
|
67
|
+
* and an NTFS hard link is a second name for one file object, so a pnpm
|
|
68
|
+
* workspace's `node_modules` → content-addressed-store links carry that label
|
|
69
|
+
* out of the tree and leave it on objects other projects build from. The
|
|
70
|
+
* section offers no removal command: the maintainers' own diagnosis skill
|
|
71
|
+
* reports the label and leaves it, removing one needs `WRITE_OWNER`, and this
|
|
72
|
+
* project has no Windows host on which to verify a line.
|
|
59
73
|
* - **The persistent-shell failure** (`pty-startup`) is *not* fixable by the
|
|
60
74
|
* caller — least of all by the model, which has no shell to run anything in.
|
|
61
75
|
* So its advice says so and stops: the remedy is a user-side preset choice,
|
|
62
76
|
* and the model's instruction is to stop retrying and use its file tools.
|
|
63
77
|
* Handing the model a command here would be advice to run something that
|
|
64
78
|
* cannot run, and naming a one-shot shell tool would be advice to call a tool
|
|
65
|
-
* the failing composition does not mount.
|
|
79
|
+
* the failing composition does not mount. **Inside a confining mode the host
|
|
80
|
+
* binary decides**, which `#8322` separated with one runner and one ConPTY in
|
|
81
|
+
* every arm: a console-subsystem `node.exe` host starts the confined shell,
|
|
82
|
+
* while the packaged desktop's GUI-subsystem Electron host kills it silently.
|
|
83
|
+
* That is the same console rule the native-init family states — under the
|
|
84
|
+
* restricted token a console can be inherited but not created — so the advisory
|
|
85
|
+
* names the host alongside the mode, says the outcome is deterministic per
|
|
86
|
+
* (session mode × host) rather than intermittent, and offers the console-owning
|
|
87
|
+
* host as a user-side option `#8313` measured working.
|
|
66
88
|
* - **The native-init death** (`native-init`) is the one whose remedy is **split**:
|
|
67
89
|
* the *class* is not the model's to fix, but one of its two measured producers
|
|
68
90
|
* is. A confined child that died with `STATUS_DLL_INIT_FAILED` never ran
|
|
@@ -109,12 +131,35 @@
|
|
|
109
131
|
*/
|
|
110
132
|
import type { ProvisioningFailure, RecognizedFailure } from './signature.js';
|
|
111
133
|
import type { SandboxModeName } from './mode.js';
|
|
112
|
-
/**
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
134
|
+
/**
|
|
135
|
+
* The upstream threads the ACL advisory is a stopgap for.
|
|
136
|
+
*
|
|
137
|
+
* The first twelve report the provisioning failure itself (the merged DACL +
|
|
138
|
+
* label write being refused). The last two, `#8312` and `#8314`, report the
|
|
139
|
+
* other end of the same backend — what its grant leaves behind once it
|
|
140
|
+
* *succeeds* — which the advisory states because it is the fact a reader needs
|
|
141
|
+
* at the moment it hands them the command that applies that grant.
|
|
142
|
+
*/
|
|
143
|
+
export declare const ACL_DISCUSSIONS = "#7538 / #7622 / #7646 / #7720 / #7750 / #7735 / #7771 / #7804 / #7816 / #8232 / #8272 / #8275 / #8312 / #8314";
|
|
144
|
+
/**
|
|
145
|
+
* The upstream threads the persistent-shell advisory is a stopgap for.
|
|
146
|
+
*
|
|
147
|
+
* `#7638` is the failure and its three-arm control (the mode is the
|
|
148
|
+
* discriminator); `#8322` is the one that separated the arms inside a confining
|
|
149
|
+
* mode and found the sandbox runner's host binary — same runner, same ConPTY,
|
|
150
|
+
* console-subsystem `node.exe` host works where a GUI-subsystem one dies
|
|
151
|
+
* silently — which is why the advisory names the host as well as the mode.
|
|
152
|
+
*/
|
|
153
|
+
export declare const PTY_DISCUSSIONS = "#7638 / #8322";
|
|
154
|
+
/**
|
|
155
|
+
* The upstream threads the native-init-death advisory is a stopgap for.
|
|
156
|
+
*
|
|
157
|
+
* `#8313` is the fifth report of the same code and the one that states the host
|
|
158
|
+
* difference from the outside: the desktop build fails where the same version
|
|
159
|
+
* launched from a terminal does not, which is the same variable the PTY family
|
|
160
|
+
* now names.
|
|
161
|
+
*/
|
|
162
|
+
export declare const NATIVE_INIT_DISCUSSIONS = "#7876 / #7877 / #8193 / #8208 / #8313";
|
|
118
163
|
/** The documented prerequisite, quoted from the backend's README. */
|
|
119
164
|
export declare const PREREQUISITE = "granted directories must be caller-owned and grant `WRITE_OWNER`";
|
|
120
165
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@argszero/cordis-plugin-sandbox-grant-advisor",
|
|
3
|
-
"description": "Turns three sandbox environment failures that name neither their cause nor a remedy into a diagnosis with a path forward. Family 1, the Windows workspace ACL: twelve reports (#7538, #7622, #7646, #7720, #7750, #7735, #7771, #7804, #7816, #8232, #8272, #8275) of one signature \u2014 every sandboxed command fails before it runs with `SetNamedSecurityInfoW failed (Win32 5): grantWrite(<workspace>)`, because the merged DACL + mandatory-label write needs WRITE_OWNER on the directory (an object right the caller can self-grant), not SeSecurityPrivilege and not elevation; the host grant is materialized lazily and caches nothing on the failure path, so the same failure repeats per command. Family 2, the persistent shell (#7638): with the `minimal` preset under a confining sandbox mode every shell call dies instantly with `PTY shell exited during startup` because the terminal backend cannot create the pseudo-console inside the sandbox, retrying never helps, and `minimal` mounts no fallback shell tool. Family 3, a confined Windows child that died during native initialization (#7876, #7877): every command spawned through the sandbox runner can report exit 0xC0000142 STATUS_DLL_INIT_FAILED with the process never reaching its entry point \u2014 the packaged desktop starts that runner as [process.execPath, entry], and that host has been measured twice with one indistinguishable appearance from inside a session \u2014 either nothing on the runner path ran because the Electron binary launches as an application unless the child's environment carries ELECTRON_RUN_AS_NODE=1 (#7876), or the desktop launcher does set that variable and the child still dies because the restricted token is derived from the Electron process image (#8193), and an MSYS2 / Git-Bash program cannot create its signal pipe under the restricted token while cmd.exe and pwsh run fine in the same workspace under the same mode (#7877) \u2014 and because upstream's runner-failure rules admit only exit 127 with the `windows-acl-run: ` signature, the code is never an error: it arrives as the canonical value of a result the pipeline calls a success. The plugin observes the public `tools/post-execute` waterfall, classifies all three signatures narrowly (only the two `...NamedSecurityInfoW` operations; the PTY message matched on a whole line, never as a substring; the loader status read as a 32-bit integer out of the shell tool's own canonical success value, never from rendered text; both mode-gated families advised only under a mode the policy resolver reports as confining), and attaches ONE durable user-role advisory per agent per family through `additionalContexts`. The ACL advisory names the missing right, both environments the identical text can describe (an inherited Modify-only entry, a data volume where no ACE names the caller at all, and a directory owned by another account), the version boundary that arrived with the mandatory label (0.1.7-alpha.1, flag 20, versus the DACL-only flag 4 up to 0.1.6-alpha.x) together with why downgrading is not the remedy, the second boundary the later reports needed (the backend's own `diagnose-windows-sandbox-acl` skill is not part of the 0.1.7 line at all — it arrives with 0.2.0, measured on both published tarballs: 0.1.7-rc.2 names it zero times in either README, ships no assets, and carries no registration symbol anywhere in its tree, while 0.2.0-rc.2 ships assets/diagnose-windows-sandbox-acl/ and names it three times per README — so a reader who found the promise in a README was reading a 0.2.0-era document, and sending them to repair a files glob would be the wrong repair), and why a \"weaker grant\" is a mechanism the backend cannot express rather than a policy it declines (the label rides the SAME SetNamedSecurityInfoW as the grant — there is no DACL-only apply path to fall back to — and the confined token is itself lowered to Low, so the object's Low label is what lets the confined child write there at all; declining the label usefully means declining the token level with it), the discriminator, the remedy forked on an ownership check the user runs (`(Get-Acl \"<dir>\").Owner`), because one command cannot serve both rights situations: where the caller owns the directory, the unelevated `icacls ... :(OI)(CI)(WO)` is the whole of what is missing \u2014 the owner's implicit WRITE_DAC already covers the DACL half \u2014 and where the caller does not own it that same command is refused for want of WRITE_DAC, so the grant has to come from an elevated account, or by taking ownership first, or by moving the workspace under %USERPROFILE% \u2014 and the two remedies that look right and are not (`takeown`, `icacls /reset`), each with the reason it fails — and it states the two things about the failure's shape that the reports had to measure for themselves (#8232): that it belongs to the workspace rather than to the command (a command that only reads fails identically, so there is no harmless retry), and that the grant is scoped to the directory it names and its children, so a sibling workspace root on the same volume needs the line once more; the PTY advisory names the failing combination, states the resolved mode, tells the model to stop rather than retry, and hands the user-side preset
|
|
4
|
-
"version": "0.
|
|
3
|
+
"description": "Turns three sandbox environment failures that name neither their cause nor a remedy into a diagnosis with a path forward. Family 1, the Windows workspace ACL: twelve reports (#7538, #7622, #7646, #7720, #7750, #7735, #7771, #7804, #7816, #8232, #8272, #8275) of one signature \u2014 every sandboxed command fails before it runs with `SetNamedSecurityInfoW failed (Win32 5): grantWrite(<workspace>)`, because the merged DACL + mandatory-label write needs WRITE_OWNER on the directory (an object right the caller can self-grant), not SeSecurityPrivilege and not elevation; the host grant is materialized lazily and caches nothing on the failure path, so the same failure repeats per command. Two further reports (#8312, #8314) describe the other end of that same backend — what its grant leaves behind once it succeeds: three STANDING entries nothing revokes (the workspace capability SID, the world delete-child deny, and an INHERITABLE Low integrity label that lives in the SACL, so resetting the DACL does not take it off), which makes every program launched from a folder DSH has written to run at Low integrity, and — through the NTFS hard links a pnpm workspace is made of, where two names share one file object and therefore one security descriptor — can reach out of the tree onto a content-addressed store's shared objects, leaving every project that builds from that store with Low-integrity executables and failures that name the build tool rather than DSH; the advisory states all of it before handing over the command that applies that grant, and offers no removal command, because the maintainers' own diagnosis skill does not remove the label either and removing one needs WRITE_OWNER. Family 2, the persistent shell (#7638, #8322): with the `minimal` preset under a confining sandbox mode every shell call dies instantly with `PTY shell exited during startup` because the terminal backend cannot create the pseudo-console inside the sandbox, retrying never helps, and `minimal` mounts no fallback shell tool; #8322 separated the arms inside that mode with one runner and one ConPTY — a console-subsystem node.exe host starts the confined shell while the packaged desktop's GUI-subsystem Electron host kills it silently — so the advisory names the host alongside the mode, says the outcome is deterministic per (session mode × host) rather than intermittent, and adds the console-owning host the Web UI provides (#8313) to the user-side options. Family 3, a confined Windows child that died during native initialization (#7876, #7877): every command spawned through the sandbox runner can report exit 0xC0000142 STATUS_DLL_INIT_FAILED with the process never reaching its entry point \u2014 the packaged desktop starts that runner as [process.execPath, entry], and that host has been measured twice with one indistinguishable appearance from inside a session \u2014 either nothing on the runner path ran because the Electron binary launches as an application unless the child's environment carries ELECTRON_RUN_AS_NODE=1 (#7876), or the desktop launcher does set that variable and the child still dies because the restricted token is derived from the Electron process image (#8193), and an MSYS2 / Git-Bash program cannot create its signal pipe under the restricted token while cmd.exe and pwsh run fine in the same workspace under the same mode (#7877) \u2014 and because upstream's runner-failure rules admit only exit 127 with the `windows-acl-run: ` signature, the code is never an error: it arrives as the canonical value of a result the pipeline calls a success. The plugin observes the public `tools/post-execute` waterfall, classifies all three signatures narrowly (only the two `...NamedSecurityInfoW` operations; the PTY message matched on a whole line, never as a substring; the loader status read as a 32-bit integer out of the shell tool's own canonical success value, never from rendered text; both mode-gated families advised only under a mode the policy resolver reports as confining), and attaches ONE durable user-role advisory per agent per family through `additionalContexts`. The ACL advisory names the missing right, both environments the identical text can describe (an inherited Modify-only entry, a data volume where no ACE names the caller at all, and a directory owned by another account), the version boundary that arrived with the mandatory label (0.1.7-alpha.1, flag 20, versus the DACL-only flag 4 up to 0.1.6-alpha.x) together with why downgrading is not the remedy, the second boundary the later reports needed (the backend's own `diagnose-windows-sandbox-acl` skill is not part of the 0.1.7 line at all — it arrives with 0.2.0, measured on both published tarballs: 0.1.7-rc.2 names it zero times in either README, ships no assets, and carries no registration symbol anywhere in its tree, while 0.2.0-rc.2 ships assets/diagnose-windows-sandbox-acl/ and names it three times per README — so a reader who found the promise in a README was reading a 0.2.0-era document, and sending them to repair a files glob would be the wrong repair), and why a \"weaker grant\" is a mechanism the backend cannot express rather than a policy it declines (the label rides the SAME SetNamedSecurityInfoW as the grant — there is no DACL-only apply path to fall back to — and the confined token is itself lowered to Low, so the object's Low label is what lets the confined child write there at all; declining the label usefully means declining the token level with it), the discriminator, the remedy forked on an ownership check the user runs (`(Get-Acl \"<dir>\").Owner`), because one command cannot serve both rights situations: where the caller owns the directory, the unelevated `icacls ... :(OI)(CI)(WO)` is the whole of what is missing \u2014 the owner's implicit WRITE_DAC already covers the DACL half \u2014 and where the caller does not own it that same command is refused for want of WRITE_DAC, so the grant has to come from an elevated account, or by taking ownership first, or by moving the workspace under %USERPROFILE% \u2014 and the two remedies that look right and are not (`takeown`, `icacls /reset`), each with the reason it fails — and it states the two things about the failure's shape that the reports had to measure for themselves (#8232): that it belongs to the workspace rather than to the command (a command that only reads fails identically, so there is no harmless retry), and that the grant is scoped to the directory it names and its children, so a sibling workspace root on the same volume needs the line once more; the PTY advisory names the failing combination and the host binary that decides it, states the resolved mode and that the session's recorded mode is the one that counts, tells the model to stop rather than retry, and hands over the user-side options (a preset swap, a patch row, or the console-owning host of #8313) \u2014 it never names a shell tool the failing composition does not mount; the native-init advisory states the resolved mode, says the process died before its entry point, enumerates the two producers measured under a confining mode \u2014 naming both measured shapes of the desktop host rather than asserting the one that was measured first \u2014 with the check that separates them (what program the reader ran; whether this host is the packaged desktop binary, which the plugin measures and reports rather than assumes), carries the one conversion a model can make itself (rewrite the work as PowerShell or `cmd` when an MSYS2 program is what could not start), and \u2014 for the Electron host \u2014 names the fix #8193 measured rather than a wider mode: host the runner on a real node.exe (the desktop ships one under `resources/runtime/primary-runtime/dependencies/node/bin/node.exe`), where the same confined `pwsh`/`cmd` spawns succeed, while `danger-full-access` is described as a way to confirm the diagnosis and not a fix, together with the warning that unsetting `ELECTRON_RUN_AS_NODE` instead would leave the desktop's Electron-hosted runner unable to execute `runner.js` at all (the interaction #8193 records with #8174), and says plainly which producers it does not know \u2014 it never claims the sandbox caused the failure and never offers a widened mode as a fix. An optional, off-by-default `enforceAfter` refuses an identical ACL call this plugin has watched fail, bounded by `maxDenials`; the blocking half is ACL-only by design. It never edits an ACL, never elevates, never sets another process's environment, and never changes a preset or a mode, and it complements repeat-guard-escalation, which keys on call identity rather than on the environment signature.",
|
|
4
|
+
"version": "0.10.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|