dsh-plugin-wsl-env 0.1.2 → 0.1.4
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/CHANGELOG.md +98 -8
- package/CONTRIBUTING.md +167 -0
- package/README.md +42 -134
- package/SECURITY.md +82 -0
- package/SUPPORT.md +69 -0
- package/docs/ARCHITECTURE.md +217 -0
- package/docs/CONFIGURATION.md +48 -0
- package/docs/LIMITATIONS.md +30 -0
- package/docs/README.zh.md +45 -135
- package/lib/index.js +6 -6
- package/package.json +19 -3
package/CHANGELOG.md
CHANGED
|
@@ -12,18 +12,108 @@ reference below points at that record's numbering.
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [0.1.4] - 2026-10-02
|
|
16
|
+
|
|
17
|
+
Documentation only: a shorter README, and the reference documents it links to.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- The README is a quick start now, and nothing else: install, use, configure, the
|
|
22
|
+
sandbox in brief, troubleshooting, development, and an index of the documents. It
|
|
23
|
+
went from 232 lines to 124. Everything that was not part of getting the plugin
|
|
24
|
+
running moved to the document it belongs to — the full configuration table to
|
|
25
|
+
[`docs/CONFIGURATION.md`](docs/CONFIGURATION.md), the limitations to
|
|
26
|
+
[`docs/LIMITATIONS.md`](docs/LIMITATIONS.md), the file layout to
|
|
27
|
+
[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) (the two design rationales were
|
|
28
|
+
already there), and the verification list to
|
|
29
|
+
[`CONTRIBUTING.md`](CONTRIBUTING.md). The Chinese README was rewritten to match,
|
|
30
|
+
section for section, and still does.
|
|
31
|
+
- `docs/CONFIGURATION.md`, `docs/LIMITATIONS.md` and `docs/ARCHITECTURE.md` ship in
|
|
32
|
+
the tarball, so a package consumer has the configuration reference and the honest
|
|
33
|
+
list of limitations without the repository.
|
|
34
|
+
|
|
35
|
+
## [0.1.3] - 2026-10-02
|
|
36
|
+
|
|
37
|
+
Repository and release engineering. No runtime behaviour changed.
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- `npm run lint:style` ([`test/style.mjs`](test/style.mjs)): a dependency-free gate
|
|
42
|
+
over the rules `.editorconfig` states, plus three packaging checks — every `files`
|
|
43
|
+
entry still matches tracked content, every `exports` target exists, and exactly one
|
|
44
|
+
readme candidate sits at the package root. That last one is the regression guard
|
|
45
|
+
for the 0.1.1 readme, and it is why the gate exists at all.
|
|
46
|
+
- `npm run test:coverage`, with thresholds of 85% lines, 85% branches and 70%
|
|
47
|
+
functions, using Node's built-in coverage. No coverage dependency was added. The
|
|
48
|
+
spawning half of `lib/wsl.js` and the four service modules are covered by the
|
|
49
|
+
probes instead, which is why the function threshold is lower than the others.
|
|
50
|
+
- `.github/workflows/release.yml`: a `v*` tag is checked against `package.json`
|
|
51
|
+
([`scripts/check-release-tag.mjs`](scripts/check-release-tag.mjs)), gated, then
|
|
52
|
+
published with `--provenance` through npm trusted publishing, and the GitHub
|
|
53
|
+
Release is created from the changelog section
|
|
54
|
+
([`scripts/changelog-section.mjs`](scripts/changelog-section.mjs)). No npm token is
|
|
55
|
+
stored in the repository.
|
|
56
|
+
- The governance files a public repository is expected to carry:
|
|
57
|
+
[`SECURITY.md`](SECURITY.md), [`CONTRIBUTING.md`](CONTRIBUTING.md),
|
|
58
|
+
[`SUPPORT.md`](SUPPORT.md), [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md),
|
|
59
|
+
`CODEOWNERS`, a pull-request template, bug-report and contact-link issue templates,
|
|
60
|
+
and a Dependabot configuration for the pinned workflow actions.
|
|
61
|
+
- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) describes the plugin as it is
|
|
62
|
+
today — the providers, how they are mounted, the three path coordinate systems, the
|
|
63
|
+
sandbox, the runtime mirror and the test layers — so the current design no longer
|
|
64
|
+
has to be reconstructed from a changelog and a historical record.
|
|
65
|
+
- [`docs/RELEASING.md`](docs/RELEASING.md): the release checklist, including the
|
|
66
|
+
operational facts that cost time to learn.
|
|
67
|
+
- [`docs/archive/README.en.md`](docs/archive/README.en.md): an English index of the
|
|
68
|
+
Chinese archive.
|
|
69
|
+
- `.nvmrc` names the Node version the coverage job uses.
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
- The probes no longer carry a user name, a home directory or a distro name.
|
|
74
|
+
[`test/probe/env.sh`](test/probe/env.sh) derives them from the machine
|
|
75
|
+
(`%USERPROFILE%`, `%LOCALAPPDATA%`, `WSL_DISTRO_NAME`) and
|
|
76
|
+
[`test/probe/env.mjs`](test/probe/env.mjs) does the same for the Node-side probes,
|
|
77
|
+
which learn their own location from `import.meta.url`. The YAML overlays are now
|
|
78
|
+
templates with `@NAME@` placeholders, and the scripts substitute real values into
|
|
79
|
+
generated copies that `.gitignore` keeps out of the tree. The two profile layers
|
|
80
|
+
use `distro: ''` (WSL's default distro) instead of naming one.
|
|
81
|
+
- CI runs the unit suite on Windows as well as Linux. The plugin targets Windows, and
|
|
82
|
+
the Windows legs also prove that the LF guarantee in `.gitattributes` holds on the
|
|
83
|
+
platform whose default is CRLF. The workflow gained least-privilege `permissions`,
|
|
84
|
+
a `concurrency` group, timeouts, and actions pinned by commit SHA.
|
|
85
|
+
- `package.json`: `packageManager` is gone, because the package has no dependencies
|
|
86
|
+
and npm is the reference toolchain everywhere else; `author` and `publishConfig`
|
|
87
|
+
are set; `prepublishOnly` runs the gates; and `files` ships `CONTRIBUTING.md`,
|
|
88
|
+
`SECURITY.md` and `SUPPORT.md` so a tarball without the repository still says how
|
|
89
|
+
to contribute, report and get help.
|
|
90
|
+
- Both READMEs gained a *Contributing and support* section, and list the new gates.
|
|
91
|
+
|
|
92
|
+
### Fixed
|
|
93
|
+
|
|
94
|
+
- Corrected the 0.1.2 entry. It had named the npm CLI's readme rule as the cause of
|
|
95
|
+
the 0.1.1 readme mix-up; that rule does not match `.md` names and is not the code
|
|
96
|
+
path that decides a published package's readme, so the entry now records what was
|
|
97
|
+
observed instead of a mechanism that does not hold. The GitHub Release notes for
|
|
98
|
+
v0.1.2 were corrected the same way.
|
|
99
|
+
- Six lines in [`lib/index.js`](lib/index.js) were indented with tabs while the rest
|
|
100
|
+
of the file uses spaces, which the new style gate caught.
|
|
101
|
+
|
|
15
102
|
## [0.1.2] - 2026-10-02
|
|
16
103
|
|
|
17
104
|
### Fixed
|
|
18
105
|
|
|
19
|
-
- The npm package page rendered the Chinese README instead of the English one.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
106
|
+
- The npm package page rendered the Chinese README instead of the English one. 0.1.1
|
|
107
|
+
published `README.md` and `README.zh.md` side by side, and the registry answered
|
|
108
|
+
with `readmeFilename: README.zh.md`. Two candidates are enough to make the outcome
|
|
109
|
+
unreliable, and neither obvious rule explains which one won: it is not the tarball's
|
|
110
|
+
entry order (0.1.1's tarball listed `package/README.md` first), and it is not the
|
|
111
|
+
npm CLI's own selection rule (`@npmcli/package-json/lib/normalize.js` globs
|
|
112
|
+
`{README,README.*}` and accepts `/\.m?a?r?k?d?o?w?n$/i`, which does not match a
|
|
113
|
+
`.md` name at all). The registry is picking by a rule this repository should not
|
|
114
|
+
depend on. The translation now lives at `docs/README.zh.md`, which leaves exactly
|
|
115
|
+
one candidate at the package root and removes the question by construction. The
|
|
116
|
+
English header still links to it and `files` still ships it. 0.1.1 remains on the
|
|
27
117
|
registry with the wrong readme; 0.1.2 supersedes it.
|
|
28
118
|
|
|
29
119
|
## [0.1.1] - 2026-10-02
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
This document covers the development loop, the gates a pull request must pass, and
|
|
4
|
+
the conventions this repository uses.
|
|
5
|
+
|
|
6
|
+
## Prerequisites
|
|
7
|
+
|
|
8
|
+
| Requirement | Notes |
|
|
9
|
+
|---|---|
|
|
10
|
+
| Windows 10 or 11 | the plugin targets Windows only |
|
|
11
|
+
| WSL2 with at least one distro | `wsl.exe -l -v` lists the installed distros |
|
|
12
|
+
| `bubblewrap` in the distro | `sudo apt install bubblewrap`. The sandbox fails closed without it. |
|
|
13
|
+
| Node 20, 22, or 24 | the versions CI runs. `engines` requires `>=20`. |
|
|
14
|
+
|
|
15
|
+
## Toolchain
|
|
16
|
+
|
|
17
|
+
The package has no dependencies, so there is no install step. Run the scripts
|
|
18
|
+
straight from a checkout.
|
|
19
|
+
|
|
20
|
+
`npm` runs the scripts in this repository, and CI uses npm. Treat
|
|
21
|
+
`npm run <script>` as the reference toolchain.
|
|
22
|
+
|
|
23
|
+
Do not add a runtime dependency. The package ships an empty `dependencies` object
|
|
24
|
+
on purpose, because it is loaded into the DSH process and everything it needs
|
|
25
|
+
arrives as a peer package.
|
|
26
|
+
|
|
27
|
+
Style tooling is kept out for the same reason. There is no ESLint, no Prettier, and
|
|
28
|
+
no `devDependencies` at all. The checks that would normally need them are built
|
|
29
|
+
from Node builtins instead. `test/style.mjs` verifies the rules `.editorconfig`
|
|
30
|
+
states, and it checks that every `files` entry still matches tracked content and
|
|
31
|
+
every `exports` target still exists. The syntax pass inside `npm test` uses Node's
|
|
32
|
+
own parser.
|
|
33
|
+
|
|
34
|
+
## The development loop
|
|
35
|
+
|
|
36
|
+
1. Clone the repository inside the distro. Most contributors develop there.
|
|
37
|
+
2. Make the change.
|
|
38
|
+
3. Run the gates below.
|
|
39
|
+
4. Restart the DSH app before testing by hand, if you changed anything under
|
|
40
|
+
`lib/`. A running process caches ES modules, so it keeps the old code
|
|
41
|
+
otherwise.
|
|
42
|
+
|
|
43
|
+
### The runtime mirror
|
|
44
|
+
|
|
45
|
+
A DSH profile can only link a Windows path. pnpm rewrites
|
|
46
|
+
`link:\\wsl.localhost\...` into a broken `/wsl.localhost/...` symlink, so the
|
|
47
|
+
harness cannot load the plugin from a distro checkout.
|
|
48
|
+
|
|
49
|
+
The Windows copy under `default-workspace/dsh-plugin-wsl` is a runtime mirror. Sync
|
|
50
|
+
it before launching the app:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm run sync:windows
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The mirror destination sits outside a session workspace. An agent that runs the
|
|
57
|
+
sync from a confined shell is refused by the `workspace-write` policy and has to
|
|
58
|
+
approve a wider permission for that one command. Run the sync from a plain distro
|
|
59
|
+
terminal if you would rather not see the prompt.
|
|
60
|
+
|
|
61
|
+
## Gates before a pull request
|
|
62
|
+
|
|
63
|
+
| Command | Covers | Needs a harness |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `npm test` | style checks, syntax pass, and unit tests | no |
|
|
66
|
+
| `npm run test:coverage` | unit tests with coverage | no |
|
|
67
|
+
| `npm run lint:style` | style and lint rules | no |
|
|
68
|
+
| `npm run probe` | filesystem behaviour against a real distro | yes |
|
|
69
|
+
| `npm run probe:sandbox` | what bubblewrap confines and what it does not | no |
|
|
70
|
+
| `npm run probe:sandbox-shell` | the confined executor through a real boot | yes |
|
|
71
|
+
| `npm run probe:terminal` | the terminal provider | yes |
|
|
72
|
+
|
|
73
|
+
Every script above is wired in `package.json`. `npm test` runs `lint:style`,
|
|
74
|
+
`test:syntax` and `test:unit` in that order. `prepublishOnly` runs `npm test` again
|
|
75
|
+
at publish time, so a broken gate stops a release before the upload.
|
|
76
|
+
|
|
77
|
+
CI runs `npm test` on Node 20, 22 and 24, on both `ubuntu-latest` and
|
|
78
|
+
`windows-latest`, and `npm run test:coverage` on Node 24. The Windows runners are
|
|
79
|
+
there because the plugin targets Windows; the coverage thresholds need Node 22.8 or
|
|
80
|
+
newer, which is why that job runs on one version.
|
|
81
|
+
|
|
82
|
+
The probes marked "yes" need Windows, WSL2, a mounted harness profile and a linked
|
|
83
|
+
checkout, which a hosted runner does not have. Run them on your own machine and
|
|
84
|
+
paste the output into the pull request.
|
|
85
|
+
|
|
86
|
+
## Adding a test
|
|
87
|
+
|
|
88
|
+
Choose the layer by what the test needs.
|
|
89
|
+
|
|
90
|
+
| Layer | Location | Use it for |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| Unit test | `test/*.test.mjs` | pure logic: path translation, listing, shell argument building, terminal helpers |
|
|
93
|
+
| Probe | `test/probe/` | behaviour that needs a real distro, a real profile, or bubblewrap |
|
|
94
|
+
|
|
95
|
+
Unit tests run under `node --test` and import only Node builtins. Add a file named
|
|
96
|
+
`<area>.test.mjs`, then add it to both the `test:unit` and the `test:coverage`
|
|
97
|
+
scripts in `package.json`. Both scripts list their files explicitly.
|
|
98
|
+
|
|
99
|
+
Probes are shell scripts that mount a throwaway profile, plus the small plugin
|
|
100
|
+
files they load. Those plugin files are the `*-probe.mjs` and `*-probe.yml` files
|
|
101
|
+
in the same directory. The setup steps for the throwaway profile are in the header
|
|
102
|
+
of `test/probe/run.sh`. `terminal.sh` and `sandbox-shell.sh` reuse it.
|
|
103
|
+
|
|
104
|
+
Keep probe output idempotent. Several probes delete their scratch files at the
|
|
105
|
+
start, because a leftover file from an earlier run changes the result.
|
|
106
|
+
|
|
107
|
+
## What has been verified
|
|
108
|
+
|
|
109
|
+
`npm test` covers the pure modules and runs a `--check` pass over every shipped
|
|
110
|
+
module. It cannot import the service modules, because those need DSH peer packages
|
|
111
|
+
that a bare checkout does not have. Only booting the harness closes that gap, which
|
|
112
|
+
is why the probes exist; the archived record's §15.4 documents the five rounds of
|
|
113
|
+
misdiagnosis the gap once caused.
|
|
114
|
+
|
|
115
|
+
`test/probe/sandbox.sh` needs no harness: it uses the same arguments
|
|
116
|
+
`lib/sandbox.js` builds and asserts what bubblewrap does and does not confine,
|
|
117
|
+
recording the interop escape as `INFO` because a Linux sandbox cannot govern a
|
|
118
|
+
Windows process. The other probes boot a throwaway profile bound to the distro:
|
|
119
|
+
|
|
120
|
+
- the filesystem probe asserts the write path and the write checks — a write outside
|
|
121
|
+
the policy root and a write in `read-only` mode both return `FS_SANDBOX_DENIED`,
|
|
122
|
+
while `danger-full-access` is not checked;
|
|
123
|
+
- the shell probe drives all three modes through `ctx.shell` and checks the refusal
|
|
124
|
+
classification the tool layer returns;
|
|
125
|
+
- the terminal probe asserts the distro, the starting directory, and the `DSH_*`
|
|
126
|
+
variables forwarded through `WSLENV`.
|
|
127
|
+
|
|
128
|
+
The setup steps for the throwaway profile are in the header of `test/probe/run.sh`;
|
|
129
|
+
`terminal.sh` and `sandbox-shell.sh` reuse it.
|
|
130
|
+
|
|
131
|
+
On Windows 11 + WSL2 (Ubuntu 26.04) the following has been verified:
|
|
132
|
+
|
|
133
|
+
- the wiring of each service, the UNC path handling and the picker behaviour;
|
|
134
|
+
- the plugin mounted end to end in a real profile;
|
|
135
|
+
- one real model turn in a WSL-only headless profile: `write → chmod → read → edit →
|
|
136
|
+
execute`, with the executable bit surviving the edit;
|
|
137
|
+
- the terminal provider in a throwaway Web boot and in the daily GUI profile;
|
|
138
|
+
- the sandbox in four ways: the arguments measured inside the distro, the filesystem
|
|
139
|
+
write checks, the shell path (`enforcement: partial`, with refusals classified
|
|
140
|
+
correctly), and the daily GUI profile, where a write from the agent's own session
|
|
141
|
+
outside the session workspace is refused inside the distro and the following
|
|
142
|
+
wider-permission request succeeds.
|
|
143
|
+
|
|
144
|
+
Counts: 42 unit assertions, 18 filesystem-probe assertions, 10 shell-probe checks, 10
|
|
145
|
+
sandbox expectations plus the recorded escape, and 3 terminal assertions.
|
|
146
|
+
|
|
147
|
+
## Commit and pull request conventions
|
|
148
|
+
|
|
149
|
+
- Write an imperative subject line: `Fix the npm readme`, not `Fixed` or `Fixes`.
|
|
150
|
+
- Do not use conventional-commit prefixes. No `feat:`, `fix:`, or `chore:`.
|
|
151
|
+
- Explain why in the body. What changed is already visible in the diff.
|
|
152
|
+
- Add a `CHANGELOG.md` entry under `## [Unreleased]` for a behaviour change. A
|
|
153
|
+
change to documentation or to comments does not need one.
|
|
154
|
+
- Keep one topic per commit.
|
|
155
|
+
- In the pull request, say how you tested it. Paste probe output where a probe is
|
|
156
|
+
relevant.
|
|
157
|
+
- Update `README.md` and `docs/README.zh.md` together when a change affects usage.
|
|
158
|
+
The two files match section for section.
|
|
159
|
+
|
|
160
|
+
## Reporting bugs and security issues
|
|
161
|
+
|
|
162
|
+
Open a GitHub issue for a bug:
|
|
163
|
+
|
|
164
|
+
https://github.com/Big-Dao/dsh-plugin-wsl-env/issues
|
|
165
|
+
|
|
166
|
+
Report a security issue privately. Follow [SECURITY.md](SECURITY.md) instead of
|
|
167
|
+
opening a public issue.
|
package/README.md
CHANGED
|
@@ -6,27 +6,12 @@
|
|
|
6
6
|
[](https://www.npmjs.com/package/dsh-plugin-wsl-env)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
|
|
9
|
-
This plugin lets [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) use a WSL distro as its working environment
|
|
9
|
+
This plugin lets [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) use a WSL distro as its working environment: commands run inside the distro, the model's file tools read and write real distro files, the folder picker can open a distro folder, and the GUI terminal opens inside the distro.
|
|
10
10
|
|
|
11
|
-
**Windows + WSL2 only.**
|
|
11
|
+
**Windows + WSL2 only.** One install command, no dependencies of its own. The environment applies per session, so a session on a Windows folder keeps its normal Windows environment.
|
|
12
12
|
|
|
13
|
-
[
|
|
14
|
-
[
|
|
15
|
-
[Limitations](#limitations) · [Development](#development) · [Design notes](#design-notes)
|
|
16
|
-
|
|
17
|
-
## What you get
|
|
18
|
-
|
|
19
|
-
| Before | After |
|
|
20
|
-
|---|---|
|
|
21
|
-
| Commands run on Windows | Commands run **inside your distro**, using your login shell, starting in the session's Linux directory |
|
|
22
|
-
| `read`/`write`/`edit`/`glob`/`grep` work on Windows paths | These tools work on **the distro's real files**, through the `\\wsl.localhost\<distro>` share |
|
|
23
|
-
| The folder picker cannot see WSL | One picker lists the Windows home directory and every installed distro, so you can open `/home/you/project` |
|
|
24
|
-
| The terminal opens `cmd.exe` in a UNC directory | The terminal opens **inside the distro**, in the session's directory, as your distro user |
|
|
25
|
-
| Commands have no sandbox | Commands are confined by **bubblewrap inside the distro**; see [Sandbox](#sandbox) |
|
|
26
|
-
|
|
27
|
-
If you open a session on a folder inside a distro, the WSL environment applies automatically. The `wsl` preset is bound while the session is created, so the first tool call already runs in the distro. You do not switch anything by hand.
|
|
28
|
-
|
|
29
|
-
**How to check that it works.** Ask the model to run `uname -r`. The output should be a WSL2 kernel such as `6.18.40.1-microsoft-standard-WSL2`. You can also run `echo $WSL_DISTRO_NAME`.
|
|
13
|
+
[Install](#install) · [Using it](#using-it) · [Configure](#configure) · [Sandbox](#sandbox) ·
|
|
14
|
+
[Troubleshooting](#troubleshooting) · [Development](#development) · [Documentation](#documentation)
|
|
30
15
|
|
|
31
16
|
## Install
|
|
32
17
|
|
|
@@ -39,84 +24,58 @@ dsh --profile wsl --dump-config # 3. compose only, no boot (t
|
|
|
39
24
|
dsh --profile wsl # 4. run
|
|
40
25
|
```
|
|
41
26
|
|
|
42
|
-
Step 3 should print a layer named `# == dsh-plugin-wsl-env`,
|
|
43
|
-
|
|
44
|
-
Then open a folder under `\\wsl.localhost\<distro>\...` in the GUI. The picker lists every installed distro at its root level. You can also just press **New terminal**.
|
|
27
|
+
Step 3 should print a layer named `# == dsh-plugin-wsl-env`, and `- id: terminal-controller` should now carry `shell: { path: wsl.exe, name: WSL }`. The package is a DSH bundle, so step 2 applies [`cordis.patch.yml`](cordis.patch.yml) as a configuration layer; nothing is merged by hand.
|
|
45
28
|
|
|
46
|
-
|
|
29
|
+
Then open a folder under `\\wsl.localhost\<distro>\...` in the GUI. The picker lists every installed distro at its root level, and **New terminal** opens a shell in the distro.
|
|
47
30
|
|
|
48
|
-
You also need **bubblewrap inside the distro
|
|
31
|
+
You also need **bubblewrap inside the distro**: `sudo apt install bubblewrap`. Without it every command fails closed, see [Sandbox](#sandbox).
|
|
49
32
|
|
|
50
|
-
Uninstall: `dsh plugin --profile wsl remove dsh-plugin-wsl-env`.
|
|
33
|
+
Uninstall: `dsh plugin --profile wsl remove dsh-plugin-wsl-env`. Upgrade: run the same `add` command again.
|
|
51
34
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
> **If you change `lib/` in a checkout, restart the app.** A running process caches ES modules and keeps using the old code until you do.
|
|
35
|
+
> **Changed anything under `lib/`? Restart the app.** A running process caches ES modules and keeps the old code otherwise.
|
|
55
36
|
|
|
56
37
|
## Using it
|
|
57
38
|
|
|
58
|
-
|
|
39
|
+
Open `\\wsl.localhost\ubuntu\home\you\project` as the workspace, then ask the model "what kernel am I on, and what is in `/etc/os-release`?". It runs `uname -r` and reads that file inside the distro. Nothing goes through `/mnt/c`, and no files are copied.
|
|
59
40
|
|
|
60
|
-
- **
|
|
61
|
-
- **Commands** run as `wsl.exe -d <distro> --cd <linux dir> --exec <your login shell> -lc <command
|
|
41
|
+
- **A distro folder gets the distro environment automatically.** The `wsl` preset is bound while the session is created, so the first tool call is already correct.
|
|
42
|
+
- **Commands** run as `wsl.exe -d <distro> --cd <linux dir> --exec <your login shell> -lc <command>`, so your `PATH`, `nvm`, `cargo`, `pyenv` and rc files apply. The shell is not hardcoded to bash.
|
|
62
43
|
- **Files are the distro's real files.** `/home/you/x` and `\\wsl.localhost\ubuntu\home\you\x` are the same file, and `/mnt/c/...` reaches the Windows disk.
|
|
63
44
|
- **The terminal** (right sidebar, then *New terminal*) opens a shell inside the distro, in the session's folder.
|
|
64
|
-
- **The model
|
|
65
|
-
- **Permissions work as
|
|
45
|
+
- **The model sees its shell environment.** The plugin registers `DSH_WSL_DISTRO`, `DSH_WSL_SHELL` and `DSH_WSL_HOME` in the managed `DSH_*` namespace.
|
|
46
|
+
- **Permissions work as on a Linux host.** The Permissions selector switches between `read-only`, `workspace-write` (the default) and `danger-full-access`. A refused command or write comes back with an offer to run it with wider permissions; if you approve, that one call runs without the sandbox.
|
|
66
47
|
|
|
67
48
|
## Configure
|
|
68
49
|
|
|
69
|
-
|
|
50
|
+
Override a row by id in `$DSH_HOME/profiles/<name>/cordis.patch.yml`. The keys worth knowing:
|
|
70
51
|
|
|
71
52
|
| Row | Key | Default | Meaning |
|
|
72
53
|
|---|---|---|---|
|
|
73
|
-
| `wsl-shell` | `distro` | `''`
|
|
74
|
-
| | `shell` | `''` | pin a shell inside the distro; empty means the user's login shell |
|
|
75
|
-
| | `loginShell` | `true` *(shipped)* | use `<shell> -lc`, which sources your profile, instead of a bare `-c` |
|
|
54
|
+
| `wsl-shell` | `distro` | `''` | distro name; empty means WSL's default distro |
|
|
76
55
|
| | `sandbox` | `true` | confine commands with `bubblewrap`; `false` disables the sandbox |
|
|
77
|
-
| | `
|
|
78
|
-
| | `
|
|
79
|
-
| `wsl-fs` | `distro` | `''` *(shipped)* | as above |
|
|
80
|
-
| | `restrictToDistro` | `true` *(shipped)* | refuse paths outside the pinned distro, including `/mnt/c` |
|
|
56
|
+
| `wsl-fs` | `distro` | `''` | as above |
|
|
57
|
+
| | `restrictToDistro` | `true` | refuse paths outside the pinned distro, including `/mnt/c` |
|
|
81
58
|
| | `sandbox` | `true` | check `writeText` and `editText` against the policy |
|
|
82
|
-
| | `
|
|
83
|
-
| | `
|
|
84
|
-
| `directory-picker-wsl` | `preferredDistro` | `''` *(shipped)* | the distro listed first in the picker |
|
|
85
|
-
| | `includeHostHome` | `true` *(shipped)* | also list the Windows home directory |
|
|
86
|
-
| | `maxEntries` | `1000` *(shipped)* | maximum entries listed per directory |
|
|
87
|
-
| `subprocess-wsl` | `distro` | `''` *(shipped)* | which distro the GUI terminal opens in |
|
|
88
|
-
| | `shell` | `''` | pin a shell; empty lets `wsl.exe` decide |
|
|
89
|
-
| | `loginShell` | `true` | whether a pinned shell uses login semantics |
|
|
59
|
+
| `directory-picker-wsl` | `includeHostHome` | `true` | also list the Windows home directory |
|
|
60
|
+
| `subprocess-wsl` | `distro` | `''` | which distro the GUI terminal opens in |
|
|
90
61
|
|
|
91
|
-
|
|
62
|
+
[`cordis.patch.yml`](cordis.patch.yml) is the commented reference for every shipped value. [docs/CONFIGURATION.md](docs/CONFIGURATION.md) lists the rest, including `shell`, `loginShell`, `cwd`, `timeoutMs`, `resolveSymlinks`, `preferredDistro` and `maxEntries`; [examples/profile.cordis.patch.yml](examples/profile.cordis.patch.yml) is a machine-local layer to copy from.
|
|
92
63
|
|
|
93
64
|
## Sandbox
|
|
94
65
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
On Windows, DSH confines commands with `dsh-sandbox-windows-acl`. It uses a restricted, low-integrity Windows token and an allowlist of writable paths. **That token cannot reach WSL.** `wsl.exe` fails with `Wsl/E_ACCESSDENIED`, and `\\wsl.localhost\<distro>` reports access denied, while both work normally outside the sandbox. The WSL environment therefore cannot reuse the Windows sandbox and uses a Linux sandbox instead. The plugin builds the `bubblewrap` arguments on the Windows side and passes them to `wsl.exe --exec`, which runs them inside the distro:
|
|
98
|
-
|
|
99
|
-
```text
|
|
100
|
-
wsl.exe -d <distro> --cd <linux dir> --exec bwrap \
|
|
101
|
-
--ro-bind / / --dev /dev --unshare-pid --proc /proc --die-with-parent \
|
|
102
|
-
[--tmpfs /tmp --bind <workspace> <workspace>] -- <shell> -lc <cmd>
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
These are the same arguments DSH uses on a Linux host (`dsh-sandbox-local`), so the behaviour and the error messages match.
|
|
66
|
+
Commands are confined by `bubblewrap` inside the distro, and file writes are checked against the same policy. The Windows ACL sandbox cannot be used here: its restricted token cannot reach WSL at all.
|
|
106
67
|
|
|
107
68
|
| Mode | What a command inside the distro can do |
|
|
108
69
|
|---|---|
|
|
109
|
-
| `read-only` | read the whole distro; only `/dev/null` is writable
|
|
70
|
+
| `read-only` | read the whole distro; only `/dev/null` is writable |
|
|
110
71
|
| `workspace-write` | the above, plus the session workspace is writable and `/tmp` is a temporary mount |
|
|
111
|
-
| `danger-full-access` | no sandbox
|
|
112
|
-
|
|
113
|
-
`WslFileSystem` checks writes against the same policy and the same writable list. That avoids a mismatch where `bash` could write `/tmp` but the write tool could not. Both providers report their current mode through `sandboxMode`, which is what brings back the Permissions selector and the "refused, then request wider permissions" flow.
|
|
72
|
+
| `danger-full-access` | no sandbox; used for an approved wider-permission request |
|
|
114
73
|
|
|
115
|
-
|
|
74
|
+
**`bubblewrap` is required, and the failure is closed.** Without it every confined command reports `SANDBOX_UNAVAILABLE` instead of running unconfined. Set `sandbox: false` on either provider to opt out; the tool layer then tells the model these operations have no sandbox.
|
|
116
75
|
|
|
117
|
-
|
|
76
|
+
**The reported enforcement is `partial`, not `full`.** A process inside the distro can still run a Windows program through WSL interop, for example `/mnt/c/.../*.exe`, and bubblewrap does not govern it. `npm run probe:sandbox` demonstrates the boundary on your machine.
|
|
118
77
|
|
|
119
|
-
|
|
78
|
+
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#sandbox) for the design, and [docs/LIMITATIONS.md](docs/LIMITATIONS.md) for everything the plugin does not do.
|
|
120
79
|
|
|
121
80
|
## Troubleshooting
|
|
122
81
|
|
|
@@ -132,84 +91,33 @@ These are the same arguments DSH uses on a Linux host (`dsh-sandbox-local`), so
|
|
|
132
91
|
| `glob` and `grep` are slow | the Windows-side ripgrep walks the 9p share | expected; narrow the path, or use `bash` to call tools inside the distro |
|
|
133
92
|
| the terminal reports `unknown` activity | shell integration only covers `bash`/`zsh` started directly on a POSIX host | close the tab to release the process; idle reclamation does not run for these terminals |
|
|
134
93
|
|
|
135
|
-
## Limitations
|
|
136
|
-
|
|
137
|
-
- **The sandbox does not govern WSL interop.** A confined command can still run a Windows program, which escapes the Linux-side boundary. The plugin reports this as `enforcement: partial`. See [Sandbox](#sandbox).
|
|
138
|
-
- **`bubblewrap` must be installed**, or both providers fail immediately.
|
|
139
|
-
- `workspace-write` binds the workspace root as writable, and bubblewrap refuses a bind whose source directory does not exist. If a session's workspace directory is deleted, commands fail with a runner error; the plugin does not recreate the directory.
|
|
140
|
-
- New files get the distro's umask default (0644). A `chmod` from the Windows side on a share path is silently ignored; overwrites and edits keep the original permission bits. Run `chmod +x` inside the distro when you need an executable.
|
|
141
|
-
- The check before a write is "check, then write", which is not atomic. This backend checks the caller's guard itself, because the Windows backend publishes a guarded new file with a hard link, and the share does not support hard links. This is the race that `dsh-fs-sandbox` already documents.
|
|
142
|
-
- `watch()` (file change monitoring) is not provided. Watching a 9p share is unreliable, so the plugin refuses instead of arming it.
|
|
143
|
-
- `glob` and `grep` use the Windows-side ripgrep over the share: correct results, but not fast, and `.gitignore` is interpreted with Windows rules. `editText` reads the whole file into memory before writing it back.
|
|
144
|
-
- The terminal belongs to the whole app, not to one session. A session opened on a Windows folder still gets the distro terminal, starting in `/mnt/<drive>/...`, and its shell menu is deliberately reduced to the one configured profile.
|
|
145
|
-
- Terminal activity reporting stops at `wsl.exe`, so the controller never reclaims these terminals when they are idle.
|
|
146
|
-
|
|
147
94
|
## Development
|
|
148
95
|
|
|
149
96
|
```bash
|
|
150
|
-
npm test #
|
|
97
|
+
npm test # style and packaging checks, syntax check, unit tests
|
|
98
|
+
npm run test:coverage # the unit tests with coverage thresholds (Node 22.8+)
|
|
151
99
|
npm run probe:sandbox # measure inside the distro what bubblewrap does and does not confine
|
|
152
100
|
npm run probe # filesystem probe against a real distro (Windows + WSL only)
|
|
153
101
|
npm run probe:sandbox-shell # boot a real harness and drive the confined executor
|
|
154
102
|
npm run probe:terminal # open a PTY through the terminal provider
|
|
155
103
|
```
|
|
156
104
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
```text
|
|
160
|
-
lib/paths.js pure conversion between the three path formats
|
|
161
|
-
lib/wsl.js wsl.exe interop primitives (no DSH imports)
|
|
162
|
-
lib/listing.js pure directory listing and breadcrumb helpers (no DSH imports)
|
|
163
|
-
lib/index.js WslShellExecutor (ctx.shell) and WslFileSystem (ctx.fs)
|
|
164
|
-
lib/sandbox.js the distro-side bwrap confinement shared by both providers
|
|
165
|
-
lib/picker.js WslDirectoryPicker (ctx.directoryPicker)
|
|
166
|
-
lib/subprocess.js WslSubprocessRuntime (ctx.subprocess), the terminal window
|
|
167
|
-
lib/auto-preset.js per-session environment selection
|
|
168
|
-
lib/shell-env.js registers the DSH_WSL_* environment variables
|
|
169
|
-
lib/{shell,fs}.js one-line subpath entry points
|
|
170
|
-
cordis.patch.yml the bundle configuration layer (dsh.bundle), commented
|
|
171
|
-
examples/ one machine-local profile layer, for comparison
|
|
172
|
-
test/ unit tests and behaviour probes
|
|
173
|
-
docs/archive/ the designs this one replaced, and why
|
|
174
|
-
```
|
|
105
|
+
There is nothing to install first: the package has no dependencies, and every module under test imports only Node builtins. CI runs `npm test` on Node 20, 22 and 24, on Linux and Windows, and `npm run test:coverage` on Node 24.
|
|
175
106
|
|
|
176
|
-
|
|
107
|
+
[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) has the file layout, the mounting, and the sandbox design. [CONTRIBUTING.md](CONTRIBUTING.md) has the development loop, the full gate list, and what has been verified.
|
|
177
108
|
|
|
178
|
-
|
|
109
|
+
## Documentation
|
|
179
110
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
- the wiring of each service, the UNC path handling and the picker behaviour;
|
|
191
|
-
- the plugin mounted end to end in a real profile;
|
|
192
|
-
- one real model turn in a WSL-only headless profile: `write → chmod → read → edit → execute`, with the executable bit surviving the edit;
|
|
193
|
-
- the terminal provider in a throwaway Web boot and in the daily GUI profile;
|
|
194
|
-
- the sandbox in four ways: the arguments measured inside the distro, the filesystem write checks, the shell path (`enforcement: partial`, with refusals classified correctly), and the daily GUI profile, where a write from the agent's own session outside the session workspace is refused inside the distro and the following wider-permission request succeeds.
|
|
195
|
-
|
|
196
|
-
Counts: 42 unit assertions, 18 filesystem-probe assertions, 10 shell-probe checks, 10 sandbox expectations plus the recorded escape, and 3 terminal assertions.
|
|
197
|
-
|
|
198
|
-
**The runtime mirror.** The checkout is developed inside the distro, but the harness is a Windows process, and a profile can only link a Windows path: pnpm rewrites `link:\\wsl.localhost\...` into a broken `/wsl.localhost/...` symlink. The Windows copy at `default-workspace/dsh-plugin-wsl` is therefore a runtime mirror. Sync it with `test/probe/sync-to-windows.sh` before launching the app. That destination is outside every session workspace, so an agent running the sync in a confined shell is refused by `workspace-write` and must approve `danger-full-access` for that one command. That is the sandbox behaving as designed, not a broken script; run the sync from a normal distro terminal if you prefer not to see the prompt.
|
|
199
|
-
|
|
200
|
-
**CI** runs `npm test` on Linux with Node 20, 22 and 24.
|
|
201
|
-
|
|
202
|
-
## Design notes
|
|
203
|
-
|
|
204
|
-
Two decisions are not visible in the profile YAML. The full reasoning, the measurements and every rejected design are in the archived engineering record: [docs/archive/engineering-record.zh.md](docs/archive/engineering-record.zh.md). It is a historical document, not a second README; if it disagrees with this file, this file is correct.
|
|
205
|
-
|
|
206
|
-
**Why the terminal provider is app-wide.** `ctx.shell` and `ctx.fs` are provided per session, inside the isolated scope of the `wsl` agent preset. A workspace on Windows therefore keeps the shipped sandboxed PowerShell environment, while a WSL workspace gets the distro. Both can exist in one process at the same time.
|
|
207
|
-
|
|
208
|
-
The terminal cannot be provided that way. `dsh-api-terminal-controller` looks up its environment with `agent.ctx.get("subprocess")`. An Agent's context is created by the agent loop under the **root** scope, while a preset's isolated scope is created by `dsh-agent-preset-registry` under the registry's own context. These two scope chains never meet, so a `subprocess` provider mounted inside `preset-wsl` is invisible to the terminal window.
|
|
209
|
-
|
|
210
|
-
For that reason, [`cordis.patch.yml`](cordis.patch.yml) replaces the **app-wide** `subprocess` row with a subclass that overrides only `spawnTerminal`. Ordinary `spawn()`, the Windows-side ripgrep search, the pwsh executor and the LSP host still use the shipped implementation. The cost is that the terminal follows the app configuration rather than the session; see [Limitations](#limitations).
|
|
211
|
-
|
|
212
|
-
**Why the sandbox is on the Linux side.** The Windows ACL runner's restricted token cannot reach WSL at all, so the confinement has to be built on the Windows side and run inside the distro. `lib/sandbox.js` copies the Linux `bwrap` approach from `dsh-sandbox-local` instead of using `ctx.sandbox`. `dsh-tool-bash` never asks for `ctx.sandbox` anyway: it reads the executor's `sandboxMode` and `ctx.sandboxPolicy`, and this plugin supplies both.
|
|
111
|
+
| Document | What it covers |
|
|
112
|
+
|---|---|
|
|
113
|
+
| [docs/CONFIGURATION.md](docs/CONFIGURATION.md) | every configuration key, and how to override it |
|
|
114
|
+
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | providers, mounting, path coordinates, sandbox, test layers, file layout |
|
|
115
|
+
| [docs/LIMITATIONS.md](docs/LIMITATIONS.md) | what the plugin does not do, and why |
|
|
116
|
+
| [CONTRIBUTING.md](CONTRIBUTING.md) | the development loop, gates, conventions, verification |
|
|
117
|
+
| [SECURITY.md](SECURITY.md) | reporting a vulnerability privately |
|
|
118
|
+
| [SUPPORT.md](SUPPORT.md) | supported versions, and where to ask |
|
|
119
|
+
| [docs/RELEASING.md](docs/RELEASING.md) | the release checklist |
|
|
120
|
+
| [docs/archive/README.en.md](docs/archive/README.en.md) | English index of the archived engineering record |
|
|
213
121
|
|
|
214
122
|
## License
|
|
215
123
|
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
Only the latest published version receives security fixes.
|
|
6
|
+
|
|
7
|
+
| Version | Supported |
|
|
8
|
+
|---|---|
|
|
9
|
+
| the version npm's `latest` tag points at | yes |
|
|
10
|
+
| every earlier version | no |
|
|
11
|
+
|
|
12
|
+
## Reporting a vulnerability
|
|
13
|
+
|
|
14
|
+
Report a suspected vulnerability privately. Use GitHub's private vulnerability
|
|
15
|
+
reporting on this repository:
|
|
16
|
+
|
|
17
|
+
https://github.com/Big-Dao/dsh-plugin-wsl-env/security/advisories/new
|
|
18
|
+
|
|
19
|
+
If that form is not available to you, open an ordinary issue that asks for a
|
|
20
|
+
private channel. Do not put exploit details in that issue. A maintainer will reply
|
|
21
|
+
with a private place to send them.
|
|
22
|
+
|
|
23
|
+
Do not post exploit details in a public issue, a discussion, or a pull request
|
|
24
|
+
before a fix is released.
|
|
25
|
+
|
|
26
|
+
## What to include in a report
|
|
27
|
+
|
|
28
|
+
- The plugin version you tested. `npm ls dsh-plugin-wsl-env` prints it, or name the
|
|
29
|
+
tag you built from.
|
|
30
|
+
- Your Windows build and the distro you used. `wsl.exe -l -v` lists the distros.
|
|
31
|
+
- The DSH version and the profile name.
|
|
32
|
+
- What you expected to happen, and what happened instead.
|
|
33
|
+
- The smallest reproduction you can produce, with the exact command.
|
|
34
|
+
- Whether the sandbox was enabled at the time. `sandbox: true` is the default.
|
|
35
|
+
|
|
36
|
+
## Response times
|
|
37
|
+
|
|
38
|
+
These are targets, not a contract.
|
|
39
|
+
|
|
40
|
+
| Stage | Target |
|
|
41
|
+
|---|---|
|
|
42
|
+
| Acknowledge the report | within 3 business days |
|
|
43
|
+
| Initial assessment and severity | within 10 business days |
|
|
44
|
+
| A fix, or a documented mitigation | within 30 business days |
|
|
45
|
+
|
|
46
|
+
If a report is out of scope, we will say so and explain why.
|
|
47
|
+
|
|
48
|
+
## Scope
|
|
49
|
+
|
|
50
|
+
In scope:
|
|
51
|
+
|
|
52
|
+
- the plugin's own code under `lib/`;
|
|
53
|
+
- the published package `dsh-plugin-wsl-env`.
|
|
54
|
+
|
|
55
|
+
Out of scope:
|
|
56
|
+
|
|
57
|
+
- the upstream DSH packages under `@deepseek-ai/`. Report those to their own
|
|
58
|
+
projects.
|
|
59
|
+
- WSL itself, the Windows kernel, and `bubblewrap`.
|
|
60
|
+
- The configuration of a user's own distro. That includes its `sudoers` rules, its
|
|
61
|
+
mounted filesystems, and the interpreters installed in it.
|
|
62
|
+
- Anything that requires an attacker to already control the user's Windows account
|
|
63
|
+
or their distro account.
|
|
64
|
+
|
|
65
|
+
## Sandbox enforcement is `partial` by design
|
|
66
|
+
|
|
67
|
+
The plugin reports the sandbox enforcement level as `partial`, not `full`. A
|
|
68
|
+
process inside the distro can still start a Windows program through WSL interop,
|
|
69
|
+
for example `/mnt/c/.../*.exe`. That program runs outside the Linux sandbox.
|
|
70
|
+
|
|
71
|
+
This is a documented design boundary, not a vulnerability by itself. The command
|
|
72
|
+
`npm run probe:sandbox` demonstrates it on your machine, and the README states it
|
|
73
|
+
in its Sandbox section.
|
|
74
|
+
|
|
75
|
+
A report is still welcome if you find an escape that does not depend on interop,
|
|
76
|
+
or if the plugin ever reports `full` while interop is reachable.
|
|
77
|
+
|
|
78
|
+
## Credentials
|
|
79
|
+
|
|
80
|
+
No credential, token, or private key belongs in this repository. That includes npm
|
|
81
|
+
tokens, GitHub tokens, and distro passwords. If you find one committed here, report
|
|
82
|
+
it through the private channel above. Do not open a public issue for it.
|