@argszero/cordis-plugin-sandbox-grant-advisor 0.2.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 +72 -11
- package/lib/advice.js +97 -7
- package/lib/index.js +12 -4
- package/lib/signature.js +15 -0
- package/lib/types/advice.d.ts +20 -1
- package/lib/types/index.d.ts +12 -4
- package/lib/types/signature.d.ts +22 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -30,9 +30,22 @@ stuck.
|
|
|
30
30
|
|
|
31
31
|
### 1. Workspace provisioning — the Windows ACL failure (`acl-provisioning`)
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
[discussion #7646]
|
|
35
|
-
|
|
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.
|
|
39
|
+
|
|
40
|
+
`#7720` is worth reading for where the failure lands: the grant is materialized
|
|
41
|
+
at sandbox **initialization**, so this is not one refused operation but *every*
|
|
42
|
+
shell tool at once — the reporter could not run `netstat` or even `icacls` to
|
|
43
|
+
diagnose the error they were staring at (on `0.1.5-rc.3` the same directory
|
|
44
|
+
worked, because confinement was skipped silently rather than failing closed).
|
|
45
|
+
They also report the two remedies that look right and are not
|
|
46
|
+
(`takeown /F <dir> /R /D Y`, `icacls <dir> /reset /T /C`), which is why the
|
|
47
|
+
advisory names them with the reason each fails instead of leaving the reader to
|
|
48
|
+
discover it.
|
|
36
49
|
|
|
37
50
|
The Windows backend provisions a workspace by writing the directory's DACL and
|
|
38
51
|
its mandatory-integrity label in **one** `SetNamedSecurityInfoW` call
|
|
@@ -55,7 +68,26 @@ Two consequences follow from that one line:
|
|
|
55
68
|
is the token-privilege form of the same idea, and it is the hypothesis the
|
|
56
69
|
reports naturally reach for — `whoami /priv` cannot tell the two apart,
|
|
57
70
|
because `WRITE_OWNER` is an object right and never appears in that table.
|
|
58
|
-
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.
|
|
59
91
|
|
|
60
92
|
The grant is materialized lazily, on the first confined call, and **nothing is
|
|
61
93
|
cached when it throws** — so the same failure repeats per command (850 calls
|
|
@@ -73,12 +105,24 @@ What was reported:
|
|
|
73
105
|
Why it is refused while the directory looks writable: that call is a MERGED write ...
|
|
74
106
|
... the label half additionally needs WRITE_OWNER on the directory. ...
|
|
75
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
|
+
|
|
76
113
|
Confirm the cause (unelevated) — `icacls` is a normal user command:
|
|
77
114
|
icacls "D:\ws"
|
|
78
115
|
|
|
79
116
|
Fix it (unelevated, one line) and then run the command again:
|
|
80
|
-
PowerShell: icacls "D:\ws" /grant "$env:USERNAME:(OI)(CI)
|
|
81
|
-
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)"
|
|
119
|
+
|
|
120
|
+
What will NOT fix it — both look like the right move, and both were tried and reported:
|
|
121
|
+
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.
|
|
124
|
+
icacls "D:\ws" /reset /T /C
|
|
125
|
+
restores inheritance, and inheritance is what supplied the Modify-only ACE above.
|
|
82
126
|
```
|
|
83
127
|
|
|
84
128
|
### 2. Persistent shell startup (`pty-startup`)
|
|
@@ -277,6 +321,12 @@ than one that stays silent.
|
|
|
277
321
|
replace it.** That guard keys on **call identity** (identical arguments
|
|
278
322
|
retried); this one keys on the **environment signature**, which is how several
|
|
279
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.
|
|
280
330
|
- **The real fix is upstream, in both families.** For the ACL failure,
|
|
281
331
|
`grantWrite` already computes `hasExactGrant` / `hasExactDeny` /
|
|
282
332
|
`hasExactLabel` and discards which one was false, so the diagnostic that turns
|
|
@@ -307,11 +357,19 @@ composition that does not mount the service degrades to silence instead of
|
|
|
307
357
|
failing to load. See `src/mode.ts`.
|
|
308
358
|
|
|
309
359
|
Probed at the newest build of every line the range admits — `0.1.2-rc.1`,
|
|
310
|
-
`0.1.3-alpha.2`, `0.1.5-rc.3`, `0.1.6-alpha.2`, `0.1.7-rc.
|
|
311
|
-
|
|
312
|
-
this range, installs each one from the registry
|
|
313
|
-
|
|
314
|
-
|
|
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.
|
|
315
373
|
|
|
316
374
|
## Development
|
|
317
375
|
|
|
@@ -338,4 +396,7 @@ the current runtime cannot distinguish rather than counting it as a pass.
|
|
|
338
396
|
[discussion #7538]: https://github.com/deepseek-ai/deepseek-harness/discussions/7538
|
|
339
397
|
[discussion #7622]: https://github.com/deepseek-ai/deepseek-harness/discussions/7622
|
|
340
398
|
[discussion #7646]: https://github.com/deepseek-ai/deepseek-harness/discussions/7646
|
|
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
|
|
341
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';
|
|
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. */
|
|
@@ -57,6 +64,21 @@ export const GLOBAL_PATCH = '$DSH_HOME/cordis.patch.yml';
|
|
|
57
64
|
export const MINIMAL_PRESET_ROW = 'preset-minimal';
|
|
58
65
|
/** The one-shot shell tool the `standard` preset mounts on Windows. */
|
|
59
66
|
export const ONE_SHOT_SHELL = '@deepseek-ai/dsh-tool-pwsh';
|
|
67
|
+
/**
|
|
68
|
+
* The two remedies that look like the fix and are not.
|
|
69
|
+
*
|
|
70
|
+
* Both were applied by the reporter of `#7720` before finding the one that
|
|
71
|
+
* works, and both are the *natural* reach: making yourself the owner and
|
|
72
|
+
* resetting the directory's ACL are how one normally repairs a Windows
|
|
73
|
+
* permission problem. They fail here for two different reasons, and naming the
|
|
74
|
+
* reason is what makes this section worth its lines — a reader who already
|
|
75
|
+
* tried them learns why, and a reader who has not is spared the attempt. See
|
|
76
|
+
* {@link nonFixes} for when this is emitted.
|
|
77
|
+
*/
|
|
78
|
+
export const NOT_FIXES = [
|
|
79
|
+
'takeown /F "<dir>" /R /D Y',
|
|
80
|
+
'icacls "<dir>" /reset /T /C',
|
|
81
|
+
];
|
|
60
82
|
/** Placeholder the user replaces with the directory the error named. */
|
|
61
83
|
const PLACEHOLDER = '<the directory from the error line above>';
|
|
62
84
|
/**
|
|
@@ -72,7 +94,10 @@ function diagnosis(failure) {
|
|
|
72
94
|
'mandatory-integrity label go out as one `SetNamedSecurityInfoW`. The label lives in the SACL, and the',
|
|
73
95
|
"owner's implicit rights cover only READ_CONTROL and WRITE_DAC, so the label half additionally needs",
|
|
74
96
|
'WRITE_OWNER on the directory. A workspace created with `mkdir` normally inherits',
|
|
75
|
-
'"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.',
|
|
76
101
|
'This is a directory ACL fact, not a token privilege: `whoami /priv` will not show it, and',
|
|
77
102
|
'SeSecurityPrivilege is the wrong lever here.',
|
|
78
103
|
].join('\n');
|
|
@@ -91,6 +116,59 @@ function diagnosis(failure) {
|
|
|
91
116
|
].join('\n');
|
|
92
117
|
}
|
|
93
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* The "this is not the fix" lines for one class of failure.
|
|
121
|
+
*
|
|
122
|
+
* Emitted only for `apply-denied`, the class whose whole diagnosis is the
|
|
123
|
+
* missing `WRITE_OWNER` right — because only there is the claim true:
|
|
124
|
+
*
|
|
125
|
+
* - `read-denied` wants `READ_CONTROL`, and taking ownership *does* carry it,
|
|
126
|
+
* so calling `takeown` a non-fix there would be false.
|
|
127
|
+
* - `apply-other` already says the missing-rights story does not apply
|
|
128
|
+
* verbatim, so a section that presupposes it would contradict its own
|
|
129
|
+
* diagnosis.
|
|
130
|
+
*
|
|
131
|
+
* A negative claim still has to be earned: the failure to avoid is advice that
|
|
132
|
+
* is confidently wrong in the other direction.
|
|
133
|
+
* @param failure - the recognized provisioning failure.
|
|
134
|
+
* @param path - the directory the error named, or the placeholder.
|
|
135
|
+
* @returns the section's lines, or an empty array for a class it does not fit.
|
|
136
|
+
*/
|
|
137
|
+
function nonFixes(failure, path) {
|
|
138
|
+
if (failure.klass !== 'apply-denied')
|
|
139
|
+
return [];
|
|
140
|
+
return [
|
|
141
|
+
'What will NOT fix it — both look like the right move, and both were tried and reported:',
|
|
142
|
+
` 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.',
|
|
145
|
+
` icacls "${path}" /reset /T /C`,
|
|
146
|
+
' restores inheritance, and inheritance is what supplied the Modify-only ACE above.',
|
|
147
|
+
'',
|
|
148
|
+
];
|
|
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
|
+
}
|
|
94
172
|
/**
|
|
95
173
|
* Build the advisory attached to the failing tool result.
|
|
96
174
|
*
|
|
@@ -128,15 +206,27 @@ function aclAdvisory(failure, href) {
|
|
|
128
206
|
'',
|
|
129
207
|
diagnosis(failure),
|
|
130
208
|
'',
|
|
209
|
+
versionBoundary(),
|
|
210
|
+
'',
|
|
131
211
|
'Confirm the cause (unelevated) — `icacls` is a normal user command:',
|
|
132
212
|
` icacls "${path}"`,
|
|
133
|
-
'Look for an ACE that names YOUR OWN account (run `whoami` if unsure) with (F) / Full control
|
|
134
|
-
'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.',
|
|
135
216
|
'',
|
|
136
217
|
'Fix it (unelevated, one line) and then run the command again:',
|
|
137
|
-
` PowerShell: icacls "${path}" /grant "$env:USERNAME:(OI)(CI)
|
|
138
|
-
` 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.',
|
|
139
228
|
'',
|
|
229
|
+
...nonFixes(failure, path),
|
|
140
230
|
'How to read this: the harness documents the prerequisite (' + PREREQUISITE + ') and this',
|
|
141
231
|
'error does not name it yet, so the advice is delivered here instead. This is a stopgap, ' + where + '.',
|
|
142
232
|
'What it is NOT: this plugin neither edits ACLs nor elevates — the command above is yours to run.',
|
|
@@ -219,7 +309,7 @@ export function denialText(failure, observed, denial, maxDenials) {
|
|
|
219
309
|
'',
|
|
220
310
|
'Retrying cannot succeed — the sandbox cannot start a command until the directory grant applies.',
|
|
221
311
|
'Stop, and either apply the fix or hand the problem to the user:',
|
|
222
|
-
failure.path === undefined ? '' : ` icacls "${failure.path}" /grant "$env:USERNAME:(OI)(CI)
|
|
312
|
+
failure.path === undefined ? '' : ` icacls "${failure.path}" /grant "$env:USERNAME:(OI)(CI)(WO)"`,
|
|
223
313
|
'',
|
|
224
314
|
suffix > 0
|
|
225
315
|
? `This is automatic block ${String(denial)} of ${String(maxDenials)}; after that the call is allowed again.`
|
package/lib/index.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* ## The two failures it recognizes
|
|
6
6
|
*
|
|
7
|
-
* **Workspace provisioning (Windows ACL).**
|
|
8
|
-
* (`#7538`, `#7622`, `#7646`) describe the same shape: the host-side write grant
|
|
7
|
+
* **Workspace provisioning (Windows ACL).** Four reports of one signature
|
|
8
|
+
* (`#7538`, `#7622`, `#7646`, `#7720`) describe the same shape: the host-side write grant
|
|
9
9
|
* for a sandboxed workspace cannot be applied, every sandboxed command then
|
|
10
10
|
* fails identically **before it runs**, and the error text is a bare Win32 line:
|
|
11
11
|
*
|
|
@@ -18,6 +18,13 @@
|
|
|
18
18
|
* caller `WRITE_OWNER` — never reaches the user, so sessions escape into
|
|
19
19
|
* `danger-full-access` or die on the model's output cap.
|
|
20
20
|
*
|
|
21
|
+
* `#7720` sharpens where this lands: because the grant is materialized at
|
|
22
|
+
* sandbox *initialization*, that failure takes **every** shell tool with it, not
|
|
23
|
+
* one operation — the reporter could not run `netstat` or `icacls` to diagnose
|
|
24
|
+
* the failure they were looking at. It also contributes the two remedies that
|
|
25
|
+
* look right and are not (`takeown /R /D Y`, `icacls /reset /T /C`), which the
|
|
26
|
+
* ACL advisory now names along with the reason each fails.
|
|
27
|
+
*
|
|
21
28
|
* **Persistent shell startup (#7638).** With the `minimal` preset on Windows the
|
|
22
29
|
* only shell tool is a persistent PTY (`dsh-terminal-bash` +
|
|
23
30
|
* `dsh-tool-pwsh-persistent`), and under a *confining* sandbox mode every call
|
|
@@ -59,8 +66,9 @@
|
|
|
59
66
|
* failure of a family, the failing tool result is enriched with a user-role
|
|
60
67
|
* notice. For the ACL family it names the missing right (`WRITE_OWNER` on the
|
|
61
68
|
* directory, not `SeSecurityPrivilege`), gives the unelevated one-line
|
|
62
|
-
* `icacls` remedy,
|
|
63
|
-
* directory from a wrong prerequisite
|
|
69
|
+
* `icacls` remedy, gives the discriminator that separates a Modify-only
|
|
70
|
+
* directory from a wrong prerequisite, and names the two remedies that look
|
|
71
|
+
* right and are not (`takeown`, `icacls /reset`), each with its reason. For the PTY family it names the
|
|
64
72
|
* combination that fails (persistent PTY × a confining mode), states the
|
|
65
73
|
* resolved mode, says plainly that no command can fix it, and hands the
|
|
66
74
|
* user-side preset choice over. Both ride `additionalContexts`, so the model
|
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";
|
|
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. */
|
|
@@ -58,6 +65,18 @@ export declare const GLOBAL_PATCH = "$DSH_HOME/cordis.patch.yml";
|
|
|
58
65
|
export declare const MINIMAL_PRESET_ROW = "preset-minimal";
|
|
59
66
|
/** The one-shot shell tool the `standard` preset mounts on Windows. */
|
|
60
67
|
export declare const ONE_SHOT_SHELL = "@deepseek-ai/dsh-tool-pwsh";
|
|
68
|
+
/**
|
|
69
|
+
* The two remedies that look like the fix and are not.
|
|
70
|
+
*
|
|
71
|
+
* Both were applied by the reporter of `#7720` before finding the one that
|
|
72
|
+
* works, and both are the *natural* reach: making yourself the owner and
|
|
73
|
+
* resetting the directory's ACL are how one normally repairs a Windows
|
|
74
|
+
* permission problem. They fail here for two different reasons, and naming the
|
|
75
|
+
* reason is what makes this section worth its lines — a reader who already
|
|
76
|
+
* tried them learns why, and a reader who has not is spared the attempt. See
|
|
77
|
+
* {@link nonFixes} for when this is emitted.
|
|
78
|
+
*/
|
|
79
|
+
export declare const NOT_FIXES: readonly ["takeown /F \"<dir>\" /R /D Y", "icacls \"<dir>\" /reset /T /C"];
|
|
61
80
|
/** What the caller knows about the failing call, beyond the failure text. */
|
|
62
81
|
export interface AdvisoryContext {
|
|
63
82
|
/** URL quoted in place of the discussions list; optional. */
|
package/lib/types/index.d.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* ## The two failures it recognizes
|
|
6
6
|
*
|
|
7
|
-
* **Workspace provisioning (Windows ACL).**
|
|
8
|
-
* (`#7538`, `#7622`, `#7646`) describe the same shape: the host-side write grant
|
|
7
|
+
* **Workspace provisioning (Windows ACL).** Four reports of one signature
|
|
8
|
+
* (`#7538`, `#7622`, `#7646`, `#7720`) describe the same shape: the host-side write grant
|
|
9
9
|
* for a sandboxed workspace cannot be applied, every sandboxed command then
|
|
10
10
|
* fails identically **before it runs**, and the error text is a bare Win32 line:
|
|
11
11
|
*
|
|
@@ -18,6 +18,13 @@
|
|
|
18
18
|
* caller `WRITE_OWNER` — never reaches the user, so sessions escape into
|
|
19
19
|
* `danger-full-access` or die on the model's output cap.
|
|
20
20
|
*
|
|
21
|
+
* `#7720` sharpens where this lands: because the grant is materialized at
|
|
22
|
+
* sandbox *initialization*, that failure takes **every** shell tool with it, not
|
|
23
|
+
* one operation — the reporter could not run `netstat` or `icacls` to diagnose
|
|
24
|
+
* the failure they were looking at. It also contributes the two remedies that
|
|
25
|
+
* look right and are not (`takeown /R /D Y`, `icacls /reset /T /C`), which the
|
|
26
|
+
* ACL advisory now names along with the reason each fails.
|
|
27
|
+
*
|
|
21
28
|
* **Persistent shell startup (#7638).** With the `minimal` preset on Windows the
|
|
22
29
|
* only shell tool is a persistent PTY (`dsh-terminal-bash` +
|
|
23
30
|
* `dsh-tool-pwsh-persistent`), and under a *confining* sandbox mode every call
|
|
@@ -59,8 +66,9 @@
|
|
|
59
66
|
* failure of a family, the failing tool result is enriched with a user-role
|
|
60
67
|
* notice. For the ACL family it names the missing right (`WRITE_OWNER` on the
|
|
61
68
|
* directory, not `SeSecurityPrivilege`), gives the unelevated one-line
|
|
62
|
-
* `icacls` remedy,
|
|
63
|
-
* directory from a wrong prerequisite
|
|
69
|
+
* `icacls` remedy, gives the discriminator that separates a Modify-only
|
|
70
|
+
* directory from a wrong prerequisite, and names the two remedies that look
|
|
71
|
+
* right and are not (`takeown`, `icacls /reset`), each with its reason. For the PTY family it names the
|
|
64
72
|
* combination that fails (persistent PTY × a confining mode), states the
|
|
65
73
|
* resolved mode, says plainly that no command can fix it, and hands the
|
|
66
74
|
* user-side preset choice over. Both ride `additionalContexts`, so the model
|
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",
|