@argszero/cordis-plugin-sandbox-grant-advisor 0.8.0 → 0.8.1
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 +27 -6
- package/lib/advice.js +6 -1
- package/lib/types/advice.d.ts +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -35,12 +35,24 @@ stuck.
|
|
|
35
35
|
|
|
36
36
|
### 1. Workspace provisioning — the Windows ACL failure (`acl-provisioning`)
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
[discussion #7646], [discussion #7720], [discussion #7750], [discussion #7735]
|
|
40
|
-
(the last
|
|
41
|
-
the inherited `Authenticated Users: Modify` is the whole of
|
|
42
|
-
one every sandboxed command fails the same way, before it
|
|
43
|
-
neither the missing right nor a remedy.
|
|
38
|
+
Seven reports describe this exact line: [discussion #7538], [discussion #7622],
|
|
39
|
+
[discussion #7646], [discussion #7720], [discussion #7750], [discussion #7735] and
|
|
40
|
+
[discussion #8232] (the last three on data-volume workspaces, where *no* ACE names
|
|
41
|
+
the caller at all — the inherited `Authenticated Users: Modify` is the whole of
|
|
42
|
+
their access). In each one every sandboxed command fails the same way, before it
|
|
43
|
+
runs, and the error names neither the missing right nor a remedy.
|
|
44
|
+
|
|
45
|
+
`#8232` contributes two facts about the *shape* of the failure rather than its
|
|
46
|
+
cause, and both are in the advisory now. One is that the failure belongs to the
|
|
47
|
+
**workspace, not the command**: there a `Get-Date` failed exactly like anything
|
|
48
|
+
that writes, because the grant is materialized before the command runs at all. The
|
|
49
|
+
other is the **scope of the repair**: the `(OI)(CI)(WO)` line covers *this*
|
|
50
|
+
directory and its children, so a second workspace root on the same volume is a
|
|
51
|
+
sibling rather than a child and needs the same line once more — which is why that
|
|
52
|
+
reporter saw a second workspace fail on a machine whose first one was already
|
|
53
|
+
repaired. The report also reaches the same root cause on its own (the merged write
|
|
54
|
+
wanting `WRITE_OWNER`, which owner-implicit rights do not carry), matching the
|
|
55
|
+
backend's documented prerequisite.
|
|
44
56
|
|
|
45
57
|
`#7720` is worth reading for where the failure lands: the grant is materialized
|
|
46
58
|
at sandbox **initialization**, so this is not one refused operation but *every*
|
|
@@ -93,6 +105,13 @@ Two consequences follow from that one line:
|
|
|
93
105
|
what confines deletes to the workspace, and reverting it reintroduces the
|
|
94
106
|
escape it closed. `#7750` asks for exactly this and explains why it is a
|
|
95
107
|
usability regression traded for a security fix.
|
|
108
|
+
5. **The repair is per-directory, and the report is what established that.** `#8232`
|
|
109
|
+
applied the `(WO)` line, watched the workspace start working, and then hit the
|
|
110
|
+
same error on a *second* workspace root on the same volume. `(OI)(CI)` carries
|
|
111
|
+
the ACE into *children* of the directory that received it and nowhere else, so a
|
|
112
|
+
sibling root is untouched by it. One line per workspace root is therefore the
|
|
113
|
+
correct shape of the remedy, not one line per machine — and the advisory says so,
|
|
114
|
+
because the natural reading of "it worked" is "it is fixed".
|
|
96
115
|
|
|
97
116
|
The grant is materialized lazily, on the first confined call, and **nothing is
|
|
98
117
|
cached when it throws** — so the same failure repeats per command (850 calls
|
|
@@ -656,6 +675,7 @@ the current runtime cannot distinguish rather than counting it as a pass.
|
|
|
656
675
|
[discussion #7771]: https://github.com/deepseek-ai/deepseek-harness/discussions/7771
|
|
657
676
|
[discussion #7804]: https://github.com/deepseek-ai/deepseek-harness/discussions/7804
|
|
658
677
|
[discussion #7816]: https://github.com/deepseek-ai/deepseek-harness/discussions/7816
|
|
678
|
+
[discussion #8232]: https://github.com/deepseek-ai/deepseek-harness/discussions/8232
|
|
659
679
|
[discussion #7638]: https://github.com/deepseek-ai/deepseek-harness/discussions/7638
|
|
660
680
|
[discussion #7876]: https://github.com/deepseek-ai/deepseek-harness/discussions/7876
|
|
661
681
|
[discussion #7877]: https://github.com/deepseek-ai/deepseek-harness/discussions/7877
|
|
@@ -665,6 +685,7 @@ the current runtime cannot distinguish rather than counting it as a pass.
|
|
|
665
685
|
[#7804]: https://github.com/deepseek-ai/deepseek-harness/discussions/7804
|
|
666
686
|
[#7876]: https://github.com/deepseek-ai/deepseek-harness/discussions/7876
|
|
667
687
|
[#7877]: https://github.com/deepseek-ai/deepseek-harness/discussions/7877
|
|
688
|
+
[#8232]: https://github.com/deepseek-ai/deepseek-harness/discussions/8232
|
|
668
689
|
[#8193]: https://github.com/deepseek-ai/deepseek-harness/discussions/8193
|
|
669
690
|
[#8174]: https://github.com/deepseek-ai/deepseek-harness/discussions/8174
|
|
670
691
|
[#8208]: https://github.com/deepseek-ai/deepseek-harness/discussions/8208
|
package/lib/advice.js
CHANGED
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
*/
|
|
93
93
|
import { failureLine, STATUS_DLL_INIT_FAILED } from './signature.js';
|
|
94
94
|
/** The upstream threads the ACL advisory is a stopgap for. */
|
|
95
|
-
export const ACL_DISCUSSIONS = '#7538 / #7622 / #7646 / #7720 / #7750 / #7735 / #7771 / #7804 / #7816';
|
|
95
|
+
export const ACL_DISCUSSIONS = '#7538 / #7622 / #7646 / #7720 / #7750 / #7735 / #7771 / #7804 / #7816 / #8232';
|
|
96
96
|
/** The upstream thread the persistent-shell advisory is a stopgap for. */
|
|
97
97
|
export const PTY_DISCUSSIONS = '#7638';
|
|
98
98
|
/** The upstream threads the native-init-death advisory is a stopgap for. */
|
|
@@ -278,6 +278,8 @@ function aclAdvisory(failure, href) {
|
|
|
278
278
|
const where = href === undefined ? `tracked upstream (discussions ${ACL_DISCUSSIONS})` : `tracked upstream: ${href}`;
|
|
279
279
|
return [
|
|
280
280
|
'Sandbox provisioning failed — no sandboxed command can run in this workspace until its ACL applies.',
|
|
281
|
+
'The failure belongs to the WORKSPACE, not to the command: under this mode a command that only reads',
|
|
282
|
+
'fails identically, so trying a different or more harmless command is not a retry that can succeed.',
|
|
281
283
|
'',
|
|
282
284
|
'What was reported:',
|
|
283
285
|
` ${failureLine(failure)}`,
|
|
@@ -302,6 +304,9 @@ function aclAdvisory(failure, href) {
|
|
|
302
304
|
` cmd: icacls "${path}" /grant "%USERNAME%:(OI)(CI)(WO)"`,
|
|
303
305
|
'WRITE_OWNER is exactly the right the prerequisite names, so this grants nothing the harness did not ask for,',
|
|
304
306
|
'and (OI)(CI) makes the ACE inheritable, so one command reaches the workspace\'s existing subdirectories.',
|
|
307
|
+
'The grant is still scoped to THIS directory and its children: another workspace root on the same volume is',
|
|
308
|
+
'a sibling rather than a child, so it needs the same line once — which is why a second workspace fails on a',
|
|
309
|
+
'machine where the first one was already repaired.',
|
|
305
310
|
'Full control works just as well — the same line with `F` in place of `(WO)`:',
|
|
306
311
|
` icacls "${path}" /grant "$env:USERNAME:(OI)(CI)F"`,
|
|
307
312
|
'Why `(WO)` is the whole of what is missing there: an owner holds READ_CONTROL and WRITE_DAC implicitly,',
|
package/lib/types/advice.d.ts
CHANGED
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
import type { ProvisioningFailure, RecognizedFailure } from './signature.js';
|
|
94
94
|
import type { SandboxModeName } from './mode.js';
|
|
95
95
|
/** The upstream threads the ACL advisory is a stopgap for. */
|
|
96
|
-
export declare const ACL_DISCUSSIONS = "#7538 / #7622 / #7646 / #7720 / #7750 / #7735 / #7771 / #7804 / #7816";
|
|
96
|
+
export declare const ACL_DISCUSSIONS = "#7538 / #7622 / #7646 / #7720 / #7750 / #7735 / #7771 / #7804 / #7816 / #8232";
|
|
97
97
|
/** The upstream thread the persistent-shell advisory is a stopgap for. */
|
|
98
98
|
export declare const PTY_DISCUSSIONS = "#7638";
|
|
99
99
|
/** The upstream threads the native-init-death advisory is a stopgap for. */
|
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:
|
|
4
|
-
"version": "0.8.
|
|
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: ten reports (#7538, #7622, #7646, #7720, #7750, #7735, #7771, #7804, #7816, #8232) 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 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.8.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|