dsh-jev-guard 0.5.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/CHANGELOG.md +285 -0
- package/CHANGELOG.zh-CN.md +271 -0
- package/DEPLOY.md +202 -0
- package/DEPLOY.zh-CN.md +200 -0
- package/LICENSE +21 -0
- package/README.md +316 -0
- package/README.zh-CN.md +315 -0
- package/START-HERE.md +97 -0
- package/START-HERE.zh-CN.md +97 -0
- package/adapters/README.md +37 -0
- package/adapters/README.zh-CN.md +37 -0
- package/adapters/dsh/index.js +502 -0
- package/bin/guard.mjs +634 -0
- package/config.example.json +52 -0
- package/cordis.patch.yml +120 -0
- package/docs/AGENT-TASK-dsh.md +134 -0
- package/docs/AGENT-TASK-dsh.zh-CN.md +131 -0
- package/docs/ARCHITECTURE.md +118 -0
- package/docs/ARCHITECTURE.zh-CN.md +117 -0
- package/docs/DECISIONS.md +469 -0
- package/docs/DECISIONS.zh-CN.md +449 -0
- package/docs/DSH-INTEGRATION.md +178 -0
- package/docs/DSH-INTEGRATION.zh-CN.md +171 -0
- package/docs/MEASUREMENTS.md +433 -0
- package/docs/MEASUREMENTS.zh-CN.md +450 -0
- package/docs/USER-INTERVENTION.md +141 -0
- package/docs/USER-INTERVENTION.zh-CN.md +143 -0
- package/docs/VERIFICATION.md +279 -0
- package/docs/VERIFICATION.zh-CN.md +278 -0
- package/lib/audit.js +228 -0
- package/lib/gate.js +720 -0
- package/lib/i18n.js +575 -0
- package/lib/quota.js +389 -0
- package/lib/rules.js +174 -0
- package/lib/token.js +154 -0
- package/lib/verdict.js +285 -0
- package/package.json +82 -0
- package/tools/check-doc-pairs.mjs +158 -0
- package/tools/extract-commands.mjs +156 -0
- package/tools/gate-cli.mjs +240 -0
- package/tools/probe-prompt-lang.mjs +238 -0
- package/tools/probe-scripts.mjs +143 -0
- package/tools/report-result.mjs +146 -0
- package/tools/selftest-audit.mjs +93 -0
- package/tools/selftest-entry.mjs +177 -0
- package/tools/selftest-i18n.mjs +177 -0
- package/tools/selftest-quota.mjs +260 -0
- package/tools/selftest-reason.mjs +266 -0
- package/tools/selftest-rules.mjs +107 -0
- package/tools/selftest-token.mjs +100 -0
- package/tools/smoke-dsh-adapter.mjs +295 -0
- package/tools/smoke-dsh-pipeline.mjs +146 -0
package/DEPLOY.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# DEPLOY.md — Deployment manual (DSH)
|
|
2
|
+
|
|
3
|
+
> **English** | [简体中文](DEPLOY.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
You are reading the deployment instructions for jev-guard. The goal: make this "pre-execution safety valve" take effect on **DSH**,
|
|
6
|
+
and **have acceptable evidence for every step**. **Both WSL and Windows are supported.**
|
|
7
|
+
|
|
8
|
+
> **Read these two first, then this document:**
|
|
9
|
+
> [`docs/DSH-INTEGRATION.md`](./docs/DSH-INTEGRATION.md) — which of DSH's mechanisms it uses, how the four states map,
|
|
10
|
+
> the degradation contract, and the retrospective on "the plugin is installed ≠ it is really blocking".
|
|
11
|
+
> [`docs/USER-INTERVENTION.md`](./docs/USER-INTERVENTION.md) — the three channels a human can step in through.
|
|
12
|
+
>
|
|
13
|
+
> **If you are "an AI sent to deploy this", read [START-HERE.md](./START-HERE.md) first** — it has the pasteable task
|
|
14
|
+
> prompt and the hard constraints (which files may be changed). When you have finished, come back to this document and follow the steps;
|
|
15
|
+
> write the conclusion back to `verification-results/` with `tools/report-result.mjs`.
|
|
16
|
+
|
|
17
|
+
## 0. How to work (read this first)
|
|
18
|
+
|
|
19
|
+
1. **Do not skip steps.** The order is: first prove the judging layer works (§2.3) → then install it into DSH (§2.4) → finally the acceptance (§3).
|
|
20
|
+
2. **Back up the original value before changing any DSH configuration**, and state clearly in the conclusion how to roll back (§6).
|
|
21
|
+
3. **Verify against side effects, not against "it didn't error".** This pack has really had three layers of silent failure: the script exits 0 without a sound,
|
|
22
|
+
the command runs anyway, and the log has nothing at all (see `docs/MEASUREMENTS.md` §10).
|
|
23
|
+
4. **Never print the key.** Read it from `secrets.json` or the credentials layer when needed, and report only its length or the last digits of a hash.
|
|
24
|
+
5. Every command can first be trial-run read-only (`selftest`, `rules`, `judge`, `status`) — they change no host configuration.
|
|
25
|
+
|
|
26
|
+
## 1. Prerequisites
|
|
27
|
+
|
|
28
|
+
| Item | Requirement | Check |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| Node | ≥ 20 (uses the global `fetch`, `AbortSignal.any`) | `node -v` |
|
|
31
|
+
| TypeSafe key | of the form `apikey_...`; if you don't have one, apply at https://console.typesafe.ai | §2.2 |
|
|
32
|
+
| Directory location | `T:\dsh-jev-guard` recommended (in WSL that is `/mnt/t/dsh-jev-guard`) | `ls /mnt/t/dsh-jev-guard` |
|
|
33
|
+
| Network | able to reach `https://api.typesafe.ai` | `node bin/guard.mjs judge 'pnpm test'` |
|
|
34
|
+
| DSH | able to install local plugins (the profile's `package.json` has `dsh.profile` / bundles) | `dsh --profile <name> --dump-config` |
|
|
35
|
+
| DSH version | **0.1.6-alpha.2** — the only release this plugin is verified on; no host requirement is declared in `package.json`, so the plugin market never blocks install on a different version | `dsh --version` |
|
|
36
|
+
|
|
37
|
+
## 2. Install and configure
|
|
38
|
+
|
|
39
|
+
### 2.1 Put the directory in place
|
|
40
|
+
|
|
41
|
+
Put the whole `jev-guard` directory at `T:\dsh-jev-guard` (on the WSL side that is `/mnt/t/dsh-jev-guard`, **the same files**).
|
|
42
|
+
**`npm install` is not needed** — zero dependencies, only Node's built-in modules.
|
|
43
|
+
|
|
44
|
+
### 2.2 The key
|
|
45
|
+
|
|
46
|
+
Three sources, highest precedence first:
|
|
47
|
+
|
|
48
|
+
1. **The DSH credentials layer** (recommended): `ctx.credentials.resolve('TYPESAFE_API_KEY')` — goes through DSH's own credential store,
|
|
49
|
+
and after a rotation **needs no restart**.
|
|
50
|
+
2. The environment variable `TYPESAFE_API_KEY` (the name is decided by `apiKeyEnv` in `config.json`).
|
|
51
|
+
3. A `secrets.json` **you create yourself in the package root**, containing `{"TYPESAFE_API_KEY": "apikey_..."}`
|
|
52
|
+
(**if `apiKeyFile` is given a relative path it resolves against the package root, independently of the current directory** — true on both Windows and WSL).
|
|
53
|
+
|
|
54
|
+
Do not commit any of the three into any repository.
|
|
55
|
+
|
|
56
|
+
Record the third one with `node bin/guard.mjs key set` — it reads the key from **stdin only** (never from an
|
|
57
|
+
argument, which would land in your shell history and in `ps`), writes `apiKeyFile` with mode `0600`, keeps any
|
|
58
|
+
other keys already in that file, and prints the length and the path, never the value. `node bin/guard.mjs key
|
|
59
|
+
status` reports which source resolves and exits 3 when none does, so it doubles as a health check.
|
|
60
|
+
|
|
61
|
+
**A missing key degrades; it does not go quiet (D15).** With no key resolved the paid semantic layer is
|
|
62
|
+
paused — the free L0 rules and the pre-screen keep working — and the state is **sticky**: it does not expire
|
|
63
|
+
with time (there is nothing to probe), it ends the moment a key resolves, cleared on the spot with zero
|
|
64
|
+
requests and no restart. It is also scoped to the entry that reported it (`'cli'` or `'dsh-adapter'`), so a
|
|
65
|
+
CLI that cannot see a key does not stop DSH from judging, and vice versa. `guard status` exits 3 while
|
|
66
|
+
degraded, and the DSH session gets a one-line notice in the conversation saying which state the valve is in.
|
|
67
|
+
|
|
68
|
+
### 2.2b Language (optional, it runs without configuring it)
|
|
69
|
+
|
|
70
|
+
The copy (verdict reasons / CLI output / degradation warnings) exists in Chinese and English, and `lang` defaults to `'auto'`:
|
|
71
|
+
it resolves from `JEV_GUARD_LANG` → `LC_ALL`/`LC_MESSAGES`/`LANG`, **and only takes effect when those variables really name a supported language**
|
|
72
|
+
(such as `en_US.UTF-8` / `zh_CN.UTF-8`); otherwise (including `C.UTF-8`, or unset) it always uses `zh-CN`.
|
|
73
|
+
**This deliberately does not look at the system locale**: under WSL's common `LANG=C.UTF-8`, Node's `Intl` reports `en-US`,
|
|
74
|
+
which would quietly turn a Chinese session's reasons into English (measured 2026-09-20). To choose the language explicitly: write `"lang": "en"` in `config.json`,
|
|
75
|
+
set `JEV_GUARD_LANG=en` for the DSH process, or use the CLI's one-off `--lang en`.
|
|
76
|
+
|
|
77
|
+
**Do not casually change `promptLang`.** What it governs is the question sent to the judging service; it defaults to Chinese, which is exactly the language the 0.5/0.7 thresholds were calibrated against;
|
|
78
|
+
measured, switching it to English lowers p by about 0.04 on average, and three probes flip towards allow (`docs/MEASUREMENTS.md` §14).
|
|
79
|
+
If you really want to switch: re-calibrate first, or lower both thresholds by about 0.04 together.
|
|
80
|
+
|
|
81
|
+
### 2.3 First prove the judging layer works (not yet installed into DSH)
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
cd /mnt/t/dsh-jev-guard
|
|
85
|
+
node bin/guard.mjs selftest # expect: all 12 checks pass (offline)
|
|
86
|
+
node bin/guard.mjs rules | head -5 # expect: 21 deny + 16 ask rules listed
|
|
87
|
+
node bin/guard.mjs judge 'ls -la' 'git push --force origin main' 'pnpm test'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
| Command | Expected action | Expected source |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `ls -la` | `allow` | `prefilter` (offline) |
|
|
93
|
+
| `git push --force origin main` | `block` | `static-rule` (offline) |
|
|
94
|
+
| `pnpm test` | `allow` | `jev` (online, `p` should be far below 0.5) |
|
|
95
|
+
|
|
96
|
+
On failure: `source: error` = a key or a network problem (see the `errorKind` classification); a `selftest` failure = the pack is incomplete.
|
|
97
|
+
|
|
98
|
+
**Then run the whole set of offline self-checks again** (seven of them, cross-platform):
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
for t in selftest-entry selftest-i18n selftest-quota selftest-reason selftest-token selftest-rules selftest-audit; do
|
|
102
|
+
printf '%-18s ' "$t"; node tools/$t.mjs | tail -1
|
|
103
|
+
done
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 2.4 Install it into DSH
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
dsh plugin --profile <profile> add /mnt/t/dsh-jev-guard # on the Windows side use T:\dsh-jev-guard
|
|
110
|
+
# then restart DSH — plugins are not hot-reloaded
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
After installing, **confirm the plugin is really mounted** (don't look at "it didn't error"):
|
|
114
|
+
|
|
115
|
+
1. In a session, run a command that **is certain to be blocked** (for example `git push --force origin main`, which hits an L0 hard rule and costs nothing).
|
|
116
|
+
Expect: refused, with `hard rule git-force-push hit` in the reason.
|
|
117
|
+
2. Look at the audit: `node bin/guard.mjs log --tail 3` — that entry should appear, with `policy` and `preset`.
|
|
118
|
+
|
|
119
|
+
It only counts as installed when both hold. **Only when item 1 does not hold**, first check the three layers of silent failure in `docs/DSH-INTEGRATION.md` §5.
|
|
120
|
+
|
|
121
|
+
### 2.5 Differences on Windows
|
|
122
|
+
|
|
123
|
+
| Item | WSL | Windows |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| Tool to intercept | `bash` | `pwsh` (**already in the default `tools` list**) |
|
|
126
|
+
| Quoting in the authorisation line | POSIX `'\''` | **PowerShell `''`** (the plugin switches automatically by platform) |
|
|
127
|
+
| cmd.exe users | — | use `guard allow --command-file cmd.txt` (independent of the shell's quoting rules) |
|
|
128
|
+
| State/log directory | `~/.jev-guard/` | `%USERPROFILE%\.jev-guard\` |
|
|
129
|
+
|
|
130
|
+
## 3. Acceptance (all must pass before continuing)
|
|
131
|
+
|
|
132
|
+
The acceptance checklist is in **[docs/VERIFICATION.md](./docs/VERIFICATION.md)**, and includes **the three human intervention channels** (U1–U3).
|
|
133
|
+
Summary:
|
|
134
|
+
|
|
135
|
+
| # | Acceptance | Criteria |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| 1 | The probe command is blocked after installation | the command really is refused + `guard.log` has the record |
|
|
138
|
+
| 2 | The L0 path (offline, cannot be overridden) | `mkfs` / `git push --force` → `block` / `static-rule` |
|
|
139
|
+
| 3 | The Jev path (online semantic judgment) | `rm -rf` on a real directory → `revise` or `block`, with `p` in the record |
|
|
140
|
+
| 4 | The false-positive defence (prose / redirection not hit by mistake) | a dangerous phrase inside the command text, and a command ending in `2>/dev/null`, are both **not blocked** |
|
|
141
|
+
| 5 | Audit log | one JSONL line per verdict; `log --stats` has actions/sources/rules/failure breakdown/cost |
|
|
142
|
+
| 6 | The token closed loop | authorise → retry the same one → allowed once → the token disappears, with `source: token` in the record |
|
|
143
|
+
| 7 | The authorisation entry point is only on an interactive terminal | a non-TTY is refused and prints the whole copyable command line |
|
|
144
|
+
| 8 | The approval prompt (policy `ask`) | the prompt appears and carries the valve's reason text as it stands; after clicking allow the command runs |
|
|
145
|
+
| 9 | Quota degradation | 402/401 → degradation, zero requests, `guard status` exit code 3, L0 still blocks |
|
|
146
|
+
| 10 | A human running it by hand ≠ granting the AI permission | zero new audit entries, and an AI retry is **still blocked** |
|
|
147
|
+
|
|
148
|
+
## 4. Runtime
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
node bin/guard.mjs log --tail 20 # the last 20 verdicts
|
|
152
|
+
node bin/guard.mjs log --stats # summary: actions/sources/rules/failure breakdown/cost
|
|
153
|
+
node bin/guard.mjs status # health status (exit code 3 while degraded)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## 5. Overall acceptance checklist
|
|
157
|
+
|
|
158
|
+
- [ ] `node bin/guard.mjs selftest` 12/12
|
|
159
|
+
- [ ] all seven `tools/selftest-*.mjs` pass (**run once each on Windows and WSL**)
|
|
160
|
+
- [ ] `judge 'ls -la'` = allow / prefilter (zero network calls)
|
|
161
|
+
- [ ] `judge 'git push --force origin main'` = block / static-rule
|
|
162
|
+
- [ ] `judge 'rm -rf ~/<a real directory>'` = revise or block (online judgment)
|
|
163
|
+
- [ ] after installing it into DSH, a command that is certain to be blocked **really is blocked**, and `guard.log` has the record
|
|
164
|
+
- [ ] `guard status` outputs "✅ healthy" (health self-check; exit code 3 while degraded)
|
|
165
|
+
- [ ] **the three human intervention channels U1–U3** run through once each (token / approval prompt / a human running it by hand)
|
|
166
|
+
- [ ] rollback drill: remove it per §6 and confirm the original state is restored
|
|
167
|
+
|
|
168
|
+
## 6. Rollback
|
|
169
|
+
|
|
170
|
+
| Action | Command |
|
|
171
|
+
|---|---|
|
|
172
|
+
| Uninstall the plugin | `dsh plugin --profile <profile> remove jev-guard` + restart |
|
|
173
|
+
| One-click return to a configuration snapshot | `dsh-undo-savepoint`'s `undo_list` / `undo_restore` |
|
|
174
|
+
| Only want to disable it | `dsh-undo-savepoint`'s SAFE MODE (`undo_safe_mode on`) disables all user plugins |
|
|
175
|
+
| Clear state/log | delete `~/.jev-guard/` (it writes nowhere else) |
|
|
176
|
+
|
|
177
|
+
## 7. Troubleshooting
|
|
178
|
+
|
|
179
|
+
| Symptom | Cause | Handling |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| The plugin is installed but nothing is blocked | the plugin is not mounted, or the package path is wrong | run `selftest-entry` + see whether `guard.log` has records; read `DSH-INTEGRATION.md` §5 |
|
|
182
|
+
| `source: error`, with the reason `HTTP 401` | the key is invalid or revoked | change the key; **in the meantime the valve has already degraded automatically for 30 minutes** (it sends no more requests), so once it is fixed either wait for the cooldown to expire and it recovers by itself, or `guard status --clear` |
|
|
183
|
+
| `source: error`, with the reason `HTTP 402` | the credit is used up | same as the line above (this one **degrades** rather than retrying every time, which saves money) |
|
|
184
|
+
| `source: degraded` | inside a degradation window | `guard status` will say which class it is + how much is left; the free L0 + pre-screen still work |
|
|
185
|
+
| `source: error`, with the reason `fetch failed` | the network/proxy is unreachable | check that `https://api.typesafe.ai` is reachable. It does **not degrade** (transient), but it accumulates in the "failure breakdown" |
|
|
186
|
+
| A dangerous command was not blocked | not in L0 and `p < lowThreshold` | look at the `p` in the `judge` output; if necessary lower `lowThreshold` or add an L0 rule for that class of command |
|
|
187
|
+
| Everything is blocked and no work can be done | the threshold is too low or L0 is too aggressive | look at the `rule.id` from `judge` first; edit `lib/rules.js` or raise `lowThreshold` |
|
|
188
|
+
| The authorisation line pasted into cmd.exe reports a syntax error | cmd does not accept POSIX/PowerShell quoting | switch to `guard allow --command-file cmd.txt` |
|
|
189
|
+
| A judging-service hiccup makes work impossible | this should not happen (fail-open) | if it really does happen, check `source: error` and `errorKind` in `guard.log` |
|
|
190
|
+
|
|
191
|
+
## 8. Security and privacy (must be passed on to the user as it stands)
|
|
192
|
+
|
|
193
|
+
1. **The script body is sent to TypeSafe's API.** That is the price of "understanding what `node x.mjs` does".
|
|
194
|
+
Sensitive paths (`.env` / `.ssh` / `*.pem` / `*credential*` / `*secret*` / `*token*`) are skipped automatically, with an 8KB per-file cap.
|
|
195
|
+
To turn it off entirely: `inlineScripts: false` in `config.json` (at the cost of those commands dropping back into the p≈0.31 blind spot).
|
|
196
|
+
2. **The key is read only from the credentials layer / environment variable / `secrets.json`, and is written into no log and no report.** The command text in a report goes through masking.
|
|
197
|
+
3. **Every failure is fail-open**: when the judging service is unavailable the valve blocks nothing — because DSH's own sandbox preset
|
|
198
|
+
(anything except `danger-full-access`) is still in force before execution. If you want "block even when the service is down", thicken the L0 rules rather than changing this policy.
|
|
199
|
+
4. **It cannot stop a deliberate bypass.** Rewrapping it, encoding it, or writing `~/.jev-guard/allow.txt` directly can all get around it.
|
|
200
|
+
5. **What it guards against is accidents, not an adversary — that scope was fixed explicitly by the user; do not narrow it on your own initiative.**
|
|
201
|
+
There are at least two known and **deliberately kept** bypasses: file-writing tools writing `allow.txt` directly; and writing the same thing another way to get around the judgment.
|
|
202
|
+
The handling is to **record it**, not to seal it. The original decision text and the reasoning: [docs/DECISIONS.md](./docs/DECISIONS.md) **D1**.
|
package/DEPLOY.zh-CN.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# DEPLOY.md — 部署手册(DSH)
|
|
2
|
+
|
|
3
|
+
> [English](DEPLOY.md) | **简体中文**
|
|
4
|
+
|
|
5
|
+
你正在读的是 jev-guard 的部署说明。目标:让这条"执行前安全阀门"在 **DSH** 上生效,
|
|
6
|
+
并且**每一步都有可验收的证据**。**WSL 与 Windows 都支持。**
|
|
7
|
+
|
|
8
|
+
> **先读这两份,再读本文:**
|
|
9
|
+
> [`docs/DSH-INTEGRATION.md`](./docs/DSH-INTEGRATION.md) —— 它用 DSH 的哪些机制、四态怎么映射、
|
|
10
|
+
> 降级契约、以及"插件装上了≠真的在拦"这件事的复盘。
|
|
11
|
+
> [`docs/USER-INTERVENTION.md`](./docs/USER-INTERVENTION.md) —— 人的三条介入通道。
|
|
12
|
+
>
|
|
13
|
+
> **如果你是"被派来部署的 AI",先读 [START-HERE.md](./START-HERE.md)** —— 那里有可粘贴的任务
|
|
14
|
+
> 提示词与硬约束(能改哪些文件)。读完再回到本文按步骤执行;
|
|
15
|
+
> 结论用 `tools/report-result.mjs` 写回 `verification-results/`。
|
|
16
|
+
|
|
17
|
+
## 0. 工作方式(先读这一段)
|
|
18
|
+
|
|
19
|
+
1. **不要跳步。** 顺序是:先证明判定层能工作(§2.3)→ 再装进 DSH(§2.4)→ 最后验收(§3)。
|
|
20
|
+
2. **改任何 DSH 配置前先备份原值**,并在结论里写清怎么回滚(§6)。
|
|
21
|
+
3. **验证要看副作用,不看"没报错"。** 本包真实发生过三层静默失效:脚本一声不响地退出 0、
|
|
22
|
+
命令照跑、日志什么都没有(见 `docs/MEASUREMENTS.md` §10)。
|
|
23
|
+
4. **绝不打印密钥。** 需要时从 `secrets.json` 或凭据层读,只报长度或哈希尾号。
|
|
24
|
+
5. 所有命令都可以先只读地试跑(`selftest`、`rules`、`judge`、`status`)—— 它们不改任何宿主配置。
|
|
25
|
+
|
|
26
|
+
## 1. 前置条件
|
|
27
|
+
|
|
28
|
+
| 项 | 要求 | 检查 |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| Node | ≥ 20(用到全局 `fetch`、`AbortSignal.any`) | `node -v` |
|
|
31
|
+
| TypeSafe 密钥 | 形如 `apikey_...`;没有就去 https://console.typesafe.ai 申请 | §2.2 |
|
|
32
|
+
| 目录位置 | 建议 `T:\dsh-jev-guard`(WSL 里是 `/mnt/t/dsh-jev-guard`) | `ls /mnt/t/dsh-jev-guard` |
|
|
33
|
+
| 网络 | 能访问 `https://api.typesafe.ai` | `node bin/guard.mjs judge 'pnpm test'` |
|
|
34
|
+
| DSH | 能装本地插件(profile 的 `package.json` 有 `dsh.profile` / bundles) | `dsh --profile <名> --dump-config` |
|
|
35
|
+
| DSH 版本 | **0.1.6-alpha.2** —— 本插件唯一验证过的版本;`package.json` 未声明宿主要求,所以插件市场不会因版本不同而阻拦安装 | `dsh --version` |
|
|
36
|
+
|
|
37
|
+
## 2. 安装与配置
|
|
38
|
+
|
|
39
|
+
### 2.1 放好目录
|
|
40
|
+
|
|
41
|
+
整个 `jev-guard` 目录放到 `T:\dsh-jev-guard`(WSL 侧即 `/mnt/t/dsh-jev-guard`,**同一份文件**)。
|
|
42
|
+
**不需要 `npm install`** —— 零依赖,只用 Node 内置模块。
|
|
43
|
+
|
|
44
|
+
### 2.2 密钥
|
|
45
|
+
|
|
46
|
+
三种来源,优先级从高到低:
|
|
47
|
+
|
|
48
|
+
1. **DSH 凭据层**(推荐):`ctx.credentials.resolve('TYPESAFE_API_KEY')` —— 走 DSH 自己的凭据存储,
|
|
49
|
+
轮换后**无需重启**。
|
|
50
|
+
2. 环境变量 `TYPESAFE_API_KEY`(名字由 `config.json` 的 `apiKeyEnv` 决定)。
|
|
51
|
+
3. **你自己在包根建的** `secrets.json`,内容 `{"TYPESAFE_API_KEY": "apikey_..."}`
|
|
52
|
+
(**`apiKeyFile` 若给相对路径,按包根解析,与当前目录无关** —— Windows/WSL 都成立)。
|
|
53
|
+
|
|
54
|
+
三种都不要提交进任何仓库。
|
|
55
|
+
|
|
56
|
+
第三种用 `node bin/guard.mjs key set` 录:**只从标准输入**读密钥(绝不接受参数 —— 那会进 shell 历史与
|
|
57
|
+
`ps`),写 `apiKeyFile`、权限 `0600`,保留文件里已有的其它键,只打印长度与路径、永不打印值。
|
|
58
|
+
`node bin/guard.mjs key status` 说明当前哪个来源在生效,没有密钥时退出码 3,可以直接当健康检查。
|
|
59
|
+
|
|
60
|
+
**没有密钥会降级,不会装死(D15)。** 解析不到密钥时,付费的语义层暂停 —— 免费的 L0 规则与预筛照常
|
|
61
|
+
工作 —— 而且这个状态是**粘性**的:不随时间到期(没有可探测对象),密钥一出现就结束,当场清除、零请求、
|
|
62
|
+
不用重启。它还带作用域,只压制写下它的那条入口(`'cli'` 或 `'dsh-adapter'`),所以 CLI 看不到密钥不会
|
|
63
|
+
让 DSH 停止判定,反之亦然。降级期间 `guard status` 退出码为 3,DSH 会话里还会在对话中出现一行提示,
|
|
64
|
+
说明阀门当前处于什么状态。
|
|
65
|
+
|
|
66
|
+
### 2.2b 语言(可选,不配也能跑)
|
|
67
|
+
|
|
68
|
+
文案(判定理由 / CLI 输出 / 降级告警)有中英两份,`lang` 默认 `'auto'`:
|
|
69
|
+
按 `JEV_GUARD_LANG` → `LC_ALL`/`LC_MESSAGES`/`LANG` 解析,**只有当这些变量真的指明了一种受支持的语言**
|
|
70
|
+
(如 `en_US.UTF-8` / `zh_CN.UTF-8`)才生效;否则(含 `C.UTF-8`、未设置)一律用 `zh-CN`。
|
|
71
|
+
**这里刻意不看系统 locale**:WSL 常见的 `LANG=C.UTF-8` 下 Node 的 `Intl` 会报 `en-US`,
|
|
72
|
+
那会让中文会话的理由悄悄变英文(2026-09-20 实测)。想显式选语言:`config.json` 里写 `"lang": "en"`,
|
|
73
|
+
给 DSH 进程设 `JEV_GUARD_LANG=en`,或 CLI 单次 `--lang en`。
|
|
74
|
+
|
|
75
|
+
**别顺手改 `promptLang`。** 它管的是发给判定服务的那句问话,默认中文,正是阈值 0.5/0.7 的标定语言;
|
|
76
|
+
实测换成英文后 p 平均压低约 0.04,且有三条探针翻向放行(`docs/MEASUREMENTS.md` §14)。
|
|
77
|
+
真要切:先重标定,或把两个阈值一起下调约 0.04。
|
|
78
|
+
|
|
79
|
+
### 2.3 先证明判定层能工作(还没装进 DSH)
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
cd /mnt/t/dsh-jev-guard
|
|
83
|
+
node bin/guard.mjs selftest # 期望:12 项全部通过(不联网)
|
|
84
|
+
node bin/guard.mjs rules | head -5 # 期望:列出 21 条 deny + 16 条 ask
|
|
85
|
+
node bin/guard.mjs judge 'ls -la' 'git push --force origin main' 'pnpm test'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
| 命令 | 期望 action | 期望 source |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `ls -la` | `allow` | `prefilter`(不联网) |
|
|
91
|
+
| `git push --force origin main` | `block` | `static-rule`(不联网) |
|
|
92
|
+
| `pnpm test` | `allow` | `jev`(联网,`p` 应远低于 0.5) |
|
|
93
|
+
|
|
94
|
+
失败时:`source: error` = 密钥或网络问题(看 `errorKind` 分类);`selftest` 失败 = 包不完整。
|
|
95
|
+
|
|
96
|
+
**再跑一次整套离线自检**(七份,跨平台):
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
for t in selftest-entry selftest-i18n selftest-quota selftest-reason selftest-token selftest-rules selftest-audit; do
|
|
100
|
+
printf '%-18s ' "$t"; node tools/$t.mjs | tail -1
|
|
101
|
+
done
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 2.4 装进 DSH
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
dsh plugin --profile <profile> add /mnt/t/dsh-jev-guard # Windows 侧换成 T:\dsh-jev-guard
|
|
108
|
+
# 然后重启 DSH —— 插件没有热加载
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
装完后**确认插件真的挂上了**(别看"没报错"):
|
|
112
|
+
|
|
113
|
+
1. 在会话里跑一条**必然被拦**的命令(例如 `git push --force origin main`,它命中 L0 硬规则,不花钱)。
|
|
114
|
+
期望:被拒绝,理由里有 `命中硬规则 git-force-push`。
|
|
115
|
+
2. 看审计:`node bin/guard.mjs log --tail 3` —— 应出现那一条记录,且带 `policy` 与 `preset`。
|
|
116
|
+
|
|
117
|
+
两条都成立才算装上。**只有第 1 条不成立时**,先查 `docs/DSH-INTEGRATION.md` §5 那三层静默失效。
|
|
118
|
+
|
|
119
|
+
### 2.5 Windows 上的差异
|
|
120
|
+
|
|
121
|
+
| 项 | WSL | Windows |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| 要拦的工具 | `bash` | `pwsh`(**已在默认 `tools` 列表里**) |
|
|
124
|
+
| 授权行引号 | POSIX `'\''` | **PowerShell `''`**(插件按平台自动切换) |
|
|
125
|
+
| cmd.exe 用户 | — | 用 `guard allow --command-file cmd.txt`(与 shell 的引号规则无关) |
|
|
126
|
+
| 状态/日志目录 | `~/.jev-guard/` | `%USERPROFILE%\.jev-guard\` |
|
|
127
|
+
|
|
128
|
+
## 3. 验收(必须全过才继续)
|
|
129
|
+
|
|
130
|
+
验收清单在 **[docs/VERIFICATION.md](./docs/VERIFICATION.md)**,含**三条人工介入通道**(U1–U3)。
|
|
131
|
+
摘要:
|
|
132
|
+
|
|
133
|
+
| # | 验收 | 判定依据 |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| 1 | 安装后 probe 命令被拦 | 命令真的被拒 + `guard.log` 有记录 |
|
|
136
|
+
| 2 | L0 路径(不联网、不可覆盖) | `mkfs` / `git push --force` → `block` / `static-rule` |
|
|
137
|
+
| 3 | Jev 路径(联网语义判定) | 真实目录 `rm -rf` → `revise` 或 `block`,记录里带 `p` |
|
|
138
|
+
| 4 | 误报防线(散文/重定向不误伤) | 命令文本里的危险短语、以 `2>/dev/null` 结尾的命令都**不被拦** |
|
|
139
|
+
| 5 | 审计日志 | 每个判定一行 JSONL;`log --stats` 有动作/来源/规则/失败分类/成本 |
|
|
140
|
+
| 6 | 令牌闭环 | 授权 → 重试同一条 → 放行一次 → 令牌消失,记录里 `source: token` |
|
|
141
|
+
| 7 | 授权入口只在交互终端 | 非 TTY 被拒并打印可复制的整行命令 |
|
|
142
|
+
| 8 | 审批弹窗(策略 `ask`) | 弹窗出现且带阀门理由原文;点允许后命令执行 |
|
|
143
|
+
| 9 | 额度降级 | 402/401 → 降级、零请求、`guard status` 退出码 3、L0 仍拦 |
|
|
144
|
+
| 10 | 人工手动执行 ≠ 给 AI 授权 | 审计零新增,且 AI 重试**仍然被拦** |
|
|
145
|
+
|
|
146
|
+
## 4. 运行期
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
node bin/guard.mjs log --tail 20 # 最近 20 条判定
|
|
150
|
+
node bin/guard.mjs log --stats # 汇总:动作/来源/规则/失败分类/成本
|
|
151
|
+
node bin/guard.mjs status # 健康状态(降级时退出码 3)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 5. 总验收清单
|
|
155
|
+
|
|
156
|
+
- [ ] `node bin/guard.mjs selftest` 12/12
|
|
157
|
+
- [ ] 七份 `tools/selftest-*.mjs` 全过(**Windows 与 WSL 各跑一遍**)
|
|
158
|
+
- [ ] `judge 'ls -la'` = allow / prefilter(零网络调用)
|
|
159
|
+
- [ ] `judge 'git push --force origin main'` = block / static-rule
|
|
160
|
+
- [ ] `judge 'rm -rf ~/某个真实目录'` = revise 或 block(联网判定)
|
|
161
|
+
- [ ] 装进 DSH 后,一条必然被拦的命令**真的被拦**,且 `guard.log` 有记录
|
|
162
|
+
- [ ] `guard status` 输出"✅ 正常"(健康自检;降级时退出码为 3)
|
|
163
|
+
- [ ] **人工介入三通道 U1–U3** 各走一遍(令牌 / 审批弹窗 / 人工手动执行)
|
|
164
|
+
- [ ] 回滚演练:按 §6 撤掉,确认恢复原状
|
|
165
|
+
|
|
166
|
+
## 6. 回滚
|
|
167
|
+
|
|
168
|
+
| 动作 | 命令 |
|
|
169
|
+
|---|---|
|
|
170
|
+
| 卸掉插件 | `dsh plugin --profile <profile> remove jev-guard` + 重启 |
|
|
171
|
+
| 一键回到某个配置快照 | `dsh-undo-savepoint` 的 `undo_list` / `undo_restore` |
|
|
172
|
+
| 只想停用 | `dsh-undo-savepoint` 的 SAFE MODE(`undo_safe_mode on`)让所有用户插件停用 |
|
|
173
|
+
| 清状态/日志 | 删 `~/.jev-guard/`(它不写其他位置) |
|
|
174
|
+
|
|
175
|
+
## 7. 故障排查
|
|
176
|
+
|
|
177
|
+
| 症状 | 原因 | 处理 |
|
|
178
|
+
|---|---|---|
|
|
179
|
+
| 装了插件但什么都不拦 | 插件没挂上,或包路径不对 | 跑 `selftest-entry` + 看 `guard.log` 有没有记录;读 `DSH-INTEGRATION.md` §5 |
|
|
180
|
+
| `source: error`,理由是 `HTTP 401` | 密钥无效或被撤销 | 换密钥;**同时阀门已自动降级 30 分钟**(不再发请求),修好后等冷却到期自动恢复,或 `guard status --clear` |
|
|
181
|
+
| `source: error`,理由是 `HTTP 402` | 额度用尽 | 同上一行(这条会**降级**而不是逐次重试,省钱) |
|
|
182
|
+
| `source: degraded` | 处在降级窗口内 | `guard status` 会说明是哪一类 + 还剩多久;免费的 L0 + 预筛仍在工作 |
|
|
183
|
+
| `source: error`,理由是 `fetch failed` | 网络/代理不通 | 检查 `https://api.typesafe.ai` 可达性。**不会降级**(瞬态),但会累计在"失败分类"里 |
|
|
184
|
+
| 危险命令没被拦 | 不在 L0 且 `p < lowThreshold` | 看 `judge` 输出的 `p`;必要时调低 `lowThreshold` 或给该类命令加 L0 规则 |
|
|
185
|
+
| 全被拦,干不了活 | 阈值过低或 L0 太激进 | 先看 `judge` 的 `rule.id`;编辑 `lib/rules.js` 或调高 `lowThreshold` |
|
|
186
|
+
| 授权行粘到 cmd.exe 里报语法错 | cmd 不认 POSIX/PowerShell 的引号 | 改用 `guard allow --command-file cmd.txt` |
|
|
187
|
+
| 判定服务抖动导致干不了活 | 不该发生(fail-open) | 若确实发生,查 `guard.log` 里的 `source: error` 与 `errorKind` |
|
|
188
|
+
|
|
189
|
+
## 8. 安全与隐私(必须原样转告用户)
|
|
190
|
+
|
|
191
|
+
1. **脚本正文会被发送到 TypeSafe 的 API。** 这是"看懂 `node x.mjs` 干了什么"的代价。
|
|
192
|
+
敏感路径(`.env` / `.ssh` / `*.pem` / `*credential*` / `*secret*` / `*token*`)自动跳过,单文件 8KB 上限。
|
|
193
|
+
想彻底关闭:`config.json` 里 `inlineScripts: false`(代价是这类命令退回 p≈0.31 的盲区)。
|
|
194
|
+
2. **密钥只从凭据层 / 环境变量 / `secrets.json` 读取,不写进任何日志和报告。** 报告里的命令文本会经过掩码。
|
|
195
|
+
3. **失败一律放行(fail-open)**:判定服务不可用时阀门不拦任何东西 —— 因为 DSH 自己的沙箱档位
|
|
196
|
+
(除 `danger-full-access` 外)仍在执行之前。想"服务挂了也拦",把 L0 规则加厚,而不是改这条策略。
|
|
197
|
+
4. **它挡不住蓄意绕过。** 换壳、编码、直接写 `~/.jev-guard/allow.txt` 都可能绕开。
|
|
198
|
+
5. **它防的是事故,不是对手 —— 这个范围是用户显式定下来的,别自作主张收窄。**
|
|
199
|
+
已知且**有意保留**的旁路至少两条:文件写入类工具直接写 `allow.txt`;换一种写法绕开判定。
|
|
200
|
+
处置方式是**记录在案**,不是堵。决策原文与理由:[docs/DECISIONS.md](./docs/DECISIONS.md) **D1**。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 7starsseeker
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|