@argszero/cordis-plugin-sandbox-grant-advisor 0.3.0 → 0.4.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 +54 -12
- package/lib/advice.js +50 -7
- package/lib/signature.js +15 -0
- package/lib/types/advice.d.ts +8 -1
- package/lib/types/signature.d.ts +22 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -30,10 +30,12 @@ stuck.
|
|
|
30
30
|
|
|
31
31
|
### 1. Workspace provisioning — the Windows ACL failure (`acl-provisioning`)
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
[discussion #7646], [discussion #7720]
|
|
35
|
-
the
|
|
36
|
-
|
|
33
|
+
Six reports describe this exact line: [discussion #7538], [discussion #7622],
|
|
34
|
+
[discussion #7646], [discussion #7720], [discussion #7750], [discussion #7735]
|
|
35
|
+
(the last two on data-volume workspaces, where *no* ACE names the caller at all —
|
|
36
|
+
the inherited `Authenticated Users: Modify` is the whole of their access). In each
|
|
37
|
+
one every sandboxed command fails the same way, before it runs, and the error names
|
|
38
|
+
neither the missing right nor a remedy.
|
|
37
39
|
|
|
38
40
|
`#7720` is worth reading for where the failure lands: the grant is materialized
|
|
39
41
|
at sandbox **initialization**, so this is not one refused operation but *every*
|
|
@@ -66,7 +68,26 @@ Two consequences follow from that one line:
|
|
|
66
68
|
is the token-privilege form of the same idea, and it is the hypothesis the
|
|
67
69
|
reports naturally reach for — `whoami /priv` cannot tell the two apart,
|
|
68
70
|
because `WRITE_OWNER` is an object right and never appears in that table.
|
|
69
|
-
Granting
|
|
71
|
+
Granting `WRITE_OWNER` on the workspace root needs **no** elevation. `#7735`
|
|
72
|
+
settles the gate with an isolation table on one machine and one unprivileged
|
|
73
|
+
account: the label write succeeds with Full control *or with Take-ownership
|
|
74
|
+
alone*, and fails with `ChangePermissions`, `ReadPermissions` or `Modify`
|
|
75
|
+
alone — so the gate is `WRITE_OWNER` and nothing else.
|
|
76
|
+
3. **The remedy is the narrowest form of that right.** The advisory recommends
|
|
77
|
+
`icacls <dir> /grant "<user>:(OI)(CI)(WO)"` — `WO` *is* `WRITE_OWNER`, i.e.
|
|
78
|
+
literally the right the documented prerequisite names, so the one-liner grants
|
|
79
|
+
nothing the harness did not ask for — and offers Full control second, as the
|
|
80
|
+
same line with `F` in place of `(WO)`. Both assume the caller owns the
|
|
81
|
+
directory: owner-implicit rights cover the DACL half of the merged write.
|
|
82
|
+
4. **The version boundary is stated, because the error cannot carry it.** Up to
|
|
83
|
+
`0.1.6-alpha.x` the backend's `SetNamedSecurityInfoW` wrote the DACL only
|
|
84
|
+
(flag 4) and a Modify-only workspace provisioned fine; the mandatory label —
|
|
85
|
+
and with it the SACL, flag 20 — arrives in `0.1.7-alpha.1`. So the same string
|
|
86
|
+
on an older line belongs to a different cause space, and the natural reach
|
|
87
|
+
(downgrade to the build that "worked") is refused with its reason: the label is
|
|
88
|
+
what confines deletes to the workspace, and reverting it reintroduces the
|
|
89
|
+
escape it closed. `#7750` asks for exactly this and explains why it is a
|
|
90
|
+
usability regression traded for a security fix.
|
|
70
91
|
|
|
71
92
|
The grant is materialized lazily, on the first confined call, and **nothing is
|
|
72
93
|
cached when it throws** — so the same failure repeats per command (850 calls
|
|
@@ -84,12 +105,17 @@ What was reported:
|
|
|
84
105
|
Why it is refused while the directory looks writable: that call is a MERGED write ...
|
|
85
106
|
... the label half additionally needs WRITE_OWNER on the directory. ...
|
|
86
107
|
|
|
108
|
+
A version boundary worth knowing before reaching for an older build: the label half is new to this package.
|
|
109
|
+
Up to `0.1.6-alpha.x` the backend touched the DACL only (flag 4) ... `0.1.7-alpha.1` is where the mandatory
|
|
110
|
+
label — and with it the SACL, flag 20 — arrives. ... Rolling back is not the fix either: the label is what
|
|
111
|
+
confines deletes to the workspace, and reverting it reintroduces the escape it closed.
|
|
112
|
+
|
|
87
113
|
Confirm the cause (unelevated) — `icacls` is a normal user command:
|
|
88
114
|
icacls "D:\ws"
|
|
89
115
|
|
|
90
116
|
Fix it (unelevated, one line) and then run the command again:
|
|
91
|
-
PowerShell: icacls "D:\ws" /grant "$env:USERNAME:(OI)(CI)
|
|
92
|
-
cmd: icacls "D:\ws" /grant "%USERNAME%:(OI)(CI)
|
|
117
|
+
PowerShell: icacls "D:\ws" /grant "$env:USERNAME:(OI)(CI)(WO)"
|
|
118
|
+
cmd: icacls "D:\ws" /grant "%USERNAME%:(OI)(CI)(WO)"
|
|
93
119
|
|
|
94
120
|
What will NOT fix it — both look like the right move, and both were tried and reported:
|
|
95
121
|
takeown /F "D:\ws" /R /D Y
|
|
@@ -295,6 +321,12 @@ than one that stays silent.
|
|
|
295
321
|
replace it.** That guard keys on **call identity** (identical arguments
|
|
296
322
|
retried); this one keys on the **environment signature**, which is how several
|
|
297
323
|
*different* commands share one cause. Mounting both is sensible.
|
|
324
|
+
- **The plugin cannot see the launcher half of `#7735`.** The Low label's other
|
|
325
|
+
side effect — the shell's publisher confirmation before launching a
|
|
326
|
+
Low-integrity `.bat`/`.cmd`/`.exe` — never appears in a tool result, so it is
|
|
327
|
+
outside the seam this plugin subscribes to. The report and its proposed fix
|
|
328
|
+
stay with the maintainers; all this plugin can do is explain the provisioning
|
|
329
|
+
failure that shares its root.
|
|
298
330
|
- **The real fix is upstream, in both families.** For the ACL failure,
|
|
299
331
|
`grantWrite` already computes `hasExactGrant` / `hasExactDeny` /
|
|
300
332
|
`hasExactLabel` and discards which one was false, so the diagnostic that turns
|
|
@@ -325,11 +357,19 @@ composition that does not mount the service degrades to silence instead of
|
|
|
325
357
|
failing to load. See `src/mode.ts`.
|
|
326
358
|
|
|
327
359
|
Probed at the newest build of every line the range admits — `0.1.2-rc.1`,
|
|
328
|
-
`0.1.3-alpha.2`, `0.1.5-rc.3`, `0.1.6-alpha.2`, `0.1.7-rc.
|
|
329
|
-
|
|
330
|
-
this range, installs each one from the registry
|
|
331
|
-
|
|
332
|
-
|
|
360
|
+
`0.1.3-alpha.2`, `0.1.5-rc.3`, `0.1.6-alpha.2`, `0.1.7-rc.2` (the newest build of
|
|
361
|
+
the line the later Windows reports ran on) — with `npm run test:probe-lines`,
|
|
362
|
+
which derives those builds from this range, installs each one from the registry
|
|
363
|
+
into a scratch tree and runs the suite against it. `0.1.7-rc.1`, the build the
|
|
364
|
+
third report ran, is admitted by the same `||` segment and was probed while it was
|
|
365
|
+
the newest of that line.
|
|
366
|
+
|
|
367
|
+
The whole set is re-probed whenever this package's source changes rather than
|
|
368
|
+
carried over from an earlier version: the range is a claim about *this* build of
|
|
369
|
+
the plugin, so `0.4.0` re-ran all five lines above. A line whose probe fails is
|
|
370
|
+
removed from the range rather than left claimed. The scratch tree's resolved
|
|
371
|
+
versions are the ones to read back when a probe is quoted as evidence — the probe
|
|
372
|
+
script pins them by exact version, and `--keep` leaves the tree in place to check.
|
|
333
373
|
|
|
334
374
|
## Development
|
|
335
375
|
|
|
@@ -357,4 +397,6 @@ the current runtime cannot distinguish rather than counting it as a pass.
|
|
|
357
397
|
[discussion #7622]: https://github.com/deepseek-ai/deepseek-harness/discussions/7622
|
|
358
398
|
[discussion #7646]: https://github.com/deepseek-ai/deepseek-harness/discussions/7646
|
|
359
399
|
[discussion #7720]: https://github.com/deepseek-ai/deepseek-harness/discussions/7720
|
|
400
|
+
[discussion #7750]: https://github.com/deepseek-ai/deepseek-harness/discussions/7750
|
|
401
|
+
[discussion #7735]: https://github.com/deepseek-ai/deepseek-harness/discussions/7735
|
|
360
402
|
[discussion #7638]: https://github.com/deepseek-ai/deepseek-harness/discussions/7638
|
package/lib/advice.js
CHANGED
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
* the caller can grant itself with `icacls`, unelevated. It is **not**
|
|
14
14
|
* `SeSecurityPrivilege`, the token privilege the reports naturally reach for;
|
|
15
15
|
* `whoami /priv` cannot show the difference, and elevation is the wrong lever.
|
|
16
|
+
* The remedy is the **narrowest** form of that grant — `(WO)` alone, which is
|
|
17
|
+
* literally the right the backend's prerequisite names — with Full control
|
|
18
|
+
* offered as the broad alternative; and the advisory carries the **version
|
|
19
|
+
* boundary** the label introduced (`0.1.7-alpha.1`), because that is what
|
|
20
|
+
* separates "this is the label failure" from "this is something else", and
|
|
21
|
+
* because rolling back is the reach it invites while making the very problem
|
|
22
|
+
* it closed come back.
|
|
16
23
|
* - **The persistent-shell failure** (`pty-startup`) is *not* fixable by the
|
|
17
24
|
* caller — least of all by the model, which has no shell to run anything in.
|
|
18
25
|
* So its advice says so and stops: the remedy is a user-side preset choice,
|
|
@@ -33,7 +40,7 @@
|
|
|
33
40
|
*/
|
|
34
41
|
import { failureLine } from './signature.js';
|
|
35
42
|
/** The upstream threads the ACL advisory is a stopgap for. */
|
|
36
|
-
export const ACL_DISCUSSIONS = '#7538 / #7622 / #7646 / #7720';
|
|
43
|
+
export const ACL_DISCUSSIONS = '#7538 / #7622 / #7646 / #7720 / #7750 / #7735';
|
|
37
44
|
/** The upstream thread the persistent-shell advisory is a stopgap for. */
|
|
38
45
|
export const PTY_DISCUSSIONS = '#7638';
|
|
39
46
|
/** The documented prerequisite, quoted from the backend's README. */
|
|
@@ -87,7 +94,10 @@ function diagnosis(failure) {
|
|
|
87
94
|
'mandatory-integrity label go out as one `SetNamedSecurityInfoW`. The label lives in the SACL, and the',
|
|
88
95
|
"owner's implicit rights cover only READ_CONTROL and WRITE_DAC, so the label half additionally needs",
|
|
89
96
|
'WRITE_OWNER on the directory. A workspace created with `mkdir` normally inherits',
|
|
90
|
-
'"Authenticated Users: Modify" (`0x1301bf`) from the drive root — and that mask has neither right
|
|
97
|
+
'"Authenticated Users: Modify" (`0x1301bf`) from the drive root — and that mask has neither right; on a data',
|
|
98
|
+
'volume there may be no ACE naming you at all, so that inherited entry is the whole of your access. The two',
|
|
99
|
+
'halves go out as one call, so a refused label discards the write grant with it, and the sandbox then',
|
|
100
|
+
'refuses to start any command in the workspace instead of running it unconfined.',
|
|
91
101
|
'This is a directory ACL fact, not a token privilege: `whoami /priv` will not show it, and',
|
|
92
102
|
'SeSecurityPrivilege is the wrong lever here.',
|
|
93
103
|
].join('\n');
|
|
@@ -137,6 +147,28 @@ function nonFixes(failure, path) {
|
|
|
137
147
|
'',
|
|
138
148
|
];
|
|
139
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* When the label half arrived, and why an older build is not the remedy.
|
|
152
|
+
*
|
|
153
|
+
* Emitted for every class: the boundary is a fact about the package
|
|
154
|
+
* `sandbox-windows-acl` (whose `DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION`
|
|
155
|
+
* flag is what needs `WRITE_OWNER`), not about which of its two calls failed, so
|
|
156
|
+
* it is true wherever this module is willing to speak at all. It is also the one
|
|
157
|
+
* fact neither report could get from the error: the failure looks identical on a
|
|
158
|
+
* line where the label does not exist yet, and the build that introduced it is
|
|
159
|
+
* the natural thing to reach for and the wrong one to reach for.
|
|
160
|
+
* @returns the section's lines.
|
|
161
|
+
*/
|
|
162
|
+
function versionBoundary() {
|
|
163
|
+
return [
|
|
164
|
+
'A version boundary worth knowing before reaching for an older build: the label half is new to this package.',
|
|
165
|
+
'Up to `0.1.6-alpha.x` the backend touched the DACL only (flag 4), so a Modify-only workspace provisioned',
|
|
166
|
+
'fine; `0.1.7-alpha.1` is where the mandatory label — and with it the SACL, flag 20 — arrives. On a',
|
|
167
|
+
'`0.1.6-alpha.x`-or-older line this exact failure therefore belongs to a different cause space, while on any',
|
|
168
|
+
'`0.1.7-*` line it is this one. Rolling back is not the fix either: the label is what confines deletes to the',
|
|
169
|
+
'workspace, and reverting it reintroduces the escape it closed.',
|
|
170
|
+
].join('\n');
|
|
171
|
+
}
|
|
140
172
|
/**
|
|
141
173
|
* Build the advisory attached to the failing tool result.
|
|
142
174
|
*
|
|
@@ -174,14 +206,25 @@ function aclAdvisory(failure, href) {
|
|
|
174
206
|
'',
|
|
175
207
|
diagnosis(failure),
|
|
176
208
|
'',
|
|
209
|
+
versionBoundary(),
|
|
210
|
+
'',
|
|
177
211
|
'Confirm the cause (unelevated) — `icacls` is a normal user command:',
|
|
178
212
|
` icacls "${path}"`,
|
|
179
|
-
'Look for an ACE that names YOUR OWN account (run `whoami` if unsure) with (F) / Full control
|
|
180
|
-
'If the strongest entry naming you is (M) / Modify
|
|
213
|
+
'Look for an ACE that names YOUR OWN account (run `whoami` if unsure) with (F) / Full control or',
|
|
214
|
+
'(WO) / Write owner. If the strongest entry naming you is (M) / Modify — or no entry names you at all and',
|
|
215
|
+
'your access comes from an inherited `Authenticated Users:(M)` — that is this failure.',
|
|
181
216
|
'',
|
|
182
217
|
'Fix it (unelevated, one line) and then run the command again:',
|
|
183
|
-
` PowerShell: icacls "${path}" /grant "$env:USERNAME:(OI)(CI)
|
|
184
|
-
` cmd: icacls "${path}" /grant "%USERNAME%:(OI)(CI)
|
|
218
|
+
` PowerShell: icacls "${path}" /grant "$env:USERNAME:(OI)(CI)(WO)"`,
|
|
219
|
+
` cmd: icacls "${path}" /grant "%USERNAME%:(OI)(CI)(WO)"`,
|
|
220
|
+
'WRITE_OWNER is exactly the right the prerequisite names, so this grants nothing the harness did not ask for,',
|
|
221
|
+
'and (OI)(CI) makes the ACE inheritable, so one command reaches the workspace\'s existing subdirectories.',
|
|
222
|
+
'Full control works just as well — the same line with `F` in place of `(WO)`:',
|
|
223
|
+
` 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.',
|
|
185
228
|
'',
|
|
186
229
|
...nonFixes(failure, path),
|
|
187
230
|
'How to read this: the harness documents the prerequisite (' + PREREQUISITE + ') and this',
|
|
@@ -266,7 +309,7 @@ export function denialText(failure, observed, denial, maxDenials) {
|
|
|
266
309
|
'',
|
|
267
310
|
'Retrying cannot succeed — the sandbox cannot start a command until the directory grant applies.',
|
|
268
311
|
'Stop, and either apply the fix or hand the problem to the user:',
|
|
269
|
-
failure.path === undefined ? '' : ` icacls "${failure.path}" /grant "$env:USERNAME:(OI)(CI)
|
|
312
|
+
failure.path === undefined ? '' : ` icacls "${failure.path}" /grant "$env:USERNAME:(OI)(CI)(WO)"`,
|
|
270
313
|
'',
|
|
271
314
|
suffix > 0
|
|
272
315
|
? `This is automatic block ${String(denial)} of ${String(maxDenials)}; after that the call is allowed again.`
|
package/lib/signature.js
CHANGED
|
@@ -30,6 +30,21 @@
|
|
|
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
|
|
44
|
+
* (`packages/sandbox/sandbox-windows-acl/src/acl.ts`, flag
|
|
45
|
+
* `DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION`), so on an older line
|
|
46
|
+
* the same string belongs to a different cause space.
|
|
47
|
+
*
|
|
33
48
|
* ## The persistent-shell startup failure (`pty-startup`)
|
|
34
49
|
*
|
|
35
50
|
* `dsh-terminal-bash` throws `PTY shell exited during startup` when the shell
|
package/lib/types/advice.d.ts
CHANGED
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
* the caller can grant itself with `icacls`, unelevated. It is **not**
|
|
14
14
|
* `SeSecurityPrivilege`, the token privilege the reports naturally reach for;
|
|
15
15
|
* `whoami /priv` cannot show the difference, and elevation is the wrong lever.
|
|
16
|
+
* The remedy is the **narrowest** form of that grant — `(WO)` alone, which is
|
|
17
|
+
* literally the right the backend's prerequisite names — with Full control
|
|
18
|
+
* offered as the broad alternative; and the advisory carries the **version
|
|
19
|
+
* boundary** the label introduced (`0.1.7-alpha.1`), because that is what
|
|
20
|
+
* separates "this is the label failure" from "this is something else", and
|
|
21
|
+
* because rolling back is the reach it invites while making the very problem
|
|
22
|
+
* it closed come back.
|
|
16
23
|
* - **The persistent-shell failure** (`pty-startup`) is *not* fixable by the
|
|
17
24
|
* caller — least of all by the model, which has no shell to run anything in.
|
|
18
25
|
* So its advice says so and stops: the remedy is a user-side preset choice,
|
|
@@ -34,7 +41,7 @@
|
|
|
34
41
|
import type { ProvisioningFailure, RecognizedFailure } from './signature.js';
|
|
35
42
|
import type { SandboxModeName } from './mode.js';
|
|
36
43
|
/** The upstream threads the ACL advisory is a stopgap for. */
|
|
37
|
-
export declare const ACL_DISCUSSIONS = "#7538 / #7622 / #7646 / #7720";
|
|
44
|
+
export declare const ACL_DISCUSSIONS = "#7538 / #7622 / #7646 / #7720 / #7750 / #7735";
|
|
38
45
|
/** The upstream thread the persistent-shell advisory is a stopgap for. */
|
|
39
46
|
export declare const PTY_DISCUSSIONS = "#7638";
|
|
40
47
|
/** The documented prerequisite, quoted from the backend's README. */
|
package/lib/types/signature.d.ts
CHANGED
|
@@ -30,6 +30,21 @@
|
|
|
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
|
|
44
|
+
* (`packages/sandbox/sandbox-windows-acl/src/acl.ts`, flag
|
|
45
|
+
* `DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION`), so on an older line
|
|
46
|
+
* the same string belongs to a different cause space.
|
|
47
|
+
*
|
|
33
48
|
* ## The persistent-shell startup failure (`pty-startup`)
|
|
34
49
|
*
|
|
35
50
|
* `dsh-terminal-bash` throws `PTY shell exited during startup` when the shell
|
|
@@ -56,7 +71,13 @@
|
|
|
56
71
|
*/
|
|
57
72
|
/** Which provisioning operation failed, and which diagnosis follows from it. */
|
|
58
73
|
export type FailureClass =
|
|
59
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* `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.
|
|
80
|
+
*/
|
|
60
81
|
'apply-denied'
|
|
61
82
|
/** `SetNamedSecurityInfoW` failed with a Win32 code other than `ERROR_ACCESS_DENIED`. */
|
|
62
83
|
| 'apply-other'
|
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:
|
|
4
|
-
"version": "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: 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",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|