@argszero/cordis-plugin-sandbox-grant-advisor 0.4.0 → 0.5.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
@@ -113,18 +113,56 @@ confines deletes to the workspace, and reverting it reintroduces the escape it c
113
113
  Confirm the cause (unelevated) — `icacls` is a normal user command:
114
114
  icacls "D:\ws"
115
115
 
116
- Fix it (unelevated, one line) and then run the command again:
116
+ Ownership decides which of the two commands below can work, so read it first — PowerShell 5.1 or later:
117
+ (Get-Acl "D:\ws").Owner # compare with: whoami
118
+
119
+ IF YOU OWN THE DIRECTORY — the usual workspace, on a data volume as much as on C::
120
+ one unelevated line, then run the command again:
117
121
  PowerShell: icacls "D:\ws" /grant "$env:USERNAME:(OI)(CI)(WO)"
118
122
  cmd: icacls "D:\ws" /grant "%USERNAME%:(OI)(CI)(WO)"
119
-
120
- What will NOT fix it — both look like the right move, and both were tried and reported:
123
+ ... Full control works just as well — the same line with `F` in place of `(WO)`
124
+
125
+ IF YOU DO NOT OWN IT — a directory an installer or another account created, e.g. owner
126
+ `BUILTIN\Administrators`:
127
+ the line above cannot run at all. Changing a DACL takes WRITE_DAC, which you hold neither as owner nor
128
+ through any ACE, so `icacls /grant` is refused with `Access is denied` — for the very command that would
129
+ fix it. ... Run the grant once from an account that already holds both — that is, from an ELEVATED prompt:
130
+ icacls "D:\ws" /grant "<your-account>:(OI)(CI)F"
131
+ ... or take ownership first (also elevated; it wants SeTakeOwnership), after which the unelevated `(WO)`
132
+ line above applies: icacls "D:\ws" /setowner "<your-account>"
133
+ ... or sidestep the ACL: create the workspace under `%USERPROFILE%`.
134
+
135
+ What will NOT fix it on its own — both look like the right move, and both were tried and reported:
121
136
  takeown /F "D:\ws" /R /D Y
122
- makes you the owner, but ownership's implicit rights are READ_CONTROL and WRITE_DAC only.
123
- The owner does not implicitly hold WRITE_OWNER, which is the right this call needs.
137
+ makes you the owner, and ownership's implicit rights are READ_CONTROL and WRITE_DAC only — so it
138
+ supplies the DACL half and still not WRITE_OWNER, the right this call needs.
124
139
  icacls "D:\ws" /reset /T /C
125
140
  restores inheritance, and inheritance is what supplied the Modify-only ACE above.
126
141
  ```
127
142
 
143
+ **Why the remedy forks** (added in 0.5.0). The same error covers two different
144
+ rights situations, and one command cannot serve both. Where the caller **owns**
145
+ the directory, the owner's implicit `WRITE_DAC` satisfies the DACL half of the
146
+ merged write, `WRITE_OWNER` is the single missing right, and the unelevated
147
+ `icacls /grant` that supplies it can itself run — [#7750] measured exactly that
148
+ fix working. Where the caller **does not own** it ([#7771]: owner
149
+ `BUILTIN\Administrators`, held deny-only for that token), `WRITE_DAC` is missing
150
+ too, so the very same command is refused before it does anything, and `(WO)`
151
+ alone would not be enough even if it went through. The failure text is identical
152
+ in both, so the classifier cannot pick a branch — the advisory hands over the
153
+ **ownership check** as the selector instead of guessing, which is also the
154
+ actionable-guidance half of what [#7771] asked for. Until 0.5.0 a single
155
+ unconditional one-liner was printed, with a sentence noting it assumed
156
+ ownership; that would have sent the second environment to a command that is
157
+ denied — the same defect this plugin exists to answer.
158
+
159
+ **What is deliberately *not* shipped**: `icacls ... /grant "<user>:(OI)(CI)(WD,WO)"`,
160
+ the two needed rights named explicitly. It is the tighter form and it is
161
+ plausibly correct syntax, but this project has no Windows host to run it on, and
162
+ shipping an unverified command in a remedy whose whole point is that it works is
163
+ the failure mode being fixed. `F` (verified by [#7804]'s reporter) and
164
+ `/setowner` (named by both reports) are given instead.
165
+
128
166
  ### 2. Persistent shell startup (`pty-startup`)
129
167
 
130
168
  [Discussion #7638] reports the second shape: with the **`minimal` preset** on
@@ -399,4 +437,10 @@ the current runtime cannot distinguish rather than counting it as a pass.
399
437
  [discussion #7720]: https://github.com/deepseek-ai/deepseek-harness/discussions/7720
400
438
  [discussion #7750]: https://github.com/deepseek-ai/deepseek-harness/discussions/7750
401
439
  [discussion #7735]: https://github.com/deepseek-ai/deepseek-harness/discussions/7735
440
+ [discussion #7771]: https://github.com/deepseek-ai/deepseek-harness/discussions/7771
441
+ [discussion #7804]: https://github.com/deepseek-ai/deepseek-harness/discussions/7804
442
+ [discussion #7816]: https://github.com/deepseek-ai/deepseek-harness/discussions/7816
443
+ [#7750]: https://github.com/deepseek-ai/deepseek-harness/discussions/7750
444
+ [#7771]: https://github.com/deepseek-ai/deepseek-harness/discussions/7771
445
+ [#7804]: https://github.com/deepseek-ai/deepseek-harness/discussions/7804
402
446
  [discussion #7638]: https://github.com/deepseek-ai/deepseek-harness/discussions/7638
package/lib/advice.js CHANGED
@@ -20,6 +20,24 @@
20
20
  * separates "this is the label failure" from "this is something else", and
21
21
  * because rolling back is the reach it invites while making the very problem
22
22
  * it closed come back.
23
+ *
24
+ * **The remedy is forked on ownership, because one command cannot serve both
25
+ * environments.** The reports split into two rights situations behind an
26
+ * identical error: a workspace **the caller owns** (`#7622`, `#7646`, `#7720`,
27
+ * `#7750`, `#7804`), where the owner's implicit `WRITE_DAC` satisfies the DACL
28
+ * half and `(WO)` is the whole of what is missing — so the `icacls /grant`
29
+ * that supplies it *can itself run*, unelevated; and a directory **the caller
30
+ * does not own** (`#7771`: owner `BUILTIN\Administrators`, held deny-only for
31
+ * their token), where `WRITE_DAC` is missing too, so `icacls /grant` is denied
32
+ * for the very command that would fix it, and `(WO)` alone would not be enough
33
+ * even if it went through. The classifier cannot tell these apart — the text is
34
+ * identical — so the advisory does what it can do instead of guessing: it hands
35
+ * over the **ownership check** (`(Get-Acl "<dir>").Owner`) as the branch
36
+ * selector, then gives each branch the command that actually works there, and
37
+ * says why the other branch's command is not a fallback. A single unconditional
38
+ * one-liner would send the second environment to a command that is refused
39
+ * before it runs — the same defect this module exists to answer, a remedy that
40
+ * does not work delivered confidently.
23
41
  * - **The persistent-shell failure** (`pty-startup`) is *not* fixable by the
24
42
  * caller — least of all by the model, which has no shell to run anything in.
25
43
  * So its advice says so and stops: the remedy is a user-side preset choice,
@@ -40,7 +58,7 @@
40
58
  */
41
59
  import { failureLine } from './signature.js';
42
60
  /** The upstream threads the ACL advisory is a stopgap for. */
43
- export const ACL_DISCUSSIONS = '#7538 / #7622 / #7646 / #7720 / #7750 / #7735';
61
+ export const ACL_DISCUSSIONS = '#7538 / #7622 / #7646 / #7720 / #7750 / #7735 / #7771 / #7804 / #7816';
44
62
  /** The upstream thread the persistent-shell advisory is a stopgap for. */
45
63
  export const PTY_DISCUSSIONS = '#7638';
46
64
  /** The documented prerequisite, quoted from the backend's README. */
@@ -111,7 +129,7 @@ function diagnosis(failure) {
111
129
  return [
112
130
  'Why it is refused: this is the same merged write, but the Win32 code is not ERROR_ACCESS_DENIED (5), so',
113
131
  'the missing-rights story above does not apply verbatim — a missing path, a non-directory target, or a',
114
- 'filesystem that does not carry ACLs are all possibilities. The one-line fix below is safe to try; if the',
132
+ 'filesystem that does not carry ACLs are all possibilities. The commands below are safe to try; if the',
115
133
  'code persists, it is a different failure and worth reporting with the code.',
116
134
  ].join('\n');
117
135
  }
@@ -129,7 +147,11 @@ function diagnosis(failure) {
129
147
  * diagnosis.
130
148
  *
131
149
  * A negative claim still has to be earned: the failure to avoid is advice that
132
- * is confidently wrong in the other direction.
150
+ * is confidently wrong in the other direction. That is why the `takeown` line
151
+ * claims only what is true in **both** ownership branches: it supplies the DACL
152
+ * half and never `WRITE_OWNER`, so it is not the fix by itself — while still
153
+ * being a legitimate first step (with elevation) where the caller is not the
154
+ * owner. Calling it useless outright would have been the mirror-image error.
133
155
  * @param failure - the recognized provisioning failure.
134
156
  * @param path - the directory the error named, or the placeholder.
135
157
  * @returns the section's lines, or an empty array for a class it does not fit.
@@ -138,10 +160,11 @@ function nonFixes(failure, path) {
138
160
  if (failure.klass !== 'apply-denied')
139
161
  return [];
140
162
  return [
141
- 'What will NOT fix it — both look like the right move, and both were tried and reported:',
163
+ 'What will NOT fix it on its own — both look like the right move, and both were tried and reported:',
142
164
  ` takeown /F "${path}" /R /D Y`,
143
- " makes you the owner, but ownership's implicit rights are READ_CONTROL and WRITE_DAC only.",
144
- ' The owner does not implicitly hold WRITE_OWNER, which is the right this call needs.',
165
+ " makes you the owner, and ownership's implicit rights are READ_CONTROL and WRITE_DAC only — so it",
166
+ ' supplies the DACL half and still not WRITE_OWNER, the right this call needs. In the second branch above',
167
+ ' it is a legitimate first step with elevation; it is never the fix by itself.',
145
168
  ` icacls "${path}" /reset /T /C`,
146
169
  ' restores inheritance, and inheritance is what supplied the Modify-only ACE above.',
147
170
  '',
@@ -214,17 +237,36 @@ function aclAdvisory(failure, href) {
214
237
  '(WO) / Write owner. If the strongest entry naming you is (M) / Modify — or no entry names you at all and',
215
238
  'your access comes from an inherited `Authenticated Users:(M)` — that is this failure.',
216
239
  '',
217
- 'Fix it (unelevated, one line) and then run the command again:',
240
+ 'Ownership decides which of the two commands below can work, so read it first — PowerShell 5.1 or later:',
241
+ ` (Get-Acl "${path}").Owner # compare with: whoami`,
242
+ 'If that is not your own account, take the second branch: the first one is refused before it runs.',
243
+ '',
244
+ 'IF YOU OWN THE DIRECTORY — the usual workspace, whether on the system drive or a data volume:',
245
+ ' one unelevated line, then run the command again:',
218
246
  ` PowerShell: icacls "${path}" /grant "$env:USERNAME:(OI)(CI)(WO)"`,
219
247
  ` cmd: icacls "${path}" /grant "%USERNAME%:(OI)(CI)(WO)"`,
220
248
  'WRITE_OWNER is exactly the right the prerequisite names, so this grants nothing the harness did not ask for,',
221
249
  'and (OI)(CI) makes the ACE inheritable, so one command reaches the workspace\'s existing subdirectories.',
222
250
  'Full control works just as well — the same line with `F` in place of `(WO)`:',
223
251
  ` icacls "${path}" /grant "$env:USERNAME:(OI)(CI)F"`,
224
- 'Both assume you own the directory: owner-implicit rights cover the DACL half of the merged write, so',
225
- 'WRITE_OWNER is the single missing piece. A directory owned by someone else is a bigger change than a',
226
- 'one-liner — that is the harness\'s documented prerequisite, and it is why this failure is loud instead of',
227
- 'silently skipped.',
252
+ 'Why `(WO)` is the whole of what is missing there: an owner holds READ_CONTROL and WRITE_DAC implicitly,',
253
+ 'and WRITE_DAC is what `icacls /grant` itself needs — so the DACL half of the merged write already has what',
254
+ 'it wants, and WRITE_OWNER is the single missing piece.',
255
+ '',
256
+ 'IF YOU DO NOT OWN IT — a directory an installer or another account created, e.g. owner',
257
+ '`BUILTIN\\Administrators`:',
258
+ ' the line above cannot run at all. Changing a DACL takes WRITE_DAC, which you hold neither as owner nor',
259
+ ' through any ACE, so `icacls /grant` is refused with `Access is denied` — for the very command that would',
260
+ ' fix it. The merged write wants WRITE_DAC and WRITE_OWNER together, so `(WO)` alone would not be enough',
261
+ ' here even if it went through. Run the grant once from an account that already holds both — that is, from an',
262
+ ' ELEVATED prompt:',
263
+ ` icacls "${path}" /grant "<your-account>:(OI)(CI)F"`,
264
+ 'Full control is used because it is the rights set covering both halves; the other reach both reports name is',
265
+ 'to take ownership first, which also needs elevation (it wants SeTakeOwnership), after which the unelevated',
266
+ '`(WO)` line above applies:',
267
+ ` icacls "${path}" /setowner "<your-account>"`,
268
+ 'Or sidestep the ACL entirely: create the workspace under `%USERPROFILE%` — a directory created there',
269
+ 'inherits Full control for you — and open the session on that one.',
228
270
  '',
229
271
  ...nonFixes(failure, path),
230
272
  'How to read this: the harness documents the prerequisite (' + PREREQUISITE + ') and this',
package/lib/signature.js CHANGED
@@ -30,17 +30,32 @@
30
30
  * the advisory can quote the exact line the model and the user are looking
31
31
  * at, and the path can be re-used in the fix command.
32
32
  *
33
- * One signature covers two environments, and the text is identical in both, so
34
- * the classifier cannot and must not try to tell them apart: a workspace the
35
- * caller created with `mkdir` that inherits "Authenticated Users: Modify" from
36
- * the drive root (`#7622`, `#7646`, `#7720`), and a directory on a data volume
37
- * where **no ACE names the caller at all** so that the inherited entry is the
38
- * whole of their access (`#7750` on D:/E:, `#7735`'s second defect). Both are
39
- * the same gate — `WRITE_OWNER` on the directory — and the same one-line remedy
40
- * satisfies both, which is why they share a class and the advisory names both
41
- * shapes instead of guessing which one it is looking at. What the text *does*
42
- * carry that the failure does not is the version boundary: the merged
43
- * DACL + label write exists only from `0.1.7-alpha.1` on
33
+ * One signature covers three environments, the text is identical in all of them,
34
+ * and the classifier cannot and must not try to tell them apart — but the
35
+ * *remedy* is not the same in all of them, which is why the advisory forks on a
36
+ * check the user runs rather than on a guess this module cannot make:
37
+ *
38
+ * - a workspace the caller created with `mkdir` that inherits "Authenticated
39
+ * Users: Modify" from the drive root (`#7622`, `#7646`, `#7720`);
40
+ * - a directory on a data volume where **no ACE names the caller at all**, so
41
+ * the inherited entry is the whole of their access (`#7750` on D:/E:,
42
+ * `#7735`'s second defect);
43
+ * - a directory **the caller does not own** — an installer- or
44
+ * administrator-created one, `#7771` (owner `BUILTIN\Administrators`, held
45
+ * deny-only for that token), which `#7804` reached from the other direction.
46
+ *
47
+ * In the first two the caller is the owner, so their implicit `WRITE_DAC`
48
+ * satisfies the DACL half and `WRITE_OWNER` is the single missing right — one
49
+ * unelevated `icacls /grant` supplies exactly it, and `#7750` measured that
50
+ * remedy working. In the third `WRITE_DAC` is missing as well, so that same
51
+ * `icacls` is refused for the very command that would fix it and `(WO)` alone
52
+ * would not be enough even if it went through. The advisory therefore hands over
53
+ * the ownership check (`(Get-Acl "<dir>").Owner`) as the branch selector and
54
+ * gives each branch the command that works there — the same class, two rights
55
+ * situations, and no guess about which one this is.
56
+ *
57
+ * What the text *does* carry that the failure does not is the version boundary:
58
+ * the merged DACL + label write exists only from `0.1.7-alpha.1` on
44
59
  * (`packages/sandbox/sandbox-windows-acl/src/acl.ts`, flag
45
60
  * `DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION`), so on an older line
46
61
  * the same string belongs to a different cause space.
@@ -20,6 +20,24 @@
20
20
  * separates "this is the label failure" from "this is something else", and
21
21
  * because rolling back is the reach it invites while making the very problem
22
22
  * it closed come back.
23
+ *
24
+ * **The remedy is forked on ownership, because one command cannot serve both
25
+ * environments.** The reports split into two rights situations behind an
26
+ * identical error: a workspace **the caller owns** (`#7622`, `#7646`, `#7720`,
27
+ * `#7750`, `#7804`), where the owner's implicit `WRITE_DAC` satisfies the DACL
28
+ * half and `(WO)` is the whole of what is missing — so the `icacls /grant`
29
+ * that supplies it *can itself run*, unelevated; and a directory **the caller
30
+ * does not own** (`#7771`: owner `BUILTIN\Administrators`, held deny-only for
31
+ * their token), where `WRITE_DAC` is missing too, so `icacls /grant` is denied
32
+ * for the very command that would fix it, and `(WO)` alone would not be enough
33
+ * even if it went through. The classifier cannot tell these apart — the text is
34
+ * identical — so the advisory does what it can do instead of guessing: it hands
35
+ * over the **ownership check** (`(Get-Acl "<dir>").Owner`) as the branch
36
+ * selector, then gives each branch the command that actually works there, and
37
+ * says why the other branch's command is not a fallback. A single unconditional
38
+ * one-liner would send the second environment to a command that is refused
39
+ * before it runs — the same defect this module exists to answer, a remedy that
40
+ * does not work delivered confidently.
23
41
  * - **The persistent-shell failure** (`pty-startup`) is *not* fixable by the
24
42
  * caller — least of all by the model, which has no shell to run anything in.
25
43
  * So its advice says so and stops: the remedy is a user-side preset choice,
@@ -41,7 +59,7 @@
41
59
  import type { ProvisioningFailure, RecognizedFailure } from './signature.js';
42
60
  import type { SandboxModeName } from './mode.js';
43
61
  /** The upstream threads the ACL advisory is a stopgap for. */
44
- export declare const ACL_DISCUSSIONS = "#7538 / #7622 / #7646 / #7720 / #7750 / #7735";
62
+ export declare const ACL_DISCUSSIONS = "#7538 / #7622 / #7646 / #7720 / #7750 / #7735 / #7771 / #7804 / #7816";
45
63
  /** The upstream thread the persistent-shell advisory is a stopgap for. */
46
64
  export declare const PTY_DISCUSSIONS = "#7638";
47
65
  /** The documented prerequisite, quoted from the backend's README. */
@@ -30,17 +30,32 @@
30
30
  * the advisory can quote the exact line the model and the user are looking
31
31
  * at, and the path can be re-used in the fix command.
32
32
  *
33
- * One signature covers two environments, and the text is identical in both, so
34
- * the classifier cannot and must not try to tell them apart: a workspace the
35
- * caller created with `mkdir` that inherits "Authenticated Users: Modify" from
36
- * the drive root (`#7622`, `#7646`, `#7720`), and a directory on a data volume
37
- * where **no ACE names the caller at all** so that the inherited entry is the
38
- * whole of their access (`#7750` on D:/E:, `#7735`'s second defect). Both are
39
- * the same gate — `WRITE_OWNER` on the directory — and the same one-line remedy
40
- * satisfies both, which is why they share a class and the advisory names both
41
- * shapes instead of guessing which one it is looking at. What the text *does*
42
- * carry that the failure does not is the version boundary: the merged
43
- * DACL + label write exists only from `0.1.7-alpha.1` on
33
+ * One signature covers three environments, the text is identical in all of them,
34
+ * and the classifier cannot and must not try to tell them apart — but the
35
+ * *remedy* is not the same in all of them, which is why the advisory forks on a
36
+ * check the user runs rather than on a guess this module cannot make:
37
+ *
38
+ * - a workspace the caller created with `mkdir` that inherits "Authenticated
39
+ * Users: Modify" from the drive root (`#7622`, `#7646`, `#7720`);
40
+ * - a directory on a data volume where **no ACE names the caller at all**, so
41
+ * the inherited entry is the whole of their access (`#7750` on D:/E:,
42
+ * `#7735`'s second defect);
43
+ * - a directory **the caller does not own** — an installer- or
44
+ * administrator-created one, `#7771` (owner `BUILTIN\Administrators`, held
45
+ * deny-only for that token), which `#7804` reached from the other direction.
46
+ *
47
+ * In the first two the caller is the owner, so their implicit `WRITE_DAC`
48
+ * satisfies the DACL half and `WRITE_OWNER` is the single missing right — one
49
+ * unelevated `icacls /grant` supplies exactly it, and `#7750` measured that
50
+ * remedy working. In the third `WRITE_DAC` is missing as well, so that same
51
+ * `icacls` is refused for the very command that would fix it and `(WO)` alone
52
+ * would not be enough even if it went through. The advisory therefore hands over
53
+ * the ownership check (`(Get-Acl "<dir>").Owner`) as the branch selector and
54
+ * gives each branch the command that works there — the same class, two rights
55
+ * situations, and no guess about which one this is.
56
+ *
57
+ * What the text *does* carry that the failure does not is the version boundary:
58
+ * the merged DACL + label write exists only from `0.1.7-alpha.1` on
44
59
  * (`packages/sandbox/sandbox-windows-acl/src/acl.ts`, flag
45
60
  * `DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION`), so on an older line
46
61
  * the same string belongs to a different cause space.
@@ -73,10 +88,14 @@
73
88
  export type FailureClass =
74
89
  /**
75
90
  * `SetNamedSecurityInfoW` returned `ERROR_ACCESS_DENIED` (5): the merged
76
- * DACL + label write was refused. Both environments in the module doc land
77
- * here — the `mkdir`-inherited Modify workspace and the data-volume directory
78
- * with no ACE naming the caller — because the failure text cannot separate
79
- * them and the remedy is the same `WRITE_OWNER` grant.
91
+ * DACL + label write was refused. All three environments in the module doc
92
+ * land here — the `mkdir`-inherited Modify workspace, the data-volume
93
+ * directory with no ACE naming the caller, and the directory owned by another
94
+ * account — because the failure text cannot separate them. The first two are
95
+ * the same rights situation (the caller owns it; `WRITE_OWNER` is the whole of
96
+ * what is missing) and share the unelevated remedy; the third is missing
97
+ * `WRITE_DAC` as well, which is why the advisory hands over the ownership
98
+ * check and forks the command on it.
80
99
  */
81
100
  'apply-denied'
82
101
  /** `SetNamedSecurityInfoW` failed with a Win32 code other than `ERROR_ACCESS_DENIED`. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@argszero/cordis-plugin-sandbox-grant-advisor",
3
- "description": "Turns two sandbox environment failures that name neither their cause nor a remedy into a diagnosis with a path forward. Family 1, the Windows workspace ACL: six reports (#7538, #7622, #7646, #7720, #7750, #7735) of one signature — 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. The plugin observes the public `tools/post-execute` waterfall, classifies both signatures narrowly (only the two `...NamedSecurityInfoW` operations; the PTY message matched on a whole line, never as a substring, and 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, and a data volume where no ACE names the caller at all), 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 discriminator, the unelevated `icacls ... :(OI)(CI)(WO)` fix — the narrowest form of the exact right the backend's prerequisite names, with Full control offered second — and the two remedies that look right and are not (`takeown`, `icacls /reset`), each with the reason it fails; 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 — it never names a shell tool the failing composition does not mount. 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, 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.4.0",
3
+ "description": "Turns two sandbox environment failures that name neither their cause nor a remedy into a diagnosis with a path forward. Family 1, the Windows workspace ACL: nine reports (#7538, #7622, #7646, #7720, #7750, #7735, #7771, #7804, #7816) of one signature — 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. The plugin observes the public `tools/post-execute` waterfall, classifies both signatures narrowly (only the two `...NamedSecurityInfoW` operations; the PTY message matched on a whole line, never as a substring, and 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 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 — the owner's implicit WRITE_DAC already covers the DACL half — 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% — and the two remedies that look right and are not (`takeown`, `icacls /reset`), each with the reason it fails; 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 — it never names a shell tool the failing composition does not mount. 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, 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.5.0",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",