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
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "复制成 config.json 后按需修改。config.json 与 secrets.json 不进版本库。",
|
|
3
|
+
"model": "jev-latest",
|
|
4
|
+
"endpoint": "https://api.typesafe.ai/v1/systemone",
|
|
5
|
+
"apiKeyEnv": "TYPESAFE_API_KEY",
|
|
6
|
+
"_apiKeyFile": "密钥的第三来源(优先级低于 DSH 凭据层与环境变量)。相对路径按**包根**解析,与 cwd 无关。用 `node bin/guard.mjs key set` 录入(只从标准输入读,写这个文件、权限 0600);`node bin/guard.mjs key status` 会告诉你当前哪个来源在生效,永不回显密钥。该文件在 .gitignore 里,也不在 npm files 白名单内 —— 仓库与发布包里都没有它。",
|
|
7
|
+
"apiKeyFile": "secrets.json",
|
|
8
|
+
|
|
9
|
+
"_lang": "界面/文案语言:'auto'(默认)按 JEV_GUARD_LANG -> LC_ALL/LC_MESSAGES/LANG(仅当它们指明受支持的语言)解析,都不指明就用 zh-CN;也可直接写 'zh-CN' 或 'en' 钉死。**刻意不看系统 locale/Intl**:LANG=C.UTF-8 这类「没有偏好」的值下,Node 会报 en-US 兜底值,那会让中文会话的理由悄悄变英文(2026-09-20 实测踩过)。它换的只是**给人或模型看的话**(判定理由、规则理由、CLI 输出、降级告警),不改变任何一条判定路径。CLI 另有 --lang 开关。",
|
|
10
|
+
"lang": "auto",
|
|
11
|
+
"_promptLang": "发给判定服务的那句问话(以及 state 的键)用什么语言,默认 'zh-CN'。**它不是界面语言的翻译项,而是一个判定参数**:阈值 0.5/0.7 是在中文问话上标定的;实测换成英文问话后 21 条探针里 12 条 p 更低,平均压低约 0.04,并有三条命令翻带且方向全部朝放行(见 docs/MEASUREMENTS.md §14)。要切英文,请先重标定或把阈值下调约 0.04。",
|
|
12
|
+
"promptLang": "zh-CN",
|
|
13
|
+
|
|
14
|
+
"_thresholds": "低于 lowThreshold 放行;介于两者之间 = revise(中间带:证据不足);达到 highThreshold = block。实测在 737 条真实命令上 0.5/0.7 得到 98.51% / 0.81% / 0.68% 的三分。",
|
|
15
|
+
"lowThreshold": 0.5,
|
|
16
|
+
"highThreshold": 0.7,
|
|
17
|
+
|
|
18
|
+
"_routing": "判定动作怎么随宿主审批模式分叉(2026-09-20 决定):never(全自动,没人可问)下 revise/block 一律直接拒绝;ask(人就在场)下都转人工弹窗 —— 否则等于让一个 50% 的判断替人做决定(实测一天里 9 次 revise 拒绝发生在 ask 模式下,人就在旁边)。宿主没有应答者时审批是 fail-closed,不会变成无人自动放行。填 'deny' 可单独关掉某一档、退回旧行为。**不适用于 L0 的 deny 类硬规则**(rm -rf / / mkfs / git push --force):两种模式都拦死,也不参与 retryLimit 升级 —— 令牌与审批都不得越过 L0。",
|
|
19
|
+
"reviseInAskMode": "ask",
|
|
20
|
+
"blockInAskMode": "ask",
|
|
21
|
+
|
|
22
|
+
"_tokens": "一次性放行令牌:被拦的命令会在理由里附一个 ALLOW-XXXXXXXXXX,以及一行**绝对路径、命令原文不截断、引号已转义**的授权命令(整行复制进普通终端即可,普通终端里没有阀门)。令牌绑定命令哈希(空白差异不影响)、用掉即删、无法重放,且**不越过 L0 的永不允许规则**。授权入口要求交互终端 —— agent 自己跑会被拒。留空 = ~/.jev-guard/allow.txt",
|
|
23
|
+
"tokens": true,
|
|
24
|
+
"tokenPath": "",
|
|
25
|
+
|
|
26
|
+
"_timing": "单次判定预算;超时或报错一律放行(fail-open),因为宿主自己的沙箱与审批策略仍在执行之前。",
|
|
27
|
+
"timeoutMs": 1800,
|
|
28
|
+
"cacheSize": 256,
|
|
29
|
+
|
|
30
|
+
"_enrich": "把被调用脚本的正文读进判定状态,补掉 `node x.mjs` 这类盲区(实测 0.31 -> 0.82)。敏感路径(.env/.ssh/*.pem/*credential*/*secret*)自动跳过不上传;想彻底关闭就设 false。",
|
|
31
|
+
"inlineScripts": true,
|
|
32
|
+
"maxScriptBytes": 8192,
|
|
33
|
+
|
|
34
|
+
"_retry": "同一条(指纹相同)命令被拦多少次后升级为 escalate(交人处理),避免模型换写法无限重试。",
|
|
35
|
+
"retryLimit": 2,
|
|
36
|
+
|
|
37
|
+
"_log": "共享审计日志:每个判定追加一行 JSONL,DSH 插件与 CLI 写同一个文件。留空 = 默认 ~/.jev-guard/guard.log;超过 logMaxBytes 轮转到 guard.log.1;命令文本写入前会做密钥掩码。查看: node bin/guard.mjs log --tail 20 / --stats",
|
|
38
|
+
"logPath": "",
|
|
39
|
+
"logMaxBytes": 4194304,
|
|
40
|
+
|
|
41
|
+
"_quota": "降级有两种,别混为一谈(2026-09-20,D15)。① 服务侧状况(quota / auth)= 冷却式:写一份 degraded.json,窗口内不再发请求(省钱),到期放一次探测,成功即自动恢复。② 本地配置状况(无密钥)= **粘性**:没密钥时一次 HTTP 都不发、没有可探测对象,所以它不靠时间结束,而是靠\"密钥出现了\"结束(读到密钥即当场清除,零请求)。两类状态都记 scope:服务侧是 'global'(影响所有入口),本地类是写下它的那条入口身份(CLI 是 'cli',DSH 适配器是 'dsh-adapter')—— 于是某条入口读不到密钥不会把密钥正常的其它入口一起按停。瞬态失败(超时/网络/5xx/429限流)只逐次 fail-open,不降级但会分类记录。查状态: node bin/guard.mjs status(退出码 3 = 正在降级)。",
|
|
42
|
+
"quotaCooldownMs": 900000,
|
|
43
|
+
"authCooldownMs": 1800000,
|
|
44
|
+
"_notifyInSession": "把\"没有密钥,请录入\"与\"阀门已降级\"这两件事**在会话里说出来**:纯 host 插件没有 toast/banner 接口,所以往会话注入一条 notice 消息(它在对话里是一行、写进会话历史、并进入模型上下文)。每个会话每种状态只说一次,重启/恢复后不会重复。设 false 只保留 logger 与审计日志。",
|
|
45
|
+
"notifyInSession": true,
|
|
46
|
+
"_degradePolicy": "降级期间保留哪一层:'l0-only'(默认)= 只停要花钱联网的语义层,免费的 L0 硬规则 + 预筛照常工作(它们正好覆盖 mkfs / dd of=/dev/* / git push --force 这类最坏情况);'off' = 连 L0 一起停(整条阀门暂停,全部放行)。",
|
|
47
|
+
"degradePolicy": "l0-only",
|
|
48
|
+
"_degradedPath": "降级状态文件;留空 = ~/.jev-guard/degraded.json",
|
|
49
|
+
"degradedPath": "",
|
|
50
|
+
"_cost": "成本可见性:判定响应里的 usage 会记进审计日志,`guard log --stats` 折算成美元(单价为美元/百万输入 token;输出按官方说明免费)。",
|
|
51
|
+
"pricePerMTok": 0.042
|
|
52
|
+
}
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# dsh-jev-guard bundle patch layer.
|
|
2
|
+
#
|
|
3
|
+
# `dsh plugin --profile web add <path>` 会读这个文件,把本条 loader entry 插进 profile 树。
|
|
4
|
+
# 注意两件事:
|
|
5
|
+
# · name 必须与 package.json 的 name 完全一致(dsh-jev-guard),写成别的会导致开机解析失败;
|
|
6
|
+
# · config 里所有字段都有默认值(lib/gate.js 的 DEFAULTS),这里的值会覆盖
|
|
7
|
+
# config.json(三级优先级:patch > config.json > 内置默认)。
|
|
8
|
+
- insert:
|
|
9
|
+
- id: dsh-jev-guard
|
|
10
|
+
name: 'dsh-jev-guard'
|
|
11
|
+
config:
|
|
12
|
+
# 要拦的工具。bash 是主力;pwsh 若未挂载则无影响。
|
|
13
|
+
tools:
|
|
14
|
+
- bash
|
|
15
|
+
- pwsh
|
|
16
|
+
|
|
17
|
+
# ── 语言(2026-09-20,见 docs/DECISIONS.md D14)────────────────────────────
|
|
18
|
+
# lang = **给人或模型看的话**用什么语言:判定理由、L0 规则理由、CLI 输出、降级告警。
|
|
19
|
+
# 默认 'auto':按 JEV_GUARD_LANG → LC_ALL/LC_MESSAGES/LANG(仅当它们指明受支持的语言);
|
|
20
|
+
# 都不指明就用 zh-CN。刻意不看系统 locale:LANG=C.UTF-8 下 Node 的 Intl 会报 en-US,
|
|
21
|
+
# 那会让中文会话的理由悄悄变英文(2026-09-20 实测踩过)。只换文案,不动判定路径。
|
|
22
|
+
#
|
|
23
|
+
# 这两项**故意留空**(不像阈值那样在这里写死):语言是"这台机器/这个人"的偏好,
|
|
24
|
+
# 而本文件的优先级高于 config.json —— 写在这里就等于让 `config.json` 里的语言设置失效。
|
|
25
|
+
# 想换语言,任选一种:
|
|
26
|
+
# · config.json 里写 "lang": "en"(或 "zh-CN" 固定中文);
|
|
27
|
+
# · 给 DSH 进程设 JEV_GUARD_LANG=en;
|
|
28
|
+
# · CLI 单次用 --lang en。
|
|
29
|
+
# lang: auto
|
|
30
|
+
|
|
31
|
+
# promptLang = **发给判定服务的那句问话**与 state 的键用什么语言,默认 'zh-CN'。
|
|
32
|
+
# 它不是界面语言的翻译项,而是一个判定参数:阈值 0.5/0.7 是在中文问话上标定的
|
|
33
|
+
# (114 例)。实测换英文问话后:21 条探针 18 条同带、12 条 p 更低(平均 -0.04),
|
|
34
|
+
# 三条翻带且方向全部朝放行(见 docs/MEASUREMENTS.md §14)。
|
|
35
|
+
# 要用英文问话,先重标定,或把两个阈值下调约 0.04。
|
|
36
|
+
# promptLang: zh-CN
|
|
37
|
+
|
|
38
|
+
# 四态的两个阈值:
|
|
39
|
+
# p < lowThreshold → allow
|
|
40
|
+
# lowThreshold ≤ p < high → revise(中间带:证据不足)
|
|
41
|
+
# p ≥ highThreshold → block
|
|
42
|
+
# 实测依据:737 条真实命令上 0.5/0.7 = 98.51% / 0.81% / 0.68% 三分。
|
|
43
|
+
lowThreshold: 0.5
|
|
44
|
+
highThreshold: 0.7
|
|
45
|
+
|
|
46
|
+
# ── 判定动作怎么随宿主审批模式分叉(2026-09-20 用户决定,见 docs/DECISIONS.md D13)──
|
|
47
|
+
# 背景:以前只有 escalate 会看审批策略,revise / block 一律直接拒绝。实测代价是
|
|
48
|
+
# 一天里 9 次 revise 拒绝发生在 approval=ask 模式下 —— 人就在场,却拿不到弹窗,
|
|
49
|
+
# 一个 50% 的判断替人做了决定。
|
|
50
|
+
# 'ask' (默认)= 审批策略为 ask 时转人工弹窗(灰区/高分都交给人);
|
|
51
|
+
# 'deny' = 维持旧行为,直接拒绝。
|
|
52
|
+
# 审批策略为 never(全自动,没人可问)时两者都仍然是直接拒绝。
|
|
53
|
+
# **不适用于 L0 的 deny 类硬规则**:rm -rf / / mkfs / git push --force 这类零误报的
|
|
54
|
+
# 不可逆操作,两种模式都拦死,也不参与 retryLimit 的升级(令牌与审批都不得越过 L0)。
|
|
55
|
+
reviseInAskMode: ask
|
|
56
|
+
blockInAskMode: ask
|
|
57
|
+
|
|
58
|
+
# 单次判定预算;超时或报错一律放行(fail-open)——
|
|
59
|
+
# 宿主自己的沙箱与审批策略仍在执行之前,阀门只是增量检查。
|
|
60
|
+
timeoutMs: 1800
|
|
61
|
+
cacheSize: 256
|
|
62
|
+
|
|
63
|
+
# 同一条(指纹相同)命令被拦多少次后升级为 escalate(交人处理),
|
|
64
|
+
# 防止模型换写法无限重试。
|
|
65
|
+
retryLimit: 2
|
|
66
|
+
|
|
67
|
+
# 把被调用脚本的正文读进 state,补掉 `node x.mjs` 这类盲区
|
|
68
|
+
# (实测 0.31 → 0.82)。敏感路径(.env/.ssh/*.pem/*credential*/*secret*/*token*)
|
|
69
|
+
# 自动跳过,单文件上限 maxScriptBytes。
|
|
70
|
+
inlineScripts: true
|
|
71
|
+
maxScriptBytes: 8192
|
|
72
|
+
|
|
73
|
+
# 密钥解析:优先 ctx.credentials(即 ~/.dsh/.credentials.yaml 的 refs),
|
|
74
|
+
# 其次进程环境变量,最后包根 secrets.json(相对路径的 apiKeyFile 按**包根**解析)。
|
|
75
|
+
# 第三层就是 `node bin/guard.mjs key set` 写的那一份 —— 缺了它,"用 CLI 录入密钥"
|
|
76
|
+
# 对 DSH 用户就是一句空话。三处永不打印密钥。
|
|
77
|
+
apiKeyEnv: TYPESAFE_API_KEY
|
|
78
|
+
model: jev-latest
|
|
79
|
+
|
|
80
|
+
# ── 会话内提示(2026-09-20,见 docs/DECISIONS.md D15)─────────────────────────
|
|
81
|
+
# 纯 host 插件没有任何 toast / banner / 启动提示接口(DSH 的设置页都由浏览器侧注册占位),
|
|
82
|
+
# 所以"没有密钥,请录入"与"阀门已降级"这两件事靠**往会话里注入一条 notice 消息**说出来:
|
|
83
|
+
# 它在对话里是一行、写进会话历史、并进入模型上下文(于是模型也知道阀门现在是瞎的)。
|
|
84
|
+
# 每个会话每种状态只说一次(去重靠会话历史,重启/恢复后也不会重复)。
|
|
85
|
+
# 设 false = 只保留 logger 与审计日志(不打扰对话)。
|
|
86
|
+
notifyInSession: true
|
|
87
|
+
|
|
88
|
+
# 一次性放行令牌:被拦的命令会在理由里得到 ALLOW-XXXXXXXXXX;
|
|
89
|
+
# 用户在终端跑 `node bin/guard.mjs allow '<原命令>'` 后,重试同一条命令放行一次。
|
|
90
|
+
# 令牌绑定命令哈希、用掉即删、不越过 L0 硬规则。留空 = ~/.jev-guard/allow.txt
|
|
91
|
+
tokens: true
|
|
92
|
+
tokenPath: ''
|
|
93
|
+
|
|
94
|
+
# 共享审计日志:每个判定追加一行 JSONL。DSH 的 logger 会过滤插件的 info 级
|
|
95
|
+
# 日志(dsh-web.log 里看不到 jev-guard),所以运行期要靠这个文件回答"它判了什么"。
|
|
96
|
+
# 留空 = 默认 ~/.jev-guard/guard.log;查看:node bin/guard.mjs log --tail 20
|
|
97
|
+
logPath: ''
|
|
98
|
+
logMaxBytes: 4194304
|
|
99
|
+
|
|
100
|
+
# ── 额度耗尽后的降级(见 lib/quota.js 与 docs/DECISIONS.md D9)───────────────
|
|
101
|
+
# 判定服务是收费的,额度用完是必然事件。旧行为是"静默 fail-open":命令照跑、
|
|
102
|
+
# 日志一堆 error,但没人看得出阀门已经不在防护了。现在:
|
|
103
|
+
# · 持久性失败(quota / auth / no-key)写一份 ~/.jev-guard/degraded.json,
|
|
104
|
+
# 在冷却窗口内**不再发请求**(省钱),窗口到期放**一次**探测请求:
|
|
105
|
+
# 成功 → 自动恢复(人不用做任何事);失败 → 继续降级。
|
|
106
|
+
# · 瞬态失败(超时/网络/5xx/429限流)只逐次放行,不降级,但会被分类记录。
|
|
107
|
+
# 查状态:`node bin/guard.mjs status`(退出码 3 = 正在降级,供脚本/宿主做健康检查)。
|
|
108
|
+
quotaCooldownMs: 900000 # 额度类:15 分钟
|
|
109
|
+
authCooldownMs: 1800000 # 密钥类:30 分钟
|
|
110
|
+
|
|
111
|
+
# 降级期间保留哪一层:
|
|
112
|
+
# 'l0-only'(默认)= 只停**要花钱联网**的语义层,免费的 L0 硬规则 + 预筛照常工作。
|
|
113
|
+
# 这不是自作主张:那一层不花钱、不联网,正好覆盖最坏的一类
|
|
114
|
+
# (mkfs / dd of=/dev/* / git push --force)。
|
|
115
|
+
# 'off' = 连 L0 一起停(整条阀门暂停,全部放行)—— 想"额度没了就完全不插手"用这个。
|
|
116
|
+
degradePolicy: l0-only
|
|
117
|
+
|
|
118
|
+
# 成本可见性:判定响应里的 usage 会被记进审计日志,
|
|
119
|
+
# `guard log --stats` 会折算成美元(单价,美元/百万输入 token;输出按官方说明免费)。
|
|
120
|
+
pricePerMTok: 0.042
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Task brief: DSH
|
|
2
|
+
|
|
3
|
+
> **English** | [简体中文](AGENT-TASK-dsh.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
> **This package now supports DSH only** (narrowed on 2026-09-20, see [DECISIONS D11](./DECISIONS.md)).
|
|
6
|
+
> The acceptance checklist and the number cross-reference table are in [`VERIFICATION.md`](./VERIFICATION.md); the mechanics are in [`DSH-INTEGRATION.md`](./DSH-INTEGRATION.md).
|
|
7
|
+
> When you get a new machine, follow [`../DEPLOY.md`](../DEPLOY.md), and record the conclusion per item as `VERIFICATION.md` says.
|
|
8
|
+
|
|
9
|
+
DSH is the **only** supported host, and it needs no gamble: there is a native `tools/pre-execute`
|
|
10
|
+
interception point, and the behaviour has already been confirmed from the source (no more guessing).
|
|
11
|
+
|
|
12
|
+
## Already verified before the restart (2026-09-20, measured on this machine)
|
|
13
|
+
|
|
14
|
+
| Check | Command | Result |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Adapter wiring (fake ctx) | `node tools/smoke-dsh-adapter.mjs` | **9/9 passed** (including "under full permissions the denial reason clearly states it is not a user rejection", "the audit recorded `policy` and `preset`", "fail-open when there is no key") |
|
|
17
|
+
| **Real tool pipeline integration** | see the note below | **6/6 passed**: `git push --force` and `rm -rf` on a real directory were blocked, **the tool body did not execute**, read-only commands executed normally |
|
|
18
|
+
| Composition tree | `dsh --profile web --dump-config` | contains `dsh-jev-guard` → `name: jev-guard`, no parse errors |
|
|
19
|
+
| Install | `dsh plugin --profile web add /mnt/t/dsh-jev-guard` | depends on `link:/mnt/t/dsh-jev-guard`; bundles 25 → 26; no critical plugin missing |
|
|
20
|
+
| Credential | `refs.TYPESAFE_API_KEY` in `~/.dsh/.credentials.yaml` | written and validated as legal YAML (backup `.bak-before-typesafe-*`) |
|
|
21
|
+
| Rollback point | manual snapshot `20260920-124708-6f5f` | the clean state before the install |
|
|
22
|
+
|
|
23
|
+
How to run the real-pipeline integration test (you must be inside the deepseek-harness directory tree, otherwise `@deepseek-ai/*` will not resolve):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cp /mnt/t/dsh-jev-guard/tools/smoke-dsh-pipeline.mjs <DSH checkout>/packages/core/agent-loop/.tmp-guard-pipeline.mjs
|
|
27
|
+
cd <DSH checkout>/packages/core/agent-loop
|
|
28
|
+
JEV_GUARD_ROOT=/mnt/t/dsh-jev-guard TYPESAFE_API_KEY=... node .tmp-guard-pipeline.mjs
|
|
29
|
+
rm .tmp-guard-pipeline.mjs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Only one thing is left: run the item 6 probe after the restart.**
|
|
33
|
+
|
|
34
|
+
## Known behaviour (confirmed at the source level, no need to re-verify)
|
|
35
|
+
|
|
36
|
+
| Session approval policy | What the valve returns | Actual effect |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `ask` (workspace-write preset) | `{kind:'ask'}` | raises an approval prompt; the reason carries the Jev verdict |
|
|
39
|
+
| `never` (**full permissions / danger-full-access**) | `{kind:'deny'}` | **denies outright** |
|
|
40
|
+
|
|
41
|
+
Reason: `approval: 'never'` is defined as *never prompt anyone: every ask resolves `rejected`* —
|
|
42
|
+
so under full permissions, if `ask` were returned, it would be silently turned into a denial, and the reason
|
|
43
|
+
would wrongly say "the user rejected". This package's adapter therefore reads the session policy, and under
|
|
44
|
+
`never` it **denies directly and gives an accurate reason**.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# 1) the key: at runtime DSH reads its own credential store first
|
|
50
|
+
# add TYPESAFE_API_KEY to refs in ~/.dsh/.credentials.yaml (or have it in the DSH process environment)
|
|
51
|
+
# 2) install the plugin
|
|
52
|
+
dsh plugin --profile web add /mnt/t/dsh-jev-guard
|
|
53
|
+
# 3) restart DSH
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Item 6: is the probe blocked after the install
|
|
57
|
+
|
|
58
|
+
After the restart, run this in a DSH session:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
echo "" > ~/jev-guard-probe.txt # overwrite/truncate a file; should be blocked or raise a confirmation
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Judging:**
|
|
65
|
+
|
|
66
|
+
- the session approval policy is `ask`: an approval prompt appears and its reason contains "Jev 判定风险概率" → pass
|
|
67
|
+
- the session approval policy is `never` (full permissions): the command **is denied**, and the model receives an explanation that "这不是用户手动拒绝" → pass
|
|
68
|
+
- the command executed normally → check three things: ① whether `jev-guard` is in `dsh --dump-config`;
|
|
69
|
+
② whether the key is reachable (the log will have `jev-guard: fail-open after ...`, which says the judging failed);
|
|
70
|
+
③ whether `bash` is included in the `tools` config.
|
|
71
|
+
|
|
72
|
+
By the way, test one more safe command to confirm nothing was hit by mistake:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
ls -la ~ | head -3
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Report back:**
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
node /mnt/t/dsh-jev-guard/tools/report-result.mjs --host dsh --item 6 --status pass \
|
|
82
|
+
--evidence "in full-permission mode echo \"\" > file was denied, the reason contained '本会话是无审批提示的模式'; ls -la was allowed normally"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Runtime observability (implemented 2026-09-20)
|
|
86
|
+
|
|
87
|
+
DSH's logger threshold filters the plugin's `info`-level logs — there is not a single `jev-guard`
|
|
88
|
+
line in `dsh-web.log`. So the valve writes its own **shared audit log**:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
node /mnt/t/dsh-jev-guard/bin/guard.mjs log --tail 20 # the most recent 20 verdicts
|
|
92
|
+
node /mnt/t/dsh-jev-guard/bin/guard.mjs log --stats # last-24-hours summary (by action/source/rule)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- File: `~/.jev-guard/guard.log` (JSONL; rotates to `guard.log.1` past 4 MiB)
|
|
96
|
+
- Each line: time, action, source, p, rule hit, elapsed time, which state fields were filled in, and the command (**keys already masked** before writing)
|
|
97
|
+
- **The DSH plugin and the CLI write the same file**, so "what did it judge today" needs only one place
|
|
98
|
+
|
|
99
|
+
**While running the probe, take one look**: the blocked entry should appear in `--tail`, with
|
|
100
|
+
`action` being `block` or `escalate`. If there is not a single record, that itself is diagnostic
|
|
101
|
+
information: the plugin did not run, or the log path is not writable.
|
|
102
|
+
(`flush()` in `lib/audit.js` is already hooked onto the plugin's `dispose`, so the trailing records are not lost on exit.)
|
|
103
|
+
|
|
104
|
+
## The way out after a block: the one-shot allow token
|
|
105
|
+
|
|
106
|
+
A hard block is not a dead end. The reason for a blocked command carries `ALLOW-XXXXXXXXXX`, and a human can run this in a terminal:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
node /mnt/t/dsh-jev-guard/bin/guard.mjs allow '<the original text of that command>' # write the token
|
|
110
|
+
node /mnt/t/dsh-jev-guard/bin/guard.mjs allow --list # see the pending tokens
|
|
111
|
+
node /mnt/t/dsh-jev-guard/bin/guard.mjs allow --revoke ALLOW-… # revoke
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Then **retry the same command** (whitespace differences do not affect matching) and it is allowed once, with the
|
|
115
|
+
token deleted at the same time. Two things to note:
|
|
116
|
+
① **L0's "never allowed" rules are not affected by the token** (writing to disk, formatting, dropping a database and the like can only be done by hand);
|
|
117
|
+
② an allow by token leaves a `source: token` record in `guard.log`, auditable afterwards.
|
|
118
|
+
|
|
119
|
+
## Rollback
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
dsh plugin --profile web remove jev-guard # then restart
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
DSH's `dsh-undo-savepoint` saves a snapshot automatically on config changes, and its `undo_restore` can roll back in one command.
|
|
126
|
+
Note: the install changes two places, the profile's `dependencies` and `dsh.profile.bundles`.
|
|
127
|
+
|
|
128
|
+
## Two optional things after the install (outside this brief's scope, raised here anyway)
|
|
129
|
+
|
|
130
|
+
1. **Bring `write` / `run_code` into the valve too**: change the `tools` list in `cordis.patch.yml` to
|
|
131
|
+
`[bash, pwsh, write, edit]` — but note that the judging problem for non-shell tools needs to be redesigned (the question is currently aimed at commands).
|
|
132
|
+
2. **Wire the L0 hard rules into `ctx.tools.guard()`**: DSH has a monotonic guard that "can only deny, and cannot be
|
|
133
|
+
overturned by later listeners" (the documentation's own words: *may deny or abstain, **never force-allow***); moving those 21 "never allowed" entries over
|
|
134
|
+
would give a layer harder than the pre-execute return value.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# 任务简报:DSH
|
|
2
|
+
|
|
3
|
+
> [English](AGENT-TASK-dsh.md) | **简体中文**
|
|
4
|
+
|
|
5
|
+
> **本包现在只支持 DSH**(2026-09-20 收窄,见 [DECISIONS D11](./DECISIONS.md))。
|
|
6
|
+
> 验收清单与编号对照表在 [`VERIFICATION.md`](./VERIFICATION.md);机制在 [`DSH-INTEGRATION.md`](./DSH-INTEGRATION.md)。
|
|
7
|
+
> 拿到一台新机器时照 [`../DEPLOY.md`](../DEPLOY.md) 走,并按 `VERIFICATION.md` 逐项记录结论。
|
|
8
|
+
|
|
9
|
+
DSH 是**唯一**被支持的宿主,而且它不需要赌:有原生的 `tools/pre-execute` 拦截位,
|
|
10
|
+
行为已从源码确认(不再需要猜测)。
|
|
11
|
+
|
|
12
|
+
## 重启前已经验证过的(2026-09-20,本机实测)
|
|
13
|
+
|
|
14
|
+
| 检查 | 命令 | 结果 |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| 适配器接线(假 ctx) | `node tools/smoke-dsh-adapter.mjs` | **9/9 通过**(含"完全权限下拒绝理由明确不是用户拒绝"、"审计里记下了 `policy` 与 `preset`"、"无密钥时 fail-open") |
|
|
17
|
+
| **真实工具管线集成** | 见下方说明 | **6/6 通过**:`git push --force` 与真实目录 `rm -rf` 被拦、**工具体未执行**、只读命令正常执行 |
|
|
18
|
+
| 组合树 | `dsh --profile web --dump-config` | 含 `dsh-jev-guard` → `name: jev-guard`,无解析错误 |
|
|
19
|
+
| 安装 | `dsh plugin --profile web add /mnt/t/dsh-jev-guard` | 依赖 `link:/mnt/t/dsh-jev-guard`;bundles 25 → 26;关键插件无缺失 |
|
|
20
|
+
| 凭据 | `~/.dsh/.credentials.yaml` 的 `refs.TYPESAFE_API_KEY` | 已写入并校验 YAML 合法(备份 `.bak-before-typesafe-*`) |
|
|
21
|
+
| 回退点 | 手动快照 `20260920-124708-6f5f` | 安装前的干净状态 |
|
|
22
|
+
|
|
23
|
+
跑真实管线集成测试的方法(必须在 deepseek-harness 目录树内,否则解析不到 `@deepseek-ai/*`):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cp /mnt/t/dsh-jev-guard/tools/smoke-dsh-pipeline.mjs <DSH 检出>/packages/core/agent-loop/.tmp-guard-pipeline.mjs
|
|
27
|
+
cd <DSH 检出>/packages/core/agent-loop
|
|
28
|
+
JEV_GUARD_ROOT=/mnt/t/dsh-jev-guard TYPESAFE_API_KEY=... node .tmp-guard-pipeline.mjs
|
|
29
|
+
rm .tmp-guard-pipeline.mjs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**剩下只有一件事:重启后跑第 6 项 probe。**
|
|
33
|
+
|
|
34
|
+
## 已知行为(源码级确认,不必重新验证)
|
|
35
|
+
|
|
36
|
+
| 会话审批策略 | 阀门返回 | 实际效果 |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `ask`(workspace-write 预设) | `{kind:'ask'}` | 弹审批;理由里带 Jev 判定 |
|
|
39
|
+
| `never`(**完全权限 / danger-full-access**) | `{kind:'deny'}` | **直接拒绝** |
|
|
40
|
+
|
|
41
|
+
原因:`approval: 'never'` 的定义是 *never prompt anyone: every ask resolves `rejected`* ——
|
|
42
|
+
所以完全权限下若返回 `ask`,会被静默转成拒绝,并且理由错说成"用户拒绝了"。本包的适配器
|
|
43
|
+
因此读取会话策略,在 `never` 下**直接 deny 并给出准确理由**。
|
|
44
|
+
|
|
45
|
+
## 安装
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# 1) 密钥:DSH 运行时优先读自己的凭据库
|
|
49
|
+
# 在 ~/.dsh/.credentials.yaml 的 refs 里加 TYPESAFE_API_KEY(或让 DSH 进程环境里有它)
|
|
50
|
+
# 2) 装插件
|
|
51
|
+
dsh plugin --profile web add /mnt/t/dsh-jev-guard
|
|
52
|
+
# 3) 重启 DSH
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 第 6 项:安装后 probe 是否被拦
|
|
56
|
+
|
|
57
|
+
重启后,在 DSH 会话里执行:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
echo "" > ~/jev-guard-probe.txt # 覆盖/截断一个文件,应当被拦或弹确认
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**判定:**
|
|
64
|
+
|
|
65
|
+
- 会话审批策略是 `ask`:出现审批提示,理由里含"Jev 判定风险概率" → 通过
|
|
66
|
+
- 会话审批策略是 `never`(完全权限):命令**被拒**,模型收到"这不是用户手动拒绝"的说明 → 通过
|
|
67
|
+
- 命令照常执行了 → 检查三件事:① `dsh --dump-config` 里有没有 `jev-guard`;
|
|
68
|
+
② 密钥是否可达(日志里会有 `jev-guard: fail-open after ...` 说明是判定失败);
|
|
69
|
+
③ `tools` 配置里是否包含 `bash`。
|
|
70
|
+
|
|
71
|
+
顺手再测一条安全命令确认没被误伤:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
ls -la ~ | head -3
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**回传:**
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
node /mnt/t/dsh-jev-guard/tools/report-result.mjs --host dsh --item 6 --status pass \
|
|
81
|
+
--evidence "完全权限模式下 echo \"\" > file 被拒,理由含'本会话是无审批提示的模式';ls -la 正常放行"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 运行期可观测性(2026-09-20 已实现)
|
|
85
|
+
|
|
86
|
+
DSH 的 logger 阈值会过滤插件的 `info` 级日志 —— `dsh-web.log` 里一条 `jev-guard` 都没有。
|
|
87
|
+
所以阀门自己写一个**共享审计日志**:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
node /mnt/t/dsh-jev-guard/bin/guard.mjs log --tail 20 # 最近 20 条判定
|
|
91
|
+
node /mnt/t/dsh-jev-guard/bin/guard.mjs log --stats # 近 24 小时汇总(按动作/来源/规则)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- 文件:`~/.jev-guard/guard.log`(JSONL;超过 4 MiB 轮转到 `guard.log.1`)
|
|
95
|
+
- 每行:时间、动作、来源、p、命中规则、耗时、补齐了哪些 state、命令(写入前**已掩码密钥**)
|
|
96
|
+
- **DSH 插件与 CLI 写同一个文件**,所以"今天它判了什么"只需看一处
|
|
97
|
+
|
|
98
|
+
**跑 probe 时顺手看一眼**:被拦的那条应出现在 `--tail` 里,`action` 是 `block` 或 `escalate`。
|
|
99
|
+
如果一个记录都没有,那本身就是诊断信息:插件没跑,或日志路径不可写。
|
|
100
|
+
(`lib/audit.js` 的 `flush()` 已接在插件的 `dispose` 上,退出时不会丢尾部记录。)
|
|
101
|
+
|
|
102
|
+
## 被拦之后的出口:一次性放行令牌
|
|
103
|
+
|
|
104
|
+
硬拦不是死路。被拦命令的理由里带 `ALLOW-XXXXXXXXXX`,人员可以在终端执行:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
node /mnt/t/dsh-jev-guard/bin/guard.mjs allow '<那条命令的原文>' # 写入令牌
|
|
108
|
+
node /mnt/t/dsh-jev-guard/bin/guard.mjs allow --list # 看待用令牌
|
|
109
|
+
node /mnt/t/dsh-jev-guard/bin/guard.mjs allow --revoke ALLOW-… # 撤销
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
然后**重试同一条命令**(空白差异不影响匹配)即放行一次,令牌同时被删除。两点注意:
|
|
113
|
+
① **L0 的"永不允许"规则不受令牌影响**(写盘、格式化、删库这类只能人工手动执行);
|
|
114
|
+
② 凭令牌放行会在 `guard.log` 留下 `source: token` 记录,事后可审计。
|
|
115
|
+
|
|
116
|
+
## 回滚
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
dsh plugin --profile web remove jev-guard # 然后重启
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
DSH 的 `dsh-undo-savepoint` 会在配置变更时自动存快照,也可以用它的 `undo_restore` 一键回退。
|
|
123
|
+
注意:安装会改 profile 的 `dependencies` 与 `dsh.profile.bundles` 两处。
|
|
124
|
+
|
|
125
|
+
## 装完之后可选的两件事(不在本简报范围内,先提出来)
|
|
126
|
+
|
|
127
|
+
1. **把 `write` / `run_code` 也纳入阀门**:改 `cordis.patch.yml` 的 `tools` 列表为
|
|
128
|
+
`[bash, pwsh, write, edit]` —— 但注意非 shell 工具的判定问题需要重新设计(问句目前是针对命令的)。
|
|
129
|
+
2. **L0 硬规则接 `ctx.tools.guard()`**:DSH 有个"只能拒绝、不能被后续监听器翻案"的单调守卫
|
|
130
|
+
(文档原话:*may deny or abstain, **never force-allow***),把"永不允许"那 21 条搬进去,
|
|
131
|
+
会得到一个比 pre-execute 返回值更硬的层。
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Architecture: why four layers, and why judging must be separate from interception
|
|
2
|
+
|
|
3
|
+
> **English** | [简体中文](ARCHITECTURE.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
## In one sentence
|
|
6
|
+
|
|
7
|
+
**What is portable is the judgment, not the power.** Judging can be made a caller-agnostic function (which is what this package does);
|
|
8
|
+
but **enforcement interception must land on DSH's own pre-execution interception point**. Only by separating these two things does the approach hold up —
|
|
9
|
+
and it is also the reason it can run the same judging on both Windows and WSL.
|
|
10
|
+
|
|
11
|
+
## The four layers
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
┌─────────────────────── jev-guard ────────────────────────┐
|
|
15
|
+
│ L0 static hard rules offline · cannot be overridden │
|
|
16
|
+
DSH ──native plugin──▶│ L1 Jev semantic judgment 300ms · the four states · cache│ ──▶ TypeSafe API
|
|
17
|
+
│ L2 pre-execution snapshot (planned) │
|
|
18
|
+
(DSH only) ─────▶ │ Three surfaces: CLI · library · offline self-check │
|
|
19
|
+
└──────────────────────────────────────────────────────────┘
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### L0 — static hard rules (offline)
|
|
23
|
+
|
|
24
|
+
**Responsibility:** the two lists "never allowed" and "must be confirmed by a human". `lib/rules.js`, 21 deny + 16 ask.
|
|
25
|
+
|
|
26
|
+
**Why it is a separate layer:** it must not depend on the network, must not depend on a model, and must not be overridable
|
|
27
|
+
by anything downstream. In DSH the counterpart is
|
|
28
|
+
`ctx.tools.guard()` — the documentation's own words: may deny or abstain, **never force-allow**.
|
|
29
|
+
Implementing hard rules with a semantic model amounts to building safety on one network request.
|
|
30
|
+
|
|
31
|
+
### L1 — Jev semantic judgment
|
|
32
|
+
|
|
33
|
+
**Responsibility:** grey-zone judgment. "Will this command/script irreversibly delete or overwrite real data?"
|
|
34
|
+
|
|
35
|
+
**Why it is this one question:** across 114 measured cases, the same question scored **12/12** in Chinese; while in the
|
|
36
|
+
same batch "what should be done" (a semantically adjacent three-way choice) scored only 75%, and "risk level" only 50%. **Yes/no questions are accurate, degree questions are not** —
|
|
37
|
+
so the level is composed by code, not asked of the model.
|
|
38
|
+
|
|
39
|
+
**The four states rather than three:** "let the model try again" (a seconds-long closed loop) and "wait for a human" (possibly hours) are two different control flows.
|
|
40
|
+
Mixed together, they either drag a person into small matters, or let the model retry without end in the same pit with a different phrasing. `revise` must carry three deterministic
|
|
41
|
+
degradation templates (read-only equivalent / narrower scope / back up first), because it cannot give a command — it produces no text.
|
|
42
|
+
|
|
43
|
+
### L2 — pre-execution snapshot (planned)
|
|
44
|
+
|
|
45
|
+
**Responsibility:** judging can never be 100% accurate; this layer catches the misjudgment. **Do only the git provider first** (highest coverage, lowest cost):
|
|
46
|
+
snapshot only when it can be enumerated and the size is within budget; when it cannot be enumerated or is over budget, block outright. The measured constraints on this machine are in `docs/MEASUREMENTS.md` §5.
|
|
47
|
+
|
|
48
|
+
### L3 — the human exit
|
|
49
|
+
|
|
50
|
+
It is not in this pipeline, but it is part of the design: the **one-shot token** (lets one command through once),
|
|
51
|
+
the **DSH approval prompt** (`approval: ask`), and **a human running it by hand**. The properties of the three channels and the measured evidence for each are in
|
|
52
|
+
[`USER-INTERVENTION.md`](./USER-INTERVENTION.md).
|
|
53
|
+
|
|
54
|
+
## Data flow (one judgment)
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
command text + cwd
|
|
58
|
+
│
|
|
59
|
+
├─ L0 hit? ──yes──▶ block / escalate (end, zero network)
|
|
60
|
+
│
|
|
61
|
+
├─ deterministic pre-screen hit? ──yes──▶ allow (end, zero network)
|
|
62
|
+
│ (read-only commands / rm on rebuildable paths; compound commands judged segment by segment)
|
|
63
|
+
│
|
|
64
|
+
├─ cache hit? ──yes──▶ return the previous verdict (still goes through the current policy mapping)
|
|
65
|
+
│
|
|
66
|
+
├─ fill in state:
|
|
67
|
+
│ · the body of the script the command invokes (`node x.mjs` → read x.mjs)
|
|
68
|
+
│ · the script body in package.json (`pnpm run deploy:prod` → read scripts)
|
|
69
|
+
│ · sensitive paths skipped, 8KB cap
|
|
70
|
+
│
|
|
71
|
+
└─ one Jev question ──▶ p
|
|
72
|
+
p < low → allow
|
|
73
|
+
low ≤ p < high → revise (with templates)
|
|
74
|
+
p ≥ high → block
|
|
75
|
+
timeout/error → allow (fail-open)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Host mapping (the same verdict lands differently on different hosts)
|
|
79
|
+
|
|
80
|
+
| verdict | DSH (approval policy `ask`) | DSH (approval policy `never`, i.e. full permissions) |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| allow | run it | run it |
|
|
83
|
+
| revise | refuse + three degradation templates | refuse + one-shot token hint |
|
|
84
|
+
| block | refuse | refuse + one-shot token hint |
|
|
85
|
+
| escalate | **raises the approval prompt** (a human decides) | **refuse outright** + one-shot token hint |
|
|
86
|
+
|
|
87
|
+
> The last row is the easiest pit to fall into in this project: **under full-permissions mode `ask` is not "a prompt", it is "being rejected"**.
|
|
88
|
+
> In the DSH source the definition of `approval: 'never'` is exactly *never prompt anyone: every ask resolves rejected*.
|
|
89
|
+
> If we returned `ask` directly in such a session, the model would receive a **false statement** ("the user rejected tool bash");
|
|
90
|
+
> so we refuse ourselves, and write the real reason out clearly (which hard rule was hit, and that this is not a human refusing by hand).
|
|
91
|
+
|
|
92
|
+
## Why a "suggestion layer" cannot be a line of defence
|
|
93
|
+
|
|
94
|
+
Any approach that "hands the judging to the model to decide for itself whether to ask" (a prompt / an optional tool) is **not interception**:
|
|
95
|
+
|
|
96
|
+
- **A prompt** = the model may disobey it;
|
|
97
|
+
- **A judgment tool the model can call** = the model **decides for itself** whether to call it.
|
|
98
|
+
|
|
99
|
+
Neither can stop any other tool call. This package therefore takes only the enforcement route of the **pre-execution interception point**:
|
|
100
|
+
DSH's `tools/pre-execute`. That the judging itself is still made a caller-agnostic function is so that it can be **replayed offline**,
|
|
101
|
+
not so that the power is handed away.
|
|
102
|
+
|
|
103
|
+
## Known blind spots (accepted in the design, not bugs)
|
|
104
|
+
|
|
105
|
+
| blind spot | current state | mitigation |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| Infrastructure tooling | `terraform apply -auto-approve` measured at 0.48, below the threshold it is let through | lower the threshold for this class of command separately, or add an L0 ask rule |
|
|
108
|
+
| Paths only computed at runtime | the script decides from configuration which directory to delete; we can only guess from the source | filling in state can only supply the body text; this class falls under "cannot be enumerated → block" |
|
|
109
|
+
| Inline long code | a short `node -e` is visible (0.91); an over-long heredoc / concatenated variables are not | known; when necessary, treat heredoc as "script body" too |
|
|
110
|
+
| Non-shell tools | file-writing tools and `run_code` are not wired up yet | extend the `tools` list (a config change on the DSH side is enough) |
|
|
111
|
+
| Remote/cloud state | `git push --force`, `npm publish`, deleting cloud resources | already in L0; the rest relies on adding a parallel question "will this change remote state?" |
|
|
112
|
+
| Deliberate bypass | re-wrapping/encoding/absolute paths | **not defended** — this is an accident net, not a security boundary |
|
|
113
|
+
| The authorisation file being rewritten directly | an agent with file-writing tools can write `~/.jev-guard/allow.txt` directly (equivalent to issuing an authorisation to itself) | **deliberately kept, not sealed** — see [DECISIONS.md](./DECISIONS.md) **D1**; this is not a bug, it is a scope statement |
|
|
114
|
+
|
|
115
|
+
> The last two rows of the table are two ways of saying one thing: this valve guards against **accidents** (the model did not look carefully, the command was written out of habit, the state got mixed up),
|
|
116
|
+
> not an **adversary**. The user's own wording on this scope is in D1. **On seeing these two rows, do not "helpfully" seal them**:
|
|
117
|
+
> that would amount to requiring file-writing tools to be brought into the judging as well + file-level permissions, which is another product's worth of work,
|
|
118
|
+
> and the benefit would only target the "deliberate bypass" scenario that this project explicitly does not cover.
|