@argszero/cordis-plugin-sandbox-grant-advisor 0.7.0 → 0.7.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.
Files changed (3) hide show
  1. package/README.md +40 -1
  2. package/lib/advice.js +25 -13
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -281,6 +281,44 @@ Two producers have been measured under a confining mode:
281
281
  cause that [`#8193`] measures to be false; the single-cause sentence is gone,
282
282
  and a test arm keeps it gone.
283
283
 
284
+ **The remedy is the host, and 0.7.1 says so in that order.** [`#8193`] did not
285
+ only split the producer — it measured the way out: hosted on the desktop's own
286
+ bundled standalone node
287
+ (`resources/runtime/primary-runtime/dependencies/node/bin/node.exe`, v24.21.0)
288
+ the *same* confined `pwsh.exe` / `cmd.exe` spawns succeed (exit 81 / 82), with
289
+ workspace, temp directory, mode, SIDs, target and runner `sha256` all held
290
+ constant. The advisory now leads the Electron branch with that, and demotes
291
+ `danger-full-access` to what it actually is — a way to **confirm** the
292
+ diagnosis, not a fix, and on this platform one that silently removes the
293
+ sandbox from every shell call. The reporter's own reason is the one the plugin
294
+ repeats: *"the practical effect is that Windows Desktop users must escalate to
295
+ full access for all shell work, which silently removes the sandbox on that
296
+ platform"*, and they asked explicitly that this not be "fixed" with
297
+ `--disable-sandbox` / `--disable-gpu-sandbox` — those disable Chromium's
298
+ renderer sandbox, a different layer from the DSH file policy. The advisory also
299
+ warns off the opposite-looking move: unsetting `ELECTRON_RUN_AS_NODE` does not
300
+ help, because the desktop's runner **is** that Electron binary and without the
301
+ variable it cannot execute `runner.js` at all — the interaction [`#8193`]
302
+ records with [`#8174`], where a fix that tombstones the variable in the shared
303
+ child environment would take the ACL runner down with it, so the two changes
304
+ have to land together.
305
+
306
+ **What the flag vocabulary actually is.** The backend's own source records one
307
+ inherent boundary: *"console isolation is unavailable — children share the host
308
+ console (`CREATE_NO_WINDOW` / `CREATE_NEW_CONSOLE` children die with
309
+ `STATUS_DLL_INIT_FAILED` under the restriction)"*
310
+ (`packages/sandbox/sandbox-windows-acl/src/index.ts`), echoed at
311
+ `packages/subprocess/win32-process/src/process.ts:454`. [`#8193`] refines that
312
+ recording instead of repeating it, and the refinement is worth keeping exact:
313
+ the restricted-token path spawns with **`CREATE_SUSPENDED` alone**
314
+ (`process.ts:537-545`) and the ordinary current-token path with
315
+ **`CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT`** (`process.ts:567`, i.e.
316
+ `0x404`); `CREATE_NO_WINDOW` (`0x08000000`) is **not a constant anywhere in
317
+ that source** — only those two comments name it at all. So the flags in use are
318
+ a necessary-but-insufficient ingredient: the same flags that are fatal under an
319
+ Electron host succeed under a real-node host, and the host process image is
320
+ what turns them fatal.
321
+
284
322
  **Why this family is read from a successful result.** The producer never marks
285
323
  it an error, and that is a fact about upstream rather than a choice here:
286
324
  `RUNNER_FAILURE_RULES['windows-acl']` admits exactly one code —
@@ -539,7 +577,7 @@ the newest of that line.
539
577
 
540
578
  The whole set is re-probed whenever this package's source changes rather than
541
579
  carried over from an earlier version: the range is a claim about *this* build of
542
- the plugin, so `0.7.0` re-ran all five lines above. A line whose probe fails is
580
+ the plugin, so `0.7.1` re-ran all five lines above. A line whose probe fails is
543
581
  removed from the range rather than left claimed. The scratch tree's resolved
544
582
  versions are the ones to read back when a probe is quoted as evidence — the probe
545
583
  script pins them by exact version, and `--keep` leaves the tree in place to check.
@@ -590,3 +628,4 @@ the current runtime cannot distinguish rather than counting it as a pass.
590
628
  [#7876]: https://github.com/deepseek-ai/deepseek-harness/discussions/7876
591
629
  [#7877]: https://github.com/deepseek-ai/deepseek-harness/discussions/7877
592
630
  [#8193]: https://github.com/deepseek-ai/deepseek-harness/discussions/8193
631
+ [#8174]: https://github.com/deepseek-ai/deepseek-harness/discussions/8174
package/lib/advice.js CHANGED
@@ -377,24 +377,36 @@ function nativeInitAdvisory(failure, mode, onElectron, href) {
377
377
  ' A reader outside the session separates them (is `ELECTRON_RUN_AS_NODE` set for that host, and does',
378
378
  ' the ACL runner appear among its processes while the command runs?); from inside the session they are',
379
379
  ' one finding with one remedy, so nothing here waits on telling them apart.',
380
- onElectron
381
- ? ' This process IS an Electron binary (`process.versions.electron` is set), so that producer applies here:'
382
- : ' This process is NOT an Electron binary (`process.versions.electron` is unset), so the runner is a real',
383
- onElectron
384
- ? ' run the same command with `danger-full-access`, or from an unpacked `node apps/cli/lib/bin.js web`'
385
- : ' Node binary here and that producer cannot be the cause. If the program was not an MSYS2 one either,',
386
- onElectron
387
- ? ' host where the runner is a real Node binary. The command starts there in either shape, and that — the host, not the flags — is the discriminator.'
388
- : ' this failure is outside both measured producers: stop and hand it to the user.',
380
+ ...(onElectron
381
+ ? [
382
+ ' This process IS an Electron binary (`process.versions.electron` is set), so that producer applies here.',
383
+ ' The way out is a real node host, not a wider mode: the packaged desktop ships one at',
384
+ ' `resources/runtime/primary-runtime/dependencies/node/bin/node.exe`, and the unpacked',
385
+ ' `node apps/cli/lib/bin.js web` host works for the same reason — the same confined `pwsh`/`cmd` calls',
386
+ ' start there (#8193 measured exit 81/82). The command starts there in either shape, and that — the',
387
+ ' host, not the flags — is the discriminator. `danger-full-access` only CONFIRMS the diagnosis: on this',
388
+ ' platform it silently removes the sandbox from every shell call. Do not unset `ELECTRON_RUN_AS_NODE`',
389
+ ' instead either — the desktop runner IS that Electron binary, so without it `runner.js` cannot',
390
+ ' execute at all (#8193 records this interaction with #8174).',
391
+ ]
392
+ : [
393
+ ' This process is NOT an Electron binary (`process.versions.electron` is unset), so the runner is a real',
394
+ ' Node binary here and that producer cannot be the cause. If the program was not an MSYS2 one either,',
395
+ ' this failure is outside both measured producers: stop and hand it to the user.',
396
+ ]),
389
397
  '',
390
398
  'Do not retry this call: the environment has not changed, and the identical call produces the identical',
391
399
  'code. Convert the work only in case 1; otherwise stop and hand it to the user.',
392
400
  '',
393
401
  'Honest boundary — 0xC0000142 has producers this list does not have: a program that cannot load one of',
394
- 'its own DLLs dies this way too, and the sandbox backend\'s own source records that a child started with',
395
- 'a hidden console window does as well (which is why that backend avoids `CREATE_NO_WINDOW`). This is not',
396
- 'a claim that the sandbox caused the failure — the code cannot say that. What is claimed is narrower and',
397
- 'checkable: the process never reached its entry point, and under this mode these two producers are known.',
402
+ 'its own DLLs dies this way too, and the backend\'s own source records that a child created with a hidden',
403
+ 'console window can as well (`CREATE_NO_WINDOW` can fail restricted-token DLL initialization). #8193',
404
+ 'refines that recording rather than repeating it: `CREATE_NO_WINDOW` is not among the flags actually passed',
405
+ '(they are `CREATE_SUSPENDED`, and `CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT` on the unrestricted',
406
+ 'path), and the same flags succeed under a real-node host — so the flag is a necessary ingredient and the',
407
+ 'host process image is what turns it fatal. This is not a claim that the sandbox caused the failure — the',
408
+ 'code cannot say that. What is claimed is narrower and checkable: the process never reached its entry',
409
+ 'point, and under this mode these two producers are known.',
398
410
  '',
399
411
  'This is a stopgap, ' + where + '. What it is NOT: this plugin neither changes an environment nor',
400
412
  'widens the sandbox — the checks above are yours to make, and `danger-full-access` is not offered as a fix.',
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: nine reports (#7538, #7622, #7646, #7720, #7750, #7735, #7771, #7804, #7816) 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; 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 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.7.0",
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: nine reports (#7538, #7622, #7646, #7720, #7750, #7735, #7771, #7804, #7816) 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; 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.7.1",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",