Bundle
dsh-grantwrite-patch
Windows sandbox ACL-repair plugin for DeepSeek Harness (dsh) — fix the grantWrite failure (SetNamedSecurityInfoW Win32 5) and retry the command invisibly
- Source
- 0-v-0
- License
- MIT
- Updated
- Updated 7 days ago
Readme
# dsh-grantwrite-patch
English | [中文](README.zh.md)
Windows sandbox ACL-repair plugin for DeepSeek Harness (dsh) — fix the grantWrite failure (SetNamedSecurityInfoW Win32 5) and retry the command invisibly.
## What it solves
On Windows, the `workspace-write` sandbox writes a write grant into the workspace root DACL before every command (`AclWriteGrant.add` → `grantWrite`). When any directory on the chain lacks an effective `WRITE_DAC`/`WRITE_OWNER`, `SetNamedSecurityInfoW` fails with Win32 5 (ERROR_ACCESS_DENIED), and **every** confined command on that workspace fails before spawn:
```
Error: SetNamedSecurityInfoW failed (Win32 5): grantWrite
```
This plugin intercepts that error, repairs permissions automatically, and retries the command so you keep working without noticing.
## How it works
The plugin picks a strategy from the workspace size:
- **Normal workspaces**: intercept the error in memory and retry with zero side effects — no file permissions are touched.
- **Large workspaces**: when the object count exceeds the threshold (20,000 by default), proactively apply a full grant to the workspace once, so the write grant is not re-paid on every DSH restart and large trees stop costing you waiting time.
- **Fallback**: when the above cannot apply, run the diagnose script once to repair permissions and retry; when the script is unavailable, fall back to an `icacls` grant.
The three paths never conflict; they stack on demand.
## Requirements
| Item | Requirement |
|---|---|
| OS | Windows (NTFS). The plugin disables itself on other platforms |
| DSH version | `>= 0.1.7-rc.1`. The full diagnose-script repair needs `>= 0.2.0-rc.2` |
| Sandbox policy | Only `workspace-write` needs this (`read-only` / `danger-full-access` do not) |
## Install
### From npm
```sh
dsh plugin --profile web add dsh-grantwrite-patch
```
### From source (GitHub)
```sh
git clone https://github.com/0-v-0/dsh-grantwrite-patch
cd dsh-grantwrite-patch
pnpm install && pnpm build
dsh plugin --profile web add .
```
Notes:
- `dsh plugin` behaves like adding a dependency to your profile. A bundle plugin is loaded once its full package name appears in the profile's `dsh.profile.bundles` list (added automatically); the bundle patch (`cordis.patch.yml`) applies on the next boot.
- The source install must run `pnpm build` first: `lib/` is not committed, and the FFI preload is resolved from the installed package (`lib/preload.cjs`).
- To update, run the same command again (`add dsh-grantwrite-patch@latest`).
- Alternatively, merge the `- insert:` block of `cordis.patch.yml` into the host's `cordis.yml` by hand.
## Configuration
```yaml
- insert:
id: grantwrite-patch
name: dsh-grantwrite-patch
config:
enable: true
mode: auto # auto | patch | repair
objectThreshold: 20000 # object count at/above which a workspace counts as large -> persistent repair
probeTimeoutMs: 10000 # size-probe timeout; a timed-out probe counts as large
probeCooldownMs: 3600000 # per-workspace probe/decision cache duration
repairTimeoutMs: 120000
maxRetries: 1
cooldownMs: 60000
appendDiagnostics: true
grantFallback: true # fall back to a bare icacls grant when the diagnose script is unavailable (0.1.7) or fails; drive roots refused
# scriptPath: <absolute path> # default: resolved from @deepseek-ai/dsh-sandbox-windows-acl package assets (DSH >= 0.2.x)
# outDir: <absolute path> # default: $DSH_HOME/grantwrite-patch (~/.dsh/grantwrite-patch without DSH_HOME)
```
## Security model
- **patch path**: touches no system ACL and leaves no persistent side effect; the `WRITE_RESTRICTED` restricted token, capability-SID Allow ACEs, and the world-SID `FILE_DELETE_CHILD` Deny are preserved. The only thing lost is the Low-integrity label layer of defence in depth (children run at Medium), and only on hosts that could not apply the label in the first place.
- **repair path**: the repair scope is bounded by the script's `-AllowRoot`: only the target directory or objects strictly inside it are modified; reparse points and managed-application trees are refused; deny ACEs are never deleted; every change is backed up first and re-read for verification, and a failed batch is rolled back.
- **grant fallback** (`grantFallback`): when the script is unavailable or fails, run a bare `icacls <root> /grant "<user>:(OI)(CI)F"`. **Drive roots are hard-refused** (`isDriveRoot`: `D:\` and other drive roots are refused; only real workspaces are granted); inheritance policy is untouched and deny ACEs are not removed; roll back with `icacls <root> /remove:g "<user>"`. The script is preferred (safer: `-AllowRoot` bound + backup + verify); the fallback only covers the gap.
- The plugin process is the host process (a non-restricted token); modifying permissions is exactly what the sandbox's restricted token cannot do. The plugin never elevates its token.
- Only `SetNamedSecurityInfoW` failures in a `grantWrite` context are matched; other Win32 failures, command exit codes, EPERM, and so on all pass through untouched.
- The retry happens once (`maxRetries` is tunable from 0 to 3); a failed repair is not retried, which prevents loops.
## Credits & License
The FFI patch core is adapted from [masknull/dsh-acl-sandbox-patch](https://github.com/masknull/dsh-acl-sandbox-patch).
[MIT](LICENSE)
Install
dsh plugin --profile web add github:0-v-0/dsh-grantwrite-patch
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-grantwrite-patch from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.