@argszero/cordis-plugin-sandbox-grant-advisor 0.9.1 → 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 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 with `danger-full-access`, which drops the very confinement the sandbox exists to give.
313
- Prefer 1 or 2.
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
- Four reports of one exit code: [`#7876`] and [`#8193`] (the packaged desktop
335
- app) and [`#7877`] (MSYS2 / Git Bash) — and [`#8208`], which found the mechanism
336
- the console cases share. All are `0xC0000142`
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
@@ -45,6 +45,20 @@
45
45
  # the confined token is itself lowered to Low, so the directory's Low label is
46
46
  # what lets that child write there at all.
47
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.
61
+ #
48
62
  # Optional config:
49
63
  #
50
64
  # - set:
@@ -64,15 +78,23 @@
64
78
  # this environment, and it is bounded by `maxDenials`, because a plugin that can
65
79
  # stop command execution must never be the reason a session cannot finish.
66
80
  #
67
- # A SECOND family is recognized on the same seam (#7638): with the `minimal`
68
- # preset and a confining sandbox mode, every shell call dies instantly with
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
69
84
  #
70
85
  # PTY shell exited during startup
71
86
  #
72
87
  # The terminal backend cannot create the pseudo-console inside the sandbox, so
73
88
  # the child exits before its first prompt; retrying never helps and `minimal`
74
- # mounts no fallback shell tool. That advisory states the resolved mode, tells
75
- # the model to STOP rather than retry, and hands the user a preset choice — it
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
76
98
  # never names a command to run (there is no shell to run it in) and never names
77
99
  # a shell tool the failing composition does not mount. It is sent only when the
78
100
  # mode the call actually ran under confines; otherwise the failure is left
@@ -81,6 +103,28 @@
81
103
  # above deliberately does NOT cover this family: its remedy is a patch-layer
82
104
  # change the user makes between sessions, not a command that repairs the
83
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.
84
128
 
85
129
  - insert:
86
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
- /** The upstream threads the ACL advisory is a stopgap for. */
112
- export const ACL_DISCUSSIONS = '#7538 / #7622 / #7646 / #7720 / #7750 / #7735 / #7771 / #7804 / #7816 / #8232 / #8272 / #8275';
113
- /** The upstream thread the persistent-shell advisory is a stopgap for. */
114
- export const PTY_DISCUSSIONS = '#7638';
115
- /** The upstream threads the native-init-death advisory is a stopgap for. */
116
- export const NATIVE_INIT_DISCUSSIONS = '#7876 / #7877 / #8193 / #8208';
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 (discussion ${PTY_DISCUSSIONS})` : `tracked upstream: ${href}`;
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 with `danger-full-access`, which drops the very confinement the sandbox exists to give.',
570
- ' Prefer 1 or 2.',
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',
@@ -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
- /** The upstream threads the ACL advisory is a stopgap for. */
113
- export declare const ACL_DISCUSSIONS = "#7538 / #7622 / #7646 / #7720 / #7750 / #7735 / #7771 / #7804 / #7816 / #8232 / #8272 / #8275";
114
- /** The upstream thread the persistent-shell advisory is a stopgap for. */
115
- export declare const PTY_DISCUSSIONS = "#7638";
116
- /** The upstream threads the native-init-death advisory is a stopgap for. */
117
- export declare const NATIVE_INIT_DISCUSSIONS = "#7876 / #7877 / #8193 / #8208";
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 choice over \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.9.1",
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",