@argszero/cordis-plugin-sandbox-grant-advisor 0.2.0 → 0.3.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
@@ -30,9 +30,20 @@ stuck.
30
30
 
31
31
  ### 1. Workspace provisioning — the Windows ACL failure (`acl-provisioning`)
32
32
 
33
- Three reports describe this exact line: [discussion #7538], [discussion #7622],
34
- [discussion #7646]. In each one every sandboxed command fails the same way,
35
- before it runs, and the error names neither the missing right nor a remedy.
33
+ Four reports describe this exact line: [discussion #7538], [discussion #7622],
34
+ [discussion #7646], [discussion #7720]. In each one every sandboxed command fails
35
+ the same way, before it runs, and the error names neither the missing right nor
36
+ a remedy.
37
+
38
+ `#7720` is worth reading for where the failure lands: the grant is materialized
39
+ at sandbox **initialization**, so this is not one refused operation but *every*
40
+ shell tool at once — the reporter could not run `netstat` or even `icacls` to
41
+ diagnose the error they were staring at (on `0.1.5-rc.3` the same directory
42
+ worked, because confinement was skipped silently rather than failing closed).
43
+ They also report the two remedies that look right and are not
44
+ (`takeown /F <dir> /R /D Y`, `icacls <dir> /reset /T /C`), which is why the
45
+ advisory names them with the reason each fails instead of leaving the reader to
46
+ discover it.
36
47
 
37
48
  The Windows backend provisions a workspace by writing the directory's DACL and
38
49
  its mandatory-integrity label in **one** `SetNamedSecurityInfoW` call
@@ -79,6 +90,13 @@ Confirm the cause (unelevated) — `icacls` is a normal user command:
79
90
  Fix it (unelevated, one line) and then run the command again:
80
91
  PowerShell: icacls "D:\ws" /grant "$env:USERNAME:(OI)(CI)F"
81
92
  cmd: icacls "D:\ws" /grant "%USERNAME%:(OI)(CI)F"
93
+
94
+ What will NOT fix it — both look like the right move, and both were tried and reported:
95
+ takeown /F "D:\ws" /R /D Y
96
+ makes you the owner, but ownership's implicit rights are READ_CONTROL and WRITE_DAC only.
97
+ The owner does not implicitly hold WRITE_OWNER, which is the right this call needs.
98
+ icacls "D:\ws" /reset /T /C
99
+ restores inheritance, and inheritance is what supplied the Modify-only ACE above.
82
100
  ```
83
101
 
84
102
  ### 2. Persistent shell startup (`pty-startup`)
@@ -338,4 +356,5 @@ the current runtime cannot distinguish rather than counting it as a pass.
338
356
  [discussion #7538]: https://github.com/deepseek-ai/deepseek-harness/discussions/7538
339
357
  [discussion #7622]: https://github.com/deepseek-ai/deepseek-harness/discussions/7622
340
358
  [discussion #7646]: https://github.com/deepseek-ai/deepseek-harness/discussions/7646
359
+ [discussion #7720]: https://github.com/deepseek-ai/deepseek-harness/discussions/7720
341
360
  [discussion #7638]: https://github.com/deepseek-ai/deepseek-harness/discussions/7638
package/lib/advice.js CHANGED
@@ -33,7 +33,7 @@
33
33
  */
34
34
  import { failureLine } from './signature.js';
35
35
  /** The upstream threads the ACL advisory is a stopgap for. */
36
- export const ACL_DISCUSSIONS = '#7538 / #7622 / #7646';
36
+ export const ACL_DISCUSSIONS = '#7538 / #7622 / #7646 / #7720';
37
37
  /** The upstream thread the persistent-shell advisory is a stopgap for. */
38
38
  export const PTY_DISCUSSIONS = '#7638';
39
39
  /** The documented prerequisite, quoted from the backend's README. */
@@ -57,6 +57,21 @@ export const GLOBAL_PATCH = '$DSH_HOME/cordis.patch.yml';
57
57
  export const MINIMAL_PRESET_ROW = 'preset-minimal';
58
58
  /** The one-shot shell tool the `standard` preset mounts on Windows. */
59
59
  export const ONE_SHOT_SHELL = '@deepseek-ai/dsh-tool-pwsh';
60
+ /**
61
+ * The two remedies that look like the fix and are not.
62
+ *
63
+ * Both were applied by the reporter of `#7720` before finding the one that
64
+ * works, and both are the *natural* reach: making yourself the owner and
65
+ * resetting the directory's ACL are how one normally repairs a Windows
66
+ * permission problem. They fail here for two different reasons, and naming the
67
+ * reason is what makes this section worth its lines — a reader who already
68
+ * tried them learns why, and a reader who has not is spared the attempt. See
69
+ * {@link nonFixes} for when this is emitted.
70
+ */
71
+ export const NOT_FIXES = [
72
+ 'takeown /F "<dir>" /R /D Y',
73
+ 'icacls "<dir>" /reset /T /C',
74
+ ];
60
75
  /** Placeholder the user replaces with the directory the error named. */
61
76
  const PLACEHOLDER = '<the directory from the error line above>';
62
77
  /**
@@ -91,6 +106,37 @@ function diagnosis(failure) {
91
106
  ].join('\n');
92
107
  }
93
108
  }
109
+ /**
110
+ * The "this is not the fix" lines for one class of failure.
111
+ *
112
+ * Emitted only for `apply-denied`, the class whose whole diagnosis is the
113
+ * missing `WRITE_OWNER` right — because only there is the claim true:
114
+ *
115
+ * - `read-denied` wants `READ_CONTROL`, and taking ownership *does* carry it,
116
+ * so calling `takeown` a non-fix there would be false.
117
+ * - `apply-other` already says the missing-rights story does not apply
118
+ * verbatim, so a section that presupposes it would contradict its own
119
+ * diagnosis.
120
+ *
121
+ * A negative claim still has to be earned: the failure to avoid is advice that
122
+ * is confidently wrong in the other direction.
123
+ * @param failure - the recognized provisioning failure.
124
+ * @param path - the directory the error named, or the placeholder.
125
+ * @returns the section's lines, or an empty array for a class it does not fit.
126
+ */
127
+ function nonFixes(failure, path) {
128
+ if (failure.klass !== 'apply-denied')
129
+ return [];
130
+ return [
131
+ 'What will NOT fix it — both look like the right move, and both were tried and reported:',
132
+ ` takeown /F "${path}" /R /D Y`,
133
+ " makes you the owner, but ownership's implicit rights are READ_CONTROL and WRITE_DAC only.",
134
+ ' The owner does not implicitly hold WRITE_OWNER, which is the right this call needs.',
135
+ ` icacls "${path}" /reset /T /C`,
136
+ ' restores inheritance, and inheritance is what supplied the Modify-only ACE above.',
137
+ '',
138
+ ];
139
+ }
94
140
  /**
95
141
  * Build the advisory attached to the failing tool result.
96
142
  *
@@ -137,6 +183,7 @@ function aclAdvisory(failure, href) {
137
183
  ` PowerShell: icacls "${path}" /grant "$env:USERNAME:(OI)(CI)F"`,
138
184
  ` cmd: icacls "${path}" /grant "%USERNAME%:(OI)(CI)F"`,
139
185
  '',
186
+ ...nonFixes(failure, path),
140
187
  'How to read this: the harness documents the prerequisite (' + PREREQUISITE + ') and this',
141
188
  'error does not name it yet, so the advice is delivered here instead. This is a stopgap, ' + where + '.',
142
189
  'What it is NOT: this plugin neither edits ACLs nor elevates — the command above is yours to run.',
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).** Three reports of one signature
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, and gives the discriminator that separates a Modify-only
63
- * directory from a wrong prerequisite. For the PTY family it names the
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
@@ -34,7 +34,7 @@
34
34
  import type { ProvisioningFailure, RecognizedFailure } from './signature.js';
35
35
  import type { SandboxModeName } from './mode.js';
36
36
  /** The upstream threads the ACL advisory is a stopgap for. */
37
- export declare const ACL_DISCUSSIONS = "#7538 / #7622 / #7646";
37
+ export declare const ACL_DISCUSSIONS = "#7538 / #7622 / #7646 / #7720";
38
38
  /** The upstream thread the persistent-shell advisory is a stopgap for. */
39
39
  export declare const PTY_DISCUSSIONS = "#7638";
40
40
  /** The documented prerequisite, quoted from the backend's README. */
@@ -58,6 +58,18 @@ export declare const GLOBAL_PATCH = "$DSH_HOME/cordis.patch.yml";
58
58
  export declare const MINIMAL_PRESET_ROW = "preset-minimal";
59
59
  /** The one-shot shell tool the `standard` preset mounts on Windows. */
60
60
  export declare const ONE_SHOT_SHELL = "@deepseek-ai/dsh-tool-pwsh";
61
+ /**
62
+ * The two remedies that look like the fix and are not.
63
+ *
64
+ * Both were applied by the reporter of `#7720` before finding the one that
65
+ * works, and both are the *natural* reach: making yourself the owner and
66
+ * resetting the directory's ACL are how one normally repairs a Windows
67
+ * permission problem. They fail here for two different reasons, and naming the
68
+ * reason is what makes this section worth its lines — a reader who already
69
+ * tried them learns why, and a reader who has not is spared the attempt. See
70
+ * {@link nonFixes} for when this is emitted.
71
+ */
72
+ export declare const NOT_FIXES: readonly ["takeown /F \"<dir>\" /R /D Y", "icacls \"<dir>\" /reset /T /C"];
61
73
  /** What the caller knows about the failing call, beyond the failure text. */
62
74
  export interface AdvisoryContext {
63
75
  /** URL quoted in place of the discussions list; optional. */
@@ -4,8 +4,8 @@
4
4
  *
5
5
  * ## The two failures it recognizes
6
6
  *
7
- * **Workspace provisioning (Windows ACL).** Three reports of one signature
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, and gives the discriminator that separates a Modify-only
63
- * directory from a wrong prerequisite. For the PTY family it names the
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/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: three reports (#7538, #7622, #7646) 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, the discriminator, and the unelevated `icacls` fix; 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.2.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: four reports (#7538, #7622, #7646, #7720) 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, the discriminator, the unelevated `icacls` fix, 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.3.0",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",