@klarkxy/dsh-safe-auto 0.2.1 → 0.2.2

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/README.md CHANGED
@@ -2,107 +2,39 @@
2
2
 
3
3
  <!-- release-state-note -->
4
4
 
5
- Automatic review of native sandbox escalation requests. When a real `write`/`edit`/`bash`/`pwsh` call asks to widen its sandbox beyond Workspace Write, an independent reviewer model vets that one call. A narrow set of metadata-only or literal-output `bash` commands can pass static checks without a model. A pass continues automatically; other review results offer an exact-call confirmation in the current session, with 60 seconds to approve once or reject. No response means rejection. The plugin never switches the standing session to Full Access.
5
+ [简体中文](README.zh-CN.md)
6
6
 
7
- [简体中文](README.zh-CN.md) · [One-shot escalation](docs/ADR-0002.md) · [Reviewer routing](docs/ADR-0003.md) · [Authorization and evidence](docs/ADR-0004.md) · [Timed human confirmation](docs/ADR-0005.md) · [Static shell proof](docs/ADR-0006.md)
7
+ Adds a **Safe Auto** permission level. When the agent asks to run a command or edit a file outside the sandbox, an independent reviewer model checks that one call. If it passes, the call goes ahead; otherwise you get 60 seconds to approve or reject it. Your session is never switched to Full Access.
8
8
 
9
- > **0.2.0 is a breaking simplification.** The 0.1.x exact-rule preflight, workspace/shell/escalation candidate lists, `shadow`/`smart`/`unattended` modes, the deep review stage and the HTTP endpoint were removed. See [Migrating from 0.1.x](#migrating-from-01x).
10
-
11
- ## Install and enable
9
+ ## Install
12
10
 
13
11
  ```sh
14
- dsh plugin --profile <profile> add @klarkxy/dsh-safe-auto
12
+ dsh plugin --profile web add @klarkxy/dsh-safe-auto
15
13
  ```
16
14
 
17
- The bundle inserts the `dsh-safe-auto` and `dsh-safe-auto-ui` profile rows. Safe Auto then appears as a fourth option in the composer's permission menu, next to the native presets — pick **Safe Auto** there to enable it for the current session, and pick any native preset to disable it again. Nothing is automatic until you do: no session inherits activation, and restart, a permission change or uninstall revokes it. The plugin page only holds reviewer configuration, safety notes and the read-only budget limits, grouped into tabs.
18
-
19
- > The composer permission control is a replacement slot: while Safe Auto is installed, the plugin renders that control itself (native presets included) so the option can appear in the menu. If the host redesigns this control, the plugin's copy may lag behind until updated.
20
-
21
- ## How it works
22
-
23
- 1. A native tool call asks for `sandbox_permissions` widening (`workspace-write` or `danger-full-access`).
24
- 2. The plugin binds the live execution — agent, callId, signal, exact arguments, cwd, sandbox policy and the ordered direct-human instructions, including later restrictions and revocations.
25
- 3. A fully literal `bash` command is granted without evidence or a model only when every invocation passes both the no-mutation grammar and the metadata/literal-output check. Examples include `ls -la`, `pwd`, and `find . -name "*.md"`. `printenv`, `ps`, file-content readers, recursive content searches, Git content and Git configuration enumeration always continue to review. Unknown options, abbreviations, output operands and persistent assignments also continue to review; `pwsh` has no static path.
26
- 4. The exact request is reviewed once. For shell calls, bounded workspace package definitions and referenced local scripts provide untrusted evidence; their canonical paths and content are rechecked before granting.
27
- 5. A reviewed pass returns `allowed-once`. For an otherwise eligible action, model denial, uncertainty, failure or budget exhaustion opens a timed confirmation. The dialog shows the exact operation, reviewer opinion and countdown. Explicit approval grants this call after another binding/evidence check; rejection or expiry returns `rejected`. Closing the dialog rejects. No downstream approval answerer is consulted.
28
-
29
- Native prohibitions, structural refusals, changed evidence, cancellation and invalidated requests cannot be overridden. Timed confirmation requires the UI row and an authenticated browser client; without them, or with the timeout set to zero, the existing non-grant path remains. Headless sessions do not gain an automatic fallback.
30
-
31
- The reviewer sees the bounded action, chronological direct-human instructions and local evidence, with no main-agent prompt, private reasoning or tools. Authorization is judged by substance: necessary local installation, build and test steps may implement a requested development task without the user naming every command. Earlier authority remains visible after a progress question or “continue”; later restrictions still apply. Automatic allowance requires low/medium risk, medium/high authorization and bounded side effects.
32
-
33
- Human text is retained without lossy selection, up to 12 KiB. Sensitive text, nontext constraints or overflow prevent automatic review rather than silently dropping restrictions; an otherwise valid action can receive fresh explicit confirmation through the timed UI without sending that history to the model. Evidence is capped at eight files, 6 KiB per file and 12 KiB overall. Protected, escaped, sensitive or unreadable files are withheld with explicit omission markers. Evidence coverage is partial: dependencies, imported code and workspace-wide scripts are not exhaustively inspected. Material remaining uncertainty prevents automatic permission.
34
-
35
- The native `load_workspace_dependencies` result can identify the current agent's local Node/pnpm entry points. A single literal invocation of that exact pair is not treated as reading a protected `.dsh` path; the original command still goes through normal policy and escalation review. Tool identity and local file identity are rechecked. Data operands, credentials, redirects and compound commands receive no exemption. Run the dependency tool again after a plugin/agent restart or runtime replacement; transcript text cannot register paths.
36
-
37
- Relative paths follow native tool bases: PowerShell uses the session cwd; Bash and file mutations prefer the sandbox workspace root. Resolved review paths are bound separately without rewriting tool arguments. Command timeouts accept positive finite values (including 240000–600000 ms); the native executor owns its limits. The review deadline and 30-second approval freshness window remain separate.
38
-
39
- ## Configuration
40
-
41
- Everything is optional; with no configuration the reviewer follows the current conversation's model. Edit the profile row's `config` and reload:
42
-
43
- | Key | Default | Meaning |
44
- | --- | --- | --- |
45
- | `enabled` | `true` | Master switch. `false` mounts nothing. |
46
- | `provider` / `model` | *(follow conversation)* | Fixed reviewer route, configured as a pair using your DSH model IDs. |
47
- | `reasoningEffort` | *(model default)* | Advertised reasoning effort of the fixed route. |
48
- | `reviewerPrompt` | *(none)* | Extra review constraints (≤4096 chars); may only restrict, never widen. |
49
- | `timeoutMs` | `30000` | Per-review deadline. |
50
- | `humanApprovalTimeoutMs` | `60000` | Human confirmation window, separate from model review; `0` disables it. Maximum 300000 ms; profile-owned. |
51
- | `maxInputBytes` | `32768` | Combined UTF-8 prompt/input cap; existing explicit values are retained. |
52
- | `outputTokens` | `1024` | Reviewer output cap, including reasoning where the model counts it. Explicit profile values are retained; incomplete or invalid verdicts still require human confirmation. |
53
- | `maxReviewsPerTask` | `20` | Review calls per direct user task. |
54
- | `consecutiveDenials` | `3` | Consecutive explicit denials before the breaker opens. Uncertainty and failures reset the streak but still consume review reservations. |
55
- | `staticReadonly` | `true` | Grant `bash` escalations proven non-mutating and limited to metadata/literal output without a model call. `false` sends eligible requests to the reviewer. |
56
-
57
- Unknown keys fail validation rather than being ignored. Reviewer model, effort and prompt can also be set per deployment on the plugin page; those saved values override the profile route but never the budgets.
58
-
59
- ## The reviewer model
60
-
61
- **Default: follow the conversation.** Leave `provider`/`model` empty. Each review resolves the provider/model from the requesting session's accepted request header, reusing DSH's configured credentials — no extra API key. Only the model route is inherited, not history, prompt, tools or budgets.
62
-
63
- **Fixed model.** Set `provider` and `model` together to pin a reviewer that stays put when the conversation switches models. A missing or invalid route rejects instead of falling back silently.
64
-
65
- ## Policy
66
-
67
- | Request | Outcome |
68
- | --- | --- |
69
- | Bound single escalation of `write`/`edit`/`bash`/`pwsh`, reviewer passes | `allowed-once`, this call only |
70
- | Bound `bash` escalation passing both static mutation and exposure checks | `allowed-once`, no model call, no budget consumed |
71
- | Protected credentials/security files, secrets, dangerous programs (anywhere in a fully parsed command) | Hard denial before review |
72
- | Model denial (including risk opinions), uncertainty or review failure for an eligible action | Timed human confirmation; no grant without explicit approval |
73
- | Unknown tools, subagents, nested or unbound requests | No automatic grant |
74
- | Human rejection or confirmation expiry | Rejected; fresh execution needs fresh review |
75
- | Changed evidence, invalidated grants, audit failure or cancellation | No grant; terminal feedback |
76
-
77
- Native `approval: never` still rejects before any answerer. Never stack this with another automatic approval answerer.
15
+ Replace `web` with your profile name and restart the profile.
78
16
 
79
- ## Reading configuration and session files
17
+ ## Use
80
18
 
81
- Ordinary sessions, skills and settings under `.codex`, `.dsh`, `.claude`, `.agents`, `.config` and other directories remain readable. Protection follows dedicated credential stores and actual credential fields, not whole directories. Auth/credential stores, private SSH keys and private-key stores are refused; ordinary public keys and certificates are readable. Mutation protection remains stricter.
19
+ Pick **Safe Auto** from the permission menu next to the chat input. It applies only to that session; choosing any other level turns it off. Nothing happens automatically until you pick it.
82
20
 
83
- Native `read` masks recognized credential values in JSON/JSONC, JSONL, YAML, TOML, ENV/INI and ordinary text, while keeping non-secret fields, references, line numbers and the host's original output caps. A fresh sanitized value regenerates both display text and metadata. Native `grep` masks or withholds sensitive matches before the host's result spill policy. Secret-bearing editor views and known raw shell dumps direct you to native `read`.
21
+ When a request does not pass review, a dialog shows the full action, the reviewer's opinion, and a countdown. Approve it once, or reject it. No answer means reject.
84
22
 
85
- Structured files are screened as complete sources up to 1 MiB; larger JSONL sessions are processed by complete selected lines (1 MiB per line, 8 MiB selected source). Malformed, unsupported or oversized structured content yields a safe error instead of raw fragments. Canonical targets, source versions and returned windows are checked through the mounted filesystem. Unknown opaque secrets cannot be identified by their content alone. These checks cover the pinned native tools; arbitrary Full Access scripts, custom tools and remote providers are not a secret-isolation sandbox. Shell output cannot be made safe by redacting it after execution because jobs may already have retained it.
23
+ Under **Plugins → Safe Auto** you can choose the reviewer model (empty follows the current session model), its reasoning effort, and extra review rules.
86
24
 
87
- ## Migrating from 0.1.x
25
+ ## What it covers
88
26
 
89
- - Profile keys `mode`, `workspaceRoots`, `shellCandidates`, `escalationCandidates`, `endpoint`, `apiKeyEnv`, `tokenField`, `fast*`/`deep*` and the budget fuses `sessionBudgetUnits`/`totalDenials` were removed; a profile still carrying them fails validation. The equivalent of 0.1.x `approvalReview: true` plus a UI-enabled session is now the only behavior.
90
- - `fastProvider`/`fastModel`/`fastReasoningEffort` became `provider`/`model`/`reasoningEffort`; `fastOutputTokens` became `outputTokens`; `fastCallsPerTask` became `maxReviewsPerTask`.
91
- - Settings saved by the 0.1.x panel keep loading (retired keys are stripped), but the reviewer route falls back to following the conversation — re-pick a model on the plugin page if you had a fixed one.
27
+ - Sandbox escalations of `write`, `edit`, `bash`, and `pwsh`, one call at a time.
28
+ - A few clearly harmless shell commands, such as `ls` or `pwd`, are approved without a model call.
29
+ - Credential files, private keys, and dangerous programs are always refused.
30
+ - Other tools, MCP, remote execution, and subagents are not auto-approved.
92
31
 
93
- ## Boundaries and limits
32
+ ## Limits
94
33
 
95
- - **Not an audited boundary.** The reviewer is a language model and can be wrong, especially in adversarial contexts. Keep the native sandbox, isolation and backups you already rely on.
96
- - **Static proof assumes operator-trusted PATH and runtimes.** It checks the original command text, not installed binary identity, shell functions or ambient runtime integrity. Explicit executable paths, `.exe`/`.cmd` suffixes, case variants, executable pager/config environment prefixes and persistent assignments do not qualify. Parser or grammar defects can produce incorrect proofs; this is not a runtime attestation or an absolute safety guarantee. An unproven command returns to reviewer/manual confirmation, not a new hard refusal.
97
- - **Quoting does not bypass hard refusals.** Literal argv, environment and redirect values are decoded before the protected-path/secret checks, including adjacent quote fragments. These checks also apply outside the static path and cannot be overridden by reviewer or manual confirmation. Unquoted wildcard fragments still invalidate proof even next to quoted text. Bash `/dev/tcp` and `/dev/udp` file redirects return to review, including quoted and numbered-fd input redirects; ordinary file input, fd merges and `/dev/null` output retain their existing rules.
98
- - **Git is not safe by subcommand name.** Content, history, remote URLs and configuration enumeration require review even when their flags prevent mutation. Metadata commands retain the original disabling controls required by the no-mutation grammar. Exact `git version` and `git --no-pager config --get user.name` / `user.email` are narrow static cases. The plugin never prepends controls or rewrites argv.
99
- - **Dedicated confirmation channel.** Pinned DSH 0.2.0-rc.2 approval and question waterfalls accept automatic answerers. Manual confirmation instead uses the plugin's authenticated browser HTTP endpoints and unpredictable single-use request IDs. Fetch metadata and host origin/authentication checks protect the browser channel against cross-site requests; they do not prove physical human action. Authenticated clients, their transport and same-process plugins remain trusted. No model tool is registered for approval.
100
- - **Server-owned expiry.** The window starts when a pending request is published. The server uses a monotonic deadline and atomically accepts the first valid answer; duplicate, late or cross-session answers fail. Disabling, changing settings, unloading or cancelling invalidates pending requests. Requests are in memory only, so restarts do not restore them. Both direct browser requests and the authenticated DSH desktop carrier are supported; incompatible request metadata is rejected.
101
- - **Foreign native approvals remain native.** Blueprint order and other plugins' model approvals continue to downstream answerers when no Safe Auto-owned execution context exists. Requests in an owned context, including mismatches, stale/claimed requests, and unbound impersonation of `bash`/`pwsh`/`write`/`edit`, remain fail-closed.
102
- - **Coverage stops at native single-call escalation.** Only `write`/`edit`/`bash`/`pwsh` widening from `workspace-write` is reviewed. Other tools, MCP, remote execution, subagent calls and standing permission changes stay outside the automatic envelope.
103
- - **Evidence is checked, execution is not atomic.** Evidence paths, versions and SHA-256 digests are revalidated before granting, but files can still change between approval and execution. Other same-process plugins remain trusted; unloading removes the guards. Keep external isolation with restricted credentials and egress.
104
- - **No guarantee is claimed.** There is no fail-open option, no durable audit database and no claim of absolute safety or savings.
34
+ - **This is not a security boundary.** The reviewer is a language model and can be wrong. Keep your sandbox, isolation, and backups.
35
+ - Files can still change between approval and execution.
36
+ - Do not combine it with other automatic approvers.
105
37
 
106
38
  ## License
107
39
 
108
- Original implementation under [SATA License 2.1](LICENSE). Community designs informed the work; their code was not vendored.
40
+ [SATA License 2.1](LICENSE)
package/README.zh-CN.md CHANGED
@@ -1,108 +1,40 @@
1
- # 安全自动
1
+ # 安全自动
2
2
 
3
3
  <!-- release-state-note -->
4
4
 
5
- 自动审核原生沙箱提权请求。当真实的 `write`/`edit`/`bash`/`pwsh` 调用要求把沙箱放宽到 Workspace Write 之外时,由独立审核模型把关这一次调用。少量只列元数据或输出字面量的 `bash` 命令,可通过静态检查而不调模型。通过就自动继续;其他审核结果会在当前会话显示完整操作,给你 60 秒选择“仅批准这一次”或拒绝。无人响应则到期拒绝。插件不会把整个会话切成 Full Access。
5
+ [English](README.md)
6
6
 
7
- [English](README.md) · [单次提权](docs/ADR-0002.md) · [审核模型路由](docs/ADR-0003.md) · [授权与证据](docs/ADR-0004.md) · [限时人工确认](docs/ADR-0005.md) · [静态 Shell 证明](docs/ADR-0006.md)
7
+ 新增一个「安全自动」权限档位。智能体请求在沙箱之外执行命令或修改文件时,由独立审核模型检查这一次调用。通过就继续;不通过时给你 60 秒决定批准还是拒绝。插件不会把会话切换成 Full Access。
8
8
 
9
- > **0.2.0 是破坏性简化。** 0.1.x 的精确规则预检、workspace/shell/escalation 候选名单、`shadow`/`smart`/`unattended` 模式、深度审核阶段和 HTTP 端点均已删除。见[从 0.1.x 迁移](#从-01x-迁移)。
10
-
11
- ## 安装与启用
9
+ ## 安装
12
10
 
13
11
  ```sh
14
- dsh plugin --profile <profile> add @klarkxy/dsh-safe-auto
12
+ dsh plugin --profile web add @klarkxy/dsh-safe-auto
15
13
  ```
16
14
 
17
- bundle 会插入 `dsh-safe-auto` 与 `dsh-safe-auto-ui` 两个 profile 条目。之后输入框的权限菜单里会在原生档位旁出现第四个选项——选择 **安全自动** 即为本会话启用,选回任意原生档位即停用。在此之前没有任何自动行为:会话不继承启用状态,重启、切换权限或卸载都会撤销它。插件页只保留审核配置、安全说明和只读的预算边界,按页签分区。
18
-
19
- > 输入框的权限控件是替换型插槽:安装 Safe Auto 期间由插件自己渲染这个控件(包含原生档位),选项才能出现在菜单里。如果宿主以后重新设计这个控件,插件的副本在新版本跟进前可能落后。
20
-
21
- ## 工作原理
22
-
23
- 1. 原生工具调用请求 `sandbox_permissions` 提权(`workspace-write` 或 `danger-full-access`)。
24
- 2. 插件绑定当前活跃执行——agent、callId、signal、完整参数、cwd、沙箱策略,以及按时间排序的真人指令,包含后续限制和撤销。
25
- 3. 完全按字面解析的 `bash` 命令,只有每次调用同时通过“不修改”和“仅元数据/字面量输出”两项检查,才会不收集证据、不调模型直接放行,例如 `ls -la`、`pwd`、`find . -name "*.md"`。`printenv`、`ps`、文件内容读取、递归内容搜索、Git 内容及配置枚举均继续审核。未知选项、缩写、输出文件操作数和持久赋值也继续审核;`pwsh` 不走静态通道。
26
- 4. 审核模型审查一次完整请求。Shell 调用会补充有界的工作区包配置和所引用本地脚本作为非可信证据;放行前重新核对路径与内容。
27
- 5. 审核通过返回 `allowed-once`。请求本身符合范围时,模型拒绝、不确定、故障或预算耗尽会进入限时人工确认。窗口显示完整操作、审核意见和倒计时;明确批准后再次核对绑定及证据,只放行这一调用。拒绝、关闭窗口或超时返回 `rejected`,不交给下游审批器。
28
-
29
- 宿主禁止、结构性拒绝、证据变化、取消或请求失效不能被覆盖。人工确认需要 UI 条目和已认证的浏览器客户端;没有界面或把人工超时设为零时,保持原来的不放行处理。Headless 会话不会获得自动回退权限。
30
-
31
- 审核模型看到有界动作、按时间排序的真人指令和本地证据,不继承主代理提示词、隐藏推理或工具。授权按实际含义判断:完成开发任务所需的本地安装、构建和测试可以属于已授权工作,无须用户逐条点名命令。询问进度或说“继续”不会丢掉先前授权,后续限制仍然有效。只有低/中风险、至少中等授权且副作用有界才自动放行。
32
-
33
- 真人文本完整保留,合计最多 12 KiB。敏感文本、非文本约束或超限会阻止自动审查,不会静默丢掉限制;请求本身有效时,仍可在限时窗口中明确批准当前完整操作,不把这段历史发给模型。证据最多八个文件、每个 6 KiB、合计 12 KiB;受保护、越界、敏感或不可读内容会被标记并排除。证据并非完整安全证明:不会穷举依赖、导入代码或整个 workspace 的脚本,仍有实质不确定性时不得自动放行。
34
-
35
- 原生 `load_workspace_dependencies` 的结果可用于识别当前 agent 的本地 Node/pnpm 入口。以明确字面量执行这一精确组合时,不再把入口路径误判为读取受保护的 `.dsh` 文件;原命令仍走正常策略与提权审核。插件复核工具定义和本地文件身份,数据参数、凭据、重定向及复合命令不享有例外。插件或 agent 重启、运行器替换后需再次调用依赖工具;聊天文本不能登记可信路径。
36
-
37
- 相对路径遵循原生工具的基准:PowerShell 使用会话 cwd,Bash 与文件修改优先使用沙箱 workspace root。解析后的审核路径单独参与绑定,不改写工具参数。命令超时接受有限正数(包括 240000–600000 ms),实际限制由原生执行器负责;审核超时和 30 秒批准有效期仍各自独立。
38
-
39
- ## 配置
40
-
41
- 全部可选;不配置时审核模型跟随当前对话。编辑 profile 条目的 `config` 并重载:
42
-
43
- | 键 | 默认 | 含义 |
44
- | --- | --- | --- |
45
- | `enabled` | `true` | 总开关。`false` 不挂载任何守卫。 |
46
- | `provider` / `model` | *(跟随对话)* | 固定审核路由,用你的 DSH 模型 ID 成对配置。 |
47
- | `reasoningEffort` | *(模型默认)* | 固定路由 advertised 的思考强度。 |
48
- | `reviewerPrompt` | *(无)* | 额外审核约束(≤4096 字符);只能收紧,不能放宽。 |
49
- | `timeoutMs` | `30000` | 单次审查超时。 |
50
- | `humanApprovalTimeoutMs` | `60000` | 独立于模型审查的人工确认窗口;`0` 关闭,最多 300000 ms,只在 profile 配置。 |
51
- | `maxInputBytes` | `32768` | 提示词与输入合计的 UTF-8 字节上限;已有显式配置保持原值。 |
52
- | `outputTokens` | `1024` | 审核输出上限,含模型计入的思考预算。已有 profile 显式值保持不变;不完整或无效结论仍需人工确认。 |
53
- | `maxReviewsPerTask` | `20` | 每个真人任务的审查次数上限。 |
54
- | `consecutiveDenials` | `3` | 连续明确拒绝达到该值后熔断。不确定和故障重置拒绝计数,但仍消耗审查预算。 |
55
- | `staticReadonly` | `true` | 同时证明不修改且只输出元数据/字面量的 `bash` 提权不调模型放行;`false` 将符合范围的请求送审核模型。 |
56
-
57
- 未知键会直接校验失败,而不是被忽略。审核模型、思考强度和提示词也可以在插件页配置;保存的值覆盖 profile 路由,但永远不覆盖预算。
58
-
59
- ## 审核模型
60
-
61
- **默认:跟随对话。** `provider`/`model` 留空。每次审查从请求会话已接受的请求头解析 provider/model,复用 DSH 已配置的凭据——不需要额外 API key。继承的只有模型路由,不包括历史、提示词、工具或预算。
62
-
63
- **固定模型。** 成对设置 `provider` 和 `model`,对话切换模型时审核路由保持不变。路由缺失或非法时直接拒绝,不做静默回退。
64
-
65
- ## 策略
66
-
67
- | 请求 | 结果 |
68
- | --- | --- |
69
- | 绑定的 `write`/`edit`/`bash`/`pwsh` 单次提权,审查通过 | `allowed-once`,仅此一次 |
70
- | 同时通过静态修改与信息暴露检查的 `bash` 提权 | `allowed-once`,不调模型、不耗预算 |
71
- | 受保护的凭据/安全文件、密钥、危险程序(完整解析的命令中任何位置) | 审查前硬拒绝 |
72
- | 符合请求范围的模型拒绝(含风险意见)、不确定或审查故障 | 限时人工确认;未明确批准就不放行 |
73
- | 未知工具、子代理、嵌套或未绑定请求 | 不自动放行 |
74
- | 人工拒绝或确认超时 | 拒绝;再次执行须重新审查 |
75
- | 证据变化、请求失效、审计故障或取消 | 不放行并给出终止原因 |
76
-
77
- 原生 `approval: never` 仍在任何应答者之前拒绝。不要与其他自动审批应答者叠加使用。
15
+ 把 `web` 换成你的 profile 名,安装后重启 profile。
78
16
 
79
- ## 读取配置和会话文件
17
+ ## 使用
80
18
 
81
- `.codex`、`.dsh`、`.claude`、`.agents`、`.config` 等目录里的普通会话、技能和配置仍可读取。保护依据是专用凭据存储和实际凭据字段,不是整个目录。认证/凭据存储、SSH 私钥与私钥仓库被拒绝,普通公钥和证书可以读取。修改文件的限制仍更严格。
19
+ 在输入框旁的权限菜单中选择「安全自动」。它只对当前会话生效,选回其他档位即关闭;选择之前不会有任何自动行为。
82
20
 
83
- 原生 `read` 对 JSON/JSONC、JSONL、YAML、TOML、ENV/INI 和普通文本中的已识别凭据值隐藏显示,保留非秘密字段、引用路径、原行号和宿主输出上限。脱敏后的结构化值同时生成显示内容和元数据。原生 `grep` 在宿主落盘完整搜索结果之前脱敏或隐藏敏感匹配。包含秘密的编辑器查看操作、已知的 shell 原文转储会提示改用原生 `read`。
21
+ 请求未通过审核时,会弹出确认窗口,显示完整操作、审核意见和倒计时。你可以仅批准这一次,或者拒绝;无人响应即拒绝。
84
22
 
85
- 结构化文件以完整内容检查,上限 1 MiB;较大的 JSONL 会话按完整的所选行处理(每行 1 MiB、所选原文合计 8 MiB)。损坏、不支持或超限的结构化内容返回安全错误,不返回原文片段。通过当前挂载的文件系统核对真实目标、版本和返回窗口。没有特征的任意秘密无法仅凭文本识别。这些检查覆盖已核对的原生工具,不构成任意 Full Access 脚本、自定义工具或远程 provider 的秘密隔离沙箱。shell 结果不能靠执行后脱敏保证安全,因为 jobs 可能已经保存了原始输出。
23
+ 在「插件 → 安全自动」中可以选择审核模型(留空跟随当前会话模型)、思考强度,以及额外的审核要求。
86
24
 
87
- ## 从 0.1.x 迁移
25
+ ## 覆盖范围
88
26
 
89
- - profile 键 `mode`、`workspaceRoots`、`shellCandidates`、`escalationCandidates`、`endpoint`、`apiKeyEnv`、`tokenField`、`fast*`/`deep*` 以及熔断 `sessionBudgetUnits`/`totalDenials` 已删除;仍携带这些键的 profile 会校验失败。0.1.x 的 `approvalReview: true` 加插件页启用,现在就是唯一行为。
90
- - `fastProvider`/`fastModel`/`fastReasoningEffort` 更名为 `provider`/`model`/`reasoningEffort`;`fastOutputTokens` 更名为 `outputTokens`;`fastCallsPerTask` 更名为 `maxReviewsPerTask`。
91
- - 0.1.x 面板保存的设置仍可加载(废弃键会被剥离),但审核路由回退为跟随对话——如果你用过固定模型,请在插件页重新选择。
27
+ - `write`、`edit`、`bash`、`pwsh` 的沙箱提权,每次只放行一次调用。
28
+ - 少量明显无害的命令(如 `ls`、`pwd`)无需调用模型直接放行。
29
+ - 凭据文件、私钥和危险程序始终拒绝。
30
+ - 其他工具、MCP、远程执行和子代理不会自动放行。
92
31
 
93
- ## 边界与限制
32
+ ## 限制
94
33
 
95
- - **不是经过审计的安全边界。** 审核方是语言模型,可能出错,尤其在对抗性上下文中。保留你原本依赖的原生沙箱、隔离和备份。
96
- - **静态证明以操作者信任 PATH 和运行时为前提。** 它检查原始命令文本,不核验已安装二进制、shell 函数或环境运行时的完整性。显式程序路径、`.exe`/`.cmd` 后缀、大小写变体、可执行 pager/配置环境前缀和会持久化的赋值都不符合条件。解析器或语法缺陷仍可能造成错误证明;这不是运行时认证,也不保证绝对安全。无法证明的命令回到审核/人工确认,不新增硬拒绝。
97
- - **引号不能绕过硬拒绝。** 保护路径和秘密检查先读取解码后的字面参数、环境及重定向值,包括相邻引号片段拼接;退出静态通道时仍执行这些检查,模型或人工确认不能覆盖。引号旁的未引用通配符仍使静态证明失败。Bash 的 `/dev/tcp`、`/dev/udp` 文件重定向回到审核,包括引用过的路径及编号 fd 输入重定向;普通文件输入、fd 合并和 `/dev/null` 输出保留原有规则。
98
- - **Git 的子命令名不能证明安全。** 内容、历史、远程 URL 和配置枚举即使带有防修改参数,也要经过审核。元数据命令仍须满足“不修改”语法要求的原始控制参数。精确的 `git version`、`git --no-pager config --get user.name`/`user.email` 可走窄范围静态通道。插件不会补参数或重写命令。
99
- - **独立确认通道。** 当前 DSH 0.2.0-rc.2 的审批和问答链允许自动应答器。人工确认改用插件已认证的浏览器 HTTP 入口和不可预测、单次使用的请求 ID。Fetch Metadata 与宿主的来源、认证检查用于防跨站请求,并不证明物理真人点击;已认证客户端、其传输层和同进程插件仍然可信。不向模型注册审批工具。
100
- - **服务端掌握时限。** 请求发布后开始计时,用单调时钟判定到期,只接受第一个有效答案。重复、迟到和跨会话回答均失败。停用、修改设置、卸载或取消会使待审批失效;请求仅存内存,重启不会恢复。兼容直接浏览器请求与已认证的 DSH 桌面传输;不符合要求的请求类型仍会被拒绝。
101
- - **其他插件的原生审批保持原生流程。** 没有 Safe Auto 自有执行上下文时,Blueprint 顺序审批、其他插件的模型审批会继续交给后续处理器。已有自有上下文中的不匹配、过期、已领取请求,以及无上下文却冒用 `bash`/`pwsh`/`write`/`edit` 的请求仍然关闭放行。
102
- - **覆盖范围止于原生单次提权。** 只审核从 `workspace-write` 放宽的 `write`/`edit`/`bash`/`pwsh`。其他工具、MCP、远程执行、子代理调用和常驻权限变更都在自动范围之外。
103
- - **证据会复核,执行并非原子操作。** 放行前复核证据路径、版本和 SHA-256 摘要,但审批与执行之间仍可能发生文件变化。同进程插件仍然可信,卸载会移除守卫;实际隔离仍依赖外部手段及受限的凭据与出站。
104
- - **不作任何保证。** 没有 fail-open 选项,没有持久审计数据库,也不承诺绝对安全或节省比例。
34
+ - **这不是安全边界。** 审核方是语言模型,可能判断错误。请保留原有的沙箱、隔离和备份。
35
+ - 批准与执行之间,文件仍可能发生变化。
36
+ - 不要与其他自动审批插件同时使用。
105
37
 
106
38
  ## 许可证
107
39
 
108
- 原创实现基于 [SATA License 2.1](LICENSE)。社区设计启发了本工作;未打包其代码。
40
+ [SATA License 2.1](LICENSE)
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "@klarkxy/dsh-safe-auto",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "main": "src/index.js",
5
5
  "description": "Safe Auto: an independent reviewer vets each native sandbox escalation; only a pass auto-approves that one call.",
6
6
  "engines": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@klarkxy/dsh-safe-auto",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Safe Auto: an independent reviewer vets each native sandbox escalation; only a pass auto-approves that one call.",
5
5
  "icon": "./icon.svg",
6
6
  "type": "module",
@@ -20,7 +20,6 @@
20
20
  "cordis.patch.yml",
21
21
  "dsh.plugin.json",
22
22
  "README*.md",
23
- "docs/*.md",
24
23
  "LICENSE"
25
24
  ],
26
25
  "scripts": {
@@ -59,7 +58,7 @@
59
58
  }
60
59
  },
61
60
  "dependencies": {
62
- "@klarkxy/dsh-plugin-kit": "0.3.1",
61
+ "@klarkxy/dsh-plugin-kit": "0.3.2",
63
62
  "jsonc-parser": "^3.3.1",
64
63
  "toml-eslint-parser": "^1.0.3",
65
64
  "unbash": "^5.0.0",
@@ -79,7 +78,7 @@
79
78
  "@deepseek-ai/cordis": "4.0.4",
80
79
  "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.2",
81
80
  "@deepseek-ai/dsh-storage-domain": "0.2.0-rc.2",
82
- "@klarkxy/dsh-model-route": "0.2.1",
81
+ "@klarkxy/dsh-model-route": "0.2.2",
83
82
  "jsdom": "29.1.1"
84
83
  },
85
84
  "publishConfig": {
@@ -87,6 +86,6 @@
87
86
  "registry": "https://registry.npmjs.org/"
88
87
  },
89
88
  "dshRelease": {
90
- "contentHash": "5309180f394b9b6b2d67eda6c3c44d46fee17030f6493def4a77e431334810f7"
89
+ "contentHash": "8da26bacb5f2f2d7333afa35854a138339fbffbf2a024fcd70f6a8ca537bf4fe"
91
90
  }
92
91
  }
package/docs/ADR-0001.md DELETED
@@ -1,65 +0,0 @@
1
- # ADR-0001: bounded preflight, not an escalation auto-answerer
2
-
3
- Status: historical RC1 decision, partially superseded by [ADR-0002](ADR-0002.md) in `0.1.0-rc.2`. **The preflight mechanism itself (ordinary-command review, workspace/shell candidates, shadow/smart/unattended modes) was removed in `0.2.0-rc.0`**; only the execution-bound approval review of ADR-0002 remains.
4
-
5
- **The no-auto-escalation statements below describe RC1, not the current candidate.** RC2 adds separately enrolled, execution-bound native `allowed-once` approvals, including unattended mode, without changing standing sandbox policy. Use ADR-0002 and the current READMEs for configuration and deployment acceptance. The original decision is retained here as history.
6
-
7
- ## Evidence baseline
8
-
9
- DSH contracts were inspected at `477b4f420553e8a52c2fbccc464d7561b239c443` (`0.1.7-rc.2`):
10
-
11
- - [Tool pipeline and opaque execution identity](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/core/tools/src/index.ts). `prepareExecution` runs pre-execute, resolves any ask, then applies guards before dispatch. `tools/result` receives the final immutable result. Guards are not an async approval API.
12
- - [Approval service](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/interaction/user-approval/src/index.ts). `never` rejects before answerers; only `allowed-once` grants; `effectivePolicy` is private despite older prose implying otherwise.
13
- - [Sandbox semantics](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/docs/subsystems/sandbox.md). File effects only; full access bypasses confinement; providers may report partial enforcement. Approval can widen a single call without changing the session's displayed standing mode.
14
- - [Public synchronous policy resolver](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/sandbox/sandbox-policy/src/index.ts).
15
- - [Cordis plugin registry](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/docs/cordis-api/registry.md) accepts a real Standard Schema v1 validator. The package supplies one rather than an unvalidated Config object. No volatile settings form is promised.
16
-
17
- Design references: [Autogate](https://github.com/wangxing-git/dsh-autogate) (public hooks, path checks, independent reviewer) and [Approve for me](https://github.com/timeance/dsh-approve-for-me) (rules bound maximum grant, tool-free review, fail-closed). These are not independent security endorsements. Approve for me's own README still marks the full authenticated profile smoke test as outstanding. No community source was vendored, so there is no MIT-derived code to silently relicense.
18
-
19
- ## Decision
20
-
21
- Implement a small original module instead of importing a broad permissive approval engine. Reuse DSH's maintained extension contracts, not its internals. Runtime has no additional npm dependencies. The smaller capability set is deliberate: do not ship a regex-based universal shell parser or assume model review replaces a sandbox.
22
-
23
- ```
24
- pre-execute
25
- deterministic deny -> deny
26
- ordinary local file -> next native policy
27
- exact enrolled command -> fast -> optional deep -> next / ask / deny
28
- everything else -> native ask (unattended: deny)
29
- existing downstream asks/denials remain authoritative
30
- native ask resolution
31
- monotonic guard rechecks state and proves preflight ran for this execution token
32
- tool dispatch -> final result audit
33
- ```
34
-
35
- The plugin NEVER answers `allowed-once`. It rejects approval requests in unattended mode and otherwise delegates them. An LLM's `allow` is only permission to continue through preflight under existing constraints; it cannot approve an unrelated tool ask or escalation.
36
-
37
- The current shared `workspaceRoots` and `shellCandidates` are explicit host-operator grants. Candidate commands match exactly, including all arguments. File names/contents and tool descriptions are not user authority. Only the latest direct `user/message` supplies reviewer authority. Missing history, attachments, unresolved short replies, delegated instructions or oversized input cannot enlarge capabilities. Short human replies can lack sufficient context and cause manual fallback by design.
38
-
39
- The session authority reader uses existing `snapshotEvents()` in one isolated adapter function. DSH deprecates synchronous historical reads; do not propagate this dependency. Replace it with a host projection/read contract when that can preserve direct-user provenance without trusting model summaries.
40
-
41
- ## What the prototype does not guarantee
42
-
43
- Model false negatives, secret-detection recall, network confinement, protection from malicious same-process plugins, and TOCTOU-free filesystem identity are NOT guaranteed. Rechecks reduce stale decisions but do not replace enforcement by filesystem/process providers. Operator-enrolled scripts/executables and their dependencies can change after enrollment. Do not treat a cached or exact command name as immutable code identity.
44
-
45
- The plugin checks the resolved policy, not the existence or completeness of every enforcing backend. Use a correctly composed DSH profile. An artificial provider reporting Workspace Write is not a sandbox. Local POSIX path inspection is not valid evidence for SSH/Windows filesystem identity, so those paths/workflows remain outside the auto-grant contract.
46
-
47
- Reviewer reservations use bytes and caps, not a universal tokenizer. They bound this instance's attempts and payloads, not a provider's billing implementation or the main agent. No long-lived approval grants, cross-call allow cache, budget persistence, native DSH provider adapter, PI probe, full PTC or arbitrary MCP compatibility is included. These are explicit deferred work, not implemented features.
48
-
49
- Audit phases distinguish proposed assessment from actual outcome. Host logs are not a durable tamper-proof ledger. Counters reset on reload/restart. Unattended is denial-on-uncertainty, not a promise the overall agent task will finish.
50
-
51
- ## Validation and release gate
52
-
53
- Automated tests: deterministic policies and protected paths; symlink/hardlink/race checks; full-access rejection; exact command envelope; strict completion parsing; oversized stream limits; signal-ignoring timeout; cancellation and unload; concurrent budget reservation; circuit behavior; direct-user provenance; downstream-policy preservation; opaque token isolation and bypass prevention; default shadow behavior.
54
-
55
- The real Cordis/ToolRuntime contract test loads the same installed DSH dependencies as the existing workspace, verifies the Standard Schema plugin mount, real guard ordering, same-ID cleanup, downstream denials, preflight bypass rejection and disposal. It uses fixture tool bodies and a fixture sandbox-policy service, NOT a real operating-system sandbox. In CI missing host packages fail, rather than silently skip.
56
-
57
- Before promoting beyond candidate status:
58
-
59
- 1. `pnpm install --frozen-lockfile && pnpm check` must pass including the real-host contract test. Inspect npm pack contents and the prerelease `next` tag behavior; do not publish by merging without review.
60
- 2. Install the local package into a disposable authenticated Web profile with native tools and real filesystem/process sandbox providers. Start shadow, inspect logs, then enable smart. Confirm ordinary file work, protected targets, unknown tools, user rejection and unavailable reviewer behavior.
61
- 3. Repeat in Headless/unattended: approvals must not be auto-granted; verify cancellation, provider failure and limits. Test partial/missing sandbox backends and Full Access rejection explicitly.
62
- 4. Use a fake HTTP server for deterministic fast/deep outcomes and a separately consented real endpoint to validate its `tokenField`, usage, caps and structured output. Do not spend user API credit implicitly from tests.
63
- 5. Run task and malicious-input corpora before making accuracy, prompt-injection resistance or token-saving claims. Test supported OS/provider versions individually.
64
-
65
- CI should remain frozen-lockfile and must not be loosened merely to make this package pass. A dependency-free new workspace importer may need to be recorded by pnpm; any resulting lockfile-only metadata change should contain no dependency version updates.
package/docs/ADR-0002.md DELETED
@@ -1,76 +0,0 @@
1
- # ADR-0002: native, execution-bound one-shot escalation
2
-
3
- Status: implemented in `0.1.0-rc.2`; automated contracts verified; live-profile acceptance pending. Since `0.2.0-rc.0` this execution-bound approval review is the plugin's only mechanism; the exact-rule candidate enrollment described below was removed along with preflight.
4
-
5
- This partially supersedes [ADR-0001](ADR-0001.md): its prohibition on returning `allowed-once` and blanket unattended escalation rejection were an RC1 limitation, not the final design. Ordinary preflight, explicit envelopes, bounded review, no global permission writes and conservative failure handling remain in force.
6
-
7
- ## Verified source baseline
8
-
9
- Contracts are pinned to DSH `0.1.7-rc.2`, commit `477b4f420553e8a52c2fbccc464d7561b239c443`:
10
-
11
- - [Native escalation helper](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/sandbox/sandbox/src/escalation.ts): the `workspace-write` widening target is `danger-full-access`; `approveEscalation` asks before execution and returns a mode for that call, not a session mutation. Its exact audited reason includes target mode and justification.
12
- - [ApprovalService](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/interaction/user-approval/src/index.ts): requires an open turn, appends asked/decided with paired IDs, fails closed, and enforces native `never` before answerers. Requests omit tool arguments and execution tokens.
13
- - [ToolRuntime](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/core/tools/src/index.ts): preflight and monotonic guards precede the around-dispatch `tools/execute` seam; immutable argument snapshots and opaque execution tokens are registry-owned. Final results are observable separately.
14
- - [Bash consumer](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/shell/tool-bash/src/index.ts): native approval is requested inside tool execution; with jobs mounted, a foreground timeout may promote to background. Without jobs, the foreground executor deadline applies.
15
- - [Filesystem consumer](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/fs/tool-fs/src/sandbox.ts): consumes the same one-shot helper before file operations.
16
- - [Filesystem world mapping](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/fs/fs/src/index.ts) and [local implementation](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/fs/fs-local/src/index.ts): `processPathFromHostPath` returns no mapping for an unavailable host world; local paths can be verified without assuming SSH and local paths share identity.
17
-
18
- Compatibility is evidence for this pinned version, not a guarantee about future DSH releases. No private `effectivePolicy`, monkey-patching, global sandbox switch or custom executor is used.
19
-
20
- ## Decision
21
-
22
- Keep two separate decisions: ordinary preflight allows trying an action under standing policy; an escalation verdict approves the exact native permission request. Add an independent, empty-by-default `escalationCandidates` envelope. Each rule matches a native tool, enrolled cwd, requested mode, and exact command or absolute file path. Only native `bash`, `write` and `edit` widening from Workspace Write are candidates. Full arguments still require bounded independent model review and direct human task authority.
23
-
24
- ```
25
- pre-execute: hard checks + deterministic escalation assessment
26
- -> preserve downstream ask/deny
27
- -> save execution-token binding; no model call yet
28
- monotonic guard: confirm binding and unchanged policy
29
- -> admit the native tool to ask, not to execute unconfined
30
- around tools/execute: AsyncLocalStorage with one live execution
31
- -> native approveEscalation -> ApprovalService.request
32
- -> approval/request: exact identity and reason match
33
- -> claim one slot before awaiting
34
- -> envelope + shared budget + fast/optional-deep review
35
- -> recheck binding, lifetime, cancellation and freshness
36
- -> allowed-once / rejected / cancelled / native human fallback
37
- native tool consumes mode for this invocation only
38
- -> tools/result clears execution decision; next call remains confined
39
- ```
40
-
41
- An `AsyncLocalStorage` scope is necessary because ApprovalRequest has neither full arguments nor a registry token. A process-wide map keyed by model-visible callId can confuse agents or parallel calls. Match agent object, tool name, callId, current signal and complete helper reason against the active dispatch; the opaque token binds its preflight. Mark the single slot as claimed before any await. Detached child async work retains an inactive store and cannot reuse a finished dispatch. Other same-process plugins are trusted, not adversaries this mechanism can isolate.
42
-
43
- The SHA-256 binding includes complete arguments, effective policy/root, latest direct user message identity and text, execution-world/lifetime flags and file state where applicable. Both automatic and fallback outcomes are rechecked before return. It is a stale-decision detector, not an OS capability or promise of zero races. Model justifications are data and never authority.
44
-
45
- ## Process and file lifetime
46
-
47
- Automatic Bash widening requires no jobs service, a sandbox-capable shell, no background request, bounded explicit timeoutMs and no alternate workdir. Jobs-enabled compositions fall back to a human or reject unattended; the plugin does not alter global jobs configuration. Arbitrary descendants, scripts, hooks, PATH and mutable executable inputs remain risks requiring isolation and narrow enrollment. Do not label a simple command string as a validated process tree.
48
-
49
- File widening requires an exact canonical absolute target, existing canonical parent, regular/non-linked target (or a new file for write), and supported argument fields. Credential/configuration/system targets remain denied. The parent/target stat snapshot is compared around review. A full-access file call still depends on the trusted file provider; there is no finer kernel path grant in this plugin.
50
-
51
- Automatic decision freshness defaults to 30 seconds, with a separate default 30-second maximum requested Bash timeout. The former is not a process kill timer, retrospective revocation or rollback. Fresh human approval may resolve an expired automatic review in smart mode, but changed bindings and cancelled requests do not grant.
52
-
53
- ## Failure, budget and composition
54
-
55
- Only a fresh `allow` inside the envelope produces `allowed-once`. Explicit denial is final for that question; uncertainty/errors use native human fallback in smart and reject in unattended. Missing identity or an inactive/claimed context rejects. No semantic retries and no transferable allow cache.
56
-
57
- Both phases share session budget reservations and fuses. Native escalation calls do not receive another model review in preflight. HTTP caps, bounded streams, cancellation and late-answer handling remain unchanged. Budgets are per-instance in memory, not exact billing or the main-agent budget.
58
-
59
- Native `approval: ask` is required for the answerer to run, including plugin unattended mode. Native `never` wins before this listener. Preserve other preflight decisions; unrelated asks cannot acquire an automatic grant from this escalation envelope. Do not stack with other auto answerers. Audit decisions before returning permission, preserving DSH's own paired audit events; logger failures cannot create a grant.
60
-
61
- ## Validation
62
-
63
- The PR adds 50 escalation unit/race/fault tests, a real loopback HTTP transport test, and a real Cordis/ToolRuntime/ApprovalService/approveEscalation contract test. Combined with RC1's tests, CI ran **119 tests, all passing, none skipped**, for implementation commit `37c4c987c84656f0490f09232f2a0612bc576630` ([CI #34](https://github.com/klarkxy/dsh-plugins/actions/runs/36293861872)). Frozen-lockfile installation and the entire existing `pnpm check` also passed without loosening CI.
64
-
65
- The native contract verifies an outside-workspace disposable fixture write, the real asked/decided pair, native `never` precedence, downstream denial and absence of escalation inheritance on the next call. A loopback server exercises real HTTP, not a paid model. Session storage, filesystem policy and tool bodies are test fixtures. This verifies interface composition, not authenticated client routing, an actual OS sandbox or classifier accuracy. Local dependency-free testing ran the 50 new unit tests plus the HTTP test; the new native contract ran in CI with the workspace's pinned DSH dependencies.
66
-
67
- ## Remaining deployment acceptance
68
-
69
- - [ ] Install a local candidate into disposable authenticated Web and Headless profiles, preserving actual users' profiles. Verify required native services and plugin Config load/reload.
70
- - [ ] Exercise actual native Bash and filesystem consumers with real OS providers. Verify full/partial/missing enforcement separately, one-shot mode consumption and the next command's unchanged standing sandbox.
71
- - [ ] Check jobs-enabled Bash stays manual/rejected; no-jobs Bash respects timeout/cancellation. Exercise descendant/process cleanup rather than inferring it from a returned tool result.
72
- - [ ] Check native human fallback, explicit rejection, cancellation, shutdown during review and no other auto answerer in the chain.
73
- - [ ] With separately authorized model credentials, test provider output caps and structured output, task corpus, prompt injection and false-negative behavior. Loopback tests are not model quality measurements.
74
- - [ ] Complete OS/provider-version-specific security review before stable release. Keep prerelease/Draft status until acceptance evidence exists.
75
-
76
- No PR merge, npm release, user profile change or paid model invocation is part of this implementation change. The project catalog only accepts published latest packages, so this prerelease is deliberately not added there.
package/docs/ADR-0003.md DELETED
@@ -1,39 +0,0 @@
1
- # ADR-0003: conversation-following and independent reviewer routes
2
-
3
- Status: implemented in RC3. This supersedes the HTTP-only routing statements in ADR-0001/0002, not their remaining execution restrictions or deployment acceptance gates. **`0.2.0-rc.0` narrowed the routing surface to a single reviewer route** (`provider`/`model`/`reasoningEffort`, defaulting to the conversation): the deep stage, the HTTP transport and the fast/deep split described below were removed.
4
-
5
- ## Requirement
6
-
7
- The approval model must be independently configurable, and default to the current conversation. Apply this consistently to normal preflight and one-shot escalation. Do not duplicate provider credentials, share main-agent context, add ambient permissions, or silently switch data destinations on failure.
8
-
9
- ## Verified contracts
10
-
11
- Inspected DSH `477b4f420553e8a52c2fbccc464d7561b239c443` (`0.1.7-rc.2`):
12
-
13
- - `packages/llm/llm/src/types.ts`: `GenerateOptions` accepts provider/model, one-shot identity-free user inputs, a separate system prompt, tools, maxTokens and signal. `RequestUserInput` has no durable id/source. TokenUsage counters are disjoint; native finish must be stop for a valid review.
14
- - `packages/llm/llm/src/index.ts`: `LlmRuntime.stream` uses registered adapters and normalizes adapter failures. Native `LlmAdapter` registration and controlled-stream integration are exercised in CI.
15
- - Existing session request headers carry the accepted call config; the plugin reads only provider/model. Same-agent options are a fallback only when the header is absent.
16
-
17
- ## Decision
18
-
19
- With endpoint/fastProvider/fastModel empty, resolve the requesting conversation's provider/model for every eligible review. Fixed native mode requires fastProvider + fastModel. The optional deep stage requires deepProvider + deepModel and may use another provider even when the primary follows the conversation. Leaving deep empty disables it, preventing an implicit second expensive call.
20
-
21
- Explicit HTTP endpoint + fastModel retains RC2 behavior. Native provider fields with an HTTP endpoint are invalid. Partial native pairs fail load validation. Missing routes/adapters or failed calls ask/deny; they never switch to another provider, another session or an implicit HTTP route.
22
-
23
- Only route identifiers are inherited. Reviewer calls use a new prompt and one identity-free input, and carry no tools, main transcript, request replay state, parent sessionId, reasoning setting or output budget. Native adapters own credentials. The reviewer retains its own caps; adapter defaults can still affect reasoning. Incompatibility fails closed, and users can independently choose a suitable review model/cap.
24
-
25
- Snapshot fast/deep routes once per action. Compare again after review and in the final preflight guard/approval completion. A changed model or removed native service cannot grant from stale review; explicit model denials remain final. The optional llm dependency is a child injection so its disappearance does not unload root safety guards. Model changes never reset task/session budgets.
26
-
27
- The plugin adds no retry. Native host middleware/provider transport retries remain host policy, not additional grants or precisely metered wire attempts. Reservation units still bound logical review stages and payloads rather than guaranteeing billing. Existing `allowed-once`, authority, hard-risk, context isolation, cancellation and enrollment restrictions remain unchanged.
28
-
29
- ## Configuration surface and scope
30
-
31
- RC3 exposes model selection through the existing validated profile Config. There is no graphical model picker or volatile settings form in this change. Config edits require reload. Following a newly accepted conversation model does not require plugin reload. The model-selection UI and live-profile/provider acceptance are not claimed complete.
32
-
33
- Default enrollment remains empty and mode remains shadow. However, a profile that already enrolled ordinary commands without an endpoint can now review them with the conversation model, including in shadow mode. Document this behavior change and its potential model usage. Shadow escalation remains manual and does not call the reviewer.
34
-
35
- ## Tests
36
-
37
- Route validation/defaults; accepted header priority; options fallback; malformed-header refusal; fixed/deep/HTTP isolation; concurrent sessions; stream protocol and byte caps; tools/invalid finish refusal; reasoning usage; timeout/cancellation; provider removal; stale decision and final-guard recheck; model-switch budget retention; native route in one-shot escalation; and semantic denial across a model change.
38
-
39
- The native contract loads the workspace's actual Cordis, ToolRuntime and LlmRuntime, registers a controlled LlmAdapter, exercises two accepted conversation models and a distinct fixed reviewer, then removes the model service and proves the root gate remains. This is not a live-provider, OS sandbox or authenticated Web test. No user credentials or paid model calls are used by tests.
package/docs/ADR-0004.md DELETED
@@ -1,33 +0,0 @@
1
- # Semantic authorization and bounded execution evidence
2
-
3
- Status: implemented for `0.2.0-rc.2`; its no-human-fallback decision is superseded by [ADR-0005](ADR-0005.md). Live-model accuracy and installed-profile acceptance remain separate from automated contracts. Sources checked on 2026-10-05.
4
-
5
- ## Reference and decision
6
-
7
- Codex separates the exact proposed action, retained human authorization and untrusted implementation evidence. Its [review policy template](https://github.com/openai/codex/blob/main/codex-rs/prompts/templates/guardian/policy_template.md) judges authorization by material semantics and recognizes necessary implementation steps. Its [prompt builder](https://github.com/openai/codex/blob/main/codex-rs/core/src/guardian/prompt.rs) supplies a compact transcript and the exact request. [Auto-review documentation](https://learn.chatgpt.com/docs/sandboxing/auto-review) describes read-only investigation and distinguishes timeout from an unsafe action.
8
-
9
- Adopt those principles inside the existing single-call gate. No new approval service, persistent authority store, general shell interpreter, reviewer tool execution or command whitelist is introduced. Safe Auto retains a stricter floor than Codex's default: at least medium direct authorization, bounded effects, and only low/medium risk qualify for automatic permission.
10
-
11
- ## Authority
12
-
13
- Collect direct-human text in chronological order after excluding child sessions by durable header metadata. Retain earlier authorization across follow-ups and apply later restrictions and revocations. Assistant messages, tool results, plugin feedback and quoted text do not grant permission. Bind the full selected authority to the live execution and recheck it after awaits.
14
-
15
- Retain all human text within 12 KiB; do not preserve an old grant by dropping an intervening restriction. Sensitive or nontext messages and overflow mark authority incomplete and prevent review. This intentionally requires a fresh, fully scoped conversation rather than a model-generated summary that could change authority. Only retained session events are available; the plugin cannot recover instructions removed by host compaction.
16
-
17
- ## Evidence
18
-
19
- Use the pinned host filesystem's `resolve`, `contains`, `stat`, `readBytes` and `processPath` APIs. Read package manifests for the effective working directory and workspace root, plus directly referenced local code and file references in relevant package scripts/hooks. The hint scanner only locates evidence; it does not prove shell semantics or authorize execution. Maintained POSIX `shell-quote` and argument-string packages were considered, but they do not supply PowerShell semantics and add a dependency without improving this bounded locator.
20
-
21
- Resolve canonical targets inside the canonical workspace root; exclude protected paths before and after resolution. Cap at eight files, 6 KiB per file and 12 KiB overall. Decode strict UTF-8 and withhold other encodings and control-byte content. Screen known secret patterns, including quoted and decoded JSON keys, before sending evidence. This detector is not a guarantee against arbitrary secret encodings. Mark unavailable, excessive and withheld evidence explicitly. Do not recursively inspect imports, dependencies or every workspace package.
22
-
23
- Record canonical paths, opaque filesystem versions and SHA-256 content digests, including absent manifests. Re-resolve and reread before returning `allowed-once`; changes invalidate the review. This closes stale-evidence grants during review, not the race between approval and actual execution.
24
-
25
- ## Failure and feedback
26
-
27
- Explicit model denial remains `rejected`; uncertainty, timeout, malformed response, missing reviewer and exhausted budget return native `unavailable`. Neither delegates or grants permission. Only semantic denials increment the denial streak. All attempted model calls keep their reservation; non-denials reset the streak.
28
-
29
- The `tools/execute` wrapper adds bounded classification and recovery feedback to failed result content. The real ToolRuntime contract verifies that feedback survives normalization. The `tools/result` event remains a read-only observer. Model rationale is labeled untrusted and never copied to logs; secret-bearing rationale is withheld.
30
-
31
- DSH 0.1.7-rc.2 approval and user-question services both dispatch arbitrary composed waterfall answerers. Neither proves human provenance. A direct human fallback therefore remains unsupported; uncertainty is not delegated through either chain. No fake human channel is added.
32
-
33
- The default combined prompt/input budget increases from 8 KiB to 32 KiB. Existing explicit profile limits remain valid. Reviewer route, effort, single-call execution identity, cancellation, lifetime, native `never`, settings revocation and protected-file controls remain enforced.
package/docs/ADR-0005.md DELETED
@@ -1,31 +0,0 @@
1
- # Timed human confirmation
2
-
3
- Status: implemented in the unpublished `0.2.0-rc.2` work, following explicit user approval on 2026-10-05. Supersedes ADR-0004's no-human-fallback limitation.
4
-
5
- ## Behavior
6
-
7
- Keep automatic model passes. For a structurally eligible call, a model refusal, uncertainty, timeout, unavailable reviewer or exhausted model budget offers a manual confirmation. Model risk classification remains an opinion; the user can explicitly approve that exact action. Native `never`, deterministic hard refusals, unsupported/unbound execution, changed evidence, audit failure and cancellation remain terminal.
8
-
9
- `AUTHORITY_CONTEXT_INCOMPLETE` blocks automatic review but may receive fresh one-call UI authorization. Rerun every action/host/schema/secret check with only that historical-input flag waived; keep the original live binding. Incomplete private history is never sent to the reviewer or dialog. No other structural refusal is waived.
10
-
11
- Default `humanApprovalTimeoutMs` is 60000, operator-owned and separate from the model deadline; zero disables takeover. Publish the exact action, permission change and bounded reviewer opinion to a runtime-local queue. Start the server-monotonic deadline when publishing. Only the first session-bound answer consumes the unpredictable request ID. No answer before expiry means rejection; late/duplicate answers never grant.
12
-
13
- The existing authenticated `/dsh-safe-auto` browser route owns `approval.list` and `approval.answer`. It bypasses the settings serializer so waiting never blocks approval or revocation. Direct browser JSON requests require fetch mode (`cors` or `same-origin`), destination (`empty`) and same-origin/same-site metadata. The DSH 0.2.0-rc.2 desktop carrier checks the application origin itself, removes Origin and Sec-Fetch-Site, and forwards with its Host-issued cookie. Installed Electron 44 protocol requests omit Fetch Metadata; the carrier's Node fetch adds `mode=cors` but no destination. Missing destination is admitted only with that mode and both Origin and site absent, after Host authentication and source checks. Explicitly invalid metadata remains rejected. No automatic waterfall answerer is consulted and no model tool is registered for answering.
14
-
15
- ## Lifetime and integrity
16
-
17
- Existing execution identity, complete arguments, human-context and control revision bindings remain live throughout the wait. Cancellation, settings changes, permission changes, session replacement, detach and runtime disposal invalidate requests. The queue has a fixed 32-request cap, is not persisted, and clears timers/listeners when each request settles.
18
-
19
- Approval claims the queue entry synchronously before file checks. Reverify original captured evidence using a fresh bounded signal combined with execution/runtime cancellation. Never recollect and silently replace old evidence. Check deadline and live binding immediately before the grant. The 30-second model lease applies only to model passes; it does not expire an otherwise valid 60-second human window. A successful manual grant resets the semantic denial streak but does not refill model-call budgets.
20
-
21
- ## UI
22
-
23
- The composer permission component polls serially while Safe Auto is enabled, including before a request exists. An official body-portaled Modal shows exact plain-text shell commands or complete write/edit effects, cwd, permission change, model opinion and countdown. It remains usable while the agent locks the composer. Reject receives initial focus; closing via Escape, mask or close button rejects. Approve says “only this call.” Writes are never retried automatically; uncertain transport results trigger a fresh read. Session changes/unmount discard stale responses.
24
-
25
- The browser countdown derives from server `remainingMs` and a local monotonic clock, with network time subtracted. It cannot extend the server deadline.
26
-
27
- ## Trust and limitations
28
-
29
- Authentication, origin checks, Fetch Metadata and request IDs protect the browser channel against accidental and cross-site answers. They do not prove physical human action. Authenticated clients/carriers and same-process plugins remain trusted; an actor able to steal client authentication or forge a trusted client can forge an answer. The plugin does not claim confinement against that actor. Transports missing fetch mode cannot use manual confirmation; pending requests then expire. Missing site metadata is accepted only when Origin is also absent; missing destination additionally requires `mode=cors`, matching the authenticated desktop carrier. Headless execution with no UI controls retains the non-grant path.
30
-
31
- Evidence remains partial and review-to-execution file races remain possible. No new persistent storage, credential access, service, framework or dependency is introduced. Local disable/unload remains the rollback.
package/docs/ADR-0006.md DELETED
@@ -1,58 +0,0 @@
1
- # Static shell proof: read-only fast path and AST-level hard rules
2
-
3
- Status: implemented in the unpublished `0.2.0-rc.3` work, following explicit user approval on 2026-10-06. Complements ADR-0001 (0.1.x preflight removal) and ADR-0002 (execution-bound review).
4
-
5
- ## Context
6
-
7
- 0.1.x carried a static layer (ordinary-command review, workspace/shell candidates, `shadow`/`smart`/`unattended` modes). ADR-0001 removed it because it was a fuzzy candidate-rule system: enrolled strings pretended to be policy, and three modes made the effective boundary unreadable. Two real problems remained after the removal, however:
8
-
9
- - The hard-rule layer recognized dangerous programs only as the first word of a bare, whitespace-clean command. `ls | rm x`, `sudo rm x` or `time rm x` in a compound command bypassed the deterministic layer entirely and leaned on the model to notice.
10
- - Every escalation, including a provably read-only `git status`, paid one reviewer call of latency and budget.
11
-
12
- Two sibling harnesses informed the redesign. ZCode's build/edit modes downgrade provably read-only Bash invocations through a strict AST walk plus per-command policies, and its reserved `auto` mode (never implemented, fail-closed `mode.auto.unimplemented`) shows the intended shape of "low risk goes automatic." Codex's execpolicy separates a strict fully-literal parse (allowed to prove safety) from lossy heuristics (allowed only to flag danger), aggregates compound commands with every-semantics, and fails closed when parsing cannot explain the whole command.
13
-
14
- ## Decision
15
-
16
- Parse shell commands with `unbash` (Apache-2.0, zero-dependency bash parser, runtime dependency) in a new `src/shell-command.js`. The analysis accepts only a fully literal command: Statement/AndOr/Pipeline/Command/Time nodes, no backgrounding, no heredoc bodies, and every word, assignment value and redirect target free of expansions, globs and tilde prefixes. Anything else returns `ok: false` and changes nothing about the existing pipeline.
17
-
18
- **Static fast path.** When the structural assessment reaches `review`, a fully literal bash escalation is granted `allowed-once` with `STATIC_READONLY` only if every invocation satisfies both its complete no-mutation grammar and the separate `isSafeStaticExposure` metadata/literal-output check. Exact case-sensitive POSIX executable names select policies. `rootName` normalization (paths, case and Windows suffixes) is only for danger detection. Registered options have exact arity and value validators; each operation checks input/output operand counts and incompatible modes. Unsupported syntax falls back to the existing reviewer/manual channel, not a hard denial.
19
-
20
- `ls -la`, `pwd` and `find . -name "*.md"` retain the fast path. File-content readers, transformations, recursive content searches, process arguments and environment enumeration require review: no mutation does not imply no credential exposure. `printenv` is excluded. `rg --pre`, archive/helper modes, output operands, executable pagers and mixed Git read/write operations also fail the no-mutation grammar. `date --file` cannot pass the exposure check because parse errors may repeat source content. Bash `printf -v` and `%n` cannot pass because they assign shell variables.
21
-
22
- Environment prefixes are checked before handling empty argv. Only explicit bounded values for CI, locale, color, terminal dimensions and UTC/GMT timezone settings are allowed, without duplicate names. Assignment-only statements do not qualify, including safe-looking assignments, because they persist into later compound invocations. Thus `PATH=.; ls` and `PAGER=node; git log` cannot alter subsequent resolution inside a supposedly read-only compound. PAGER, GIT_PAGER, Git configuration/transport and dynamic-loader environment prefixes are excluded.
23
-
24
- **Git proof scope.** A read-like subcommand is insufficient. Worktree status/diff can invoke configured clean/process filters. The no-mutation grammar retains explicit controls for object/index commands, but content, history, remote URLs and configuration enumeration fail the separate exposure check and continue to review. Metadata listings retain their original disabling controls. Exact version calls and `git --no-pager config --get user.name` / `user.email` are narrow static cases. Controls are recognized in unchanged argv; nothing is prepended or rewritten.
25
-
26
- `pwsh` always uses review because its grammar differs. The static grant keeps the complete execution binding, one claim, cancellation, control epoch, audit and recheck-before-grant machinery. It consumes no model review budget and touches no denial streak. Profile key `staticReadonly` (default `true`, operator-owned, not UI-editable) disables the fast path.
27
-
28
- **Native approval ownership.** In pinned DSH 0.2.0-rc.2, ApprovalService applies native policy and cancellation, then dispatches `approval/request` through a waterfall. A genuine foreign request (Blueprint order or another plugin's model approval) without a Safe Auto execution store calls `next()`. Any existing owned store stays fail-closed on mismatch, inactivity, staleness or a prior claim, and absent-store requests impersonating owned bash/pwsh/write/edit tools reject. Owned human takeover continues only through the authenticated plugin queue, never a downstream automatic answerer.
29
-
30
- **AST-level hard rules.** A refusal-only AST walk checks literal program roots (basename, Windows extensions stripped, `command`/`builtin`/`nohup`/`time` wrappers unwrapped over the known prefix, depth-capped) against the dangerous-root table and denies `DANGEROUS_PROGRAM`. Unknown later arguments do not soften a known dangerous root. Unparseable commands keep the legacy bare-command check. Unexplained expansions still require review; partial decoding never grants permission. Hard refusals stay unsoftenable by model or human.
31
-
32
- Hard protected-path/secret checks consume decoded literal argv, assignment and redirect values, including quote concatenation and option/object-path values. A refusal-only AST walk retains known literals even when a different dynamic fragment or control-flow node prevents a whole-command proof. Its 32768-character parse bound covers the maximum 32768-byte reviewable argument budget; static proof retains its stricter 10000-character bound. These hard refusals apply to every approval route and cannot publish a reviewer/manual override. Quote context is preserved in proof: unquoted glob/brace fragments invalidate it, while fully quoted filename patterns remain literal. File-opening `/dev/tcp` or `/dev/udp` redirections cannot prove read-only because Bash implements them as sockets, including quoted and numbered-fd input redirects. They fall back to review; ordinary file input, fd merges and `/dev/null` output keep their rules.
33
-
34
- `git ls-files -m`/`--modified` is also excluded: Git's `show_files` calls `ie_modified` and content conversion for that mode. The admitted cached/stage listing does not inspect worktree content; deleted/other listings only stat or enumerate with the same fixed controls. EOL, custom-format and submodule-recursion modes remain unsupported.
35
-
36
- ## Relationship to ADR-0001
37
-
38
- This is not the return of candidate rules. Nothing is enrolled, persisted, prefixed or remembered: every call is re-proven from its exact bound arguments, and the safety proof requires a fully literal command. Refusal-only partial decoding may recognize a known hazard, never establish safety. Unprovable commands flow into the unchanged reviewer path with the same budgets, and the human confirmation channel of ADR-0005 is untouched. The 0.1.x modes and profile keys remain retired and still fail validation.
39
-
40
- ## Trust and limitations
41
-
42
- The accepted trust boundary remains operator-trusted PATH resolution and installed runtimes. This is a proof over unchanged command text, not binary identity, shell-function integrity or runtime attestation. Unknown commands cost review; known dedicated credential targets remain hard refusals. Parser or grammar defects may produce incorrect proofs; strict parsing and positive rules reduce that risk but do not establish an absolute guarantee.
43
-
44
- ## Credential reads (2026-10-13 correction)
45
-
46
- Read protection follows dedicated credential stores and recognized values, not whole agent directories. Write protection retains the prior stricter configuration/instruction boundary. Native `read` runs its own validation, caps and observation logic; the around-execution layer replaces successful structured values before post-execute, rendering and persistence. Complete-source parsers preserve original character positions, allowing the original offset, line and byte windows to remain exact. Native grep results are similarly projected before its formatted-result spill callback. Secret-bearing `str_replace_editor view` calls direct callers to `read`.
47
-
48
- The verified native read body retains raw content only in memory before returning; its `fs/observed` event contains kind/version, not content. The wrapper returns a fresh minimal result, without copying raw content or metadata. The mounted provider resolves canonical targets and source versions. Complete structured sources are bounded at 1 MiB; large JSONL uses complete selected lines, with per-line/window caps. Errors and incoherent windows withhold content. The implementation uses `jsonc-parser`, `yaml` and `toml-eslint-parser` for grammar/range handling, not a custom configuration parser.
49
-
50
- Shell jobs may retain raw output before around-execution returns. Therefore known credential or secret-bearing absolute file inputs are refused before execution; no post-output secrecy guarantee is claimed. Relative and opaque shell programs are not proven safe by the new static path. Arbitrary Full Access scripts, custom/remote readers and opaque secrets remain outside this recognizer's guarantee. The README documents supported formats and size limits.
51
-
52
- The fast path skips execution evidence collection and the model assessment row. Its approval audit row carries `STATIC_READONLY` with `static` as the source. Local `staticReadonly: false`, disable or unload remains the rollback. Tests challenge helper/value/cluster alternatives, output operands, persistent assignments, executable identity and Git configuration effects, then exercise the actual adapter fallback and pinned Cordis/ToolRuntime/ApprovalService waterfall. These are source and fixture-runtime checks, not live OS confinement or real-user acceptance.
53
-
54
- ## Source basis
55
-
56
- - Read-only ZCode mirror: `apps/zcode-cli/packages/core/src/tool/handlers/bash-readonly-policy-argv-git.ts` and `tool/executor/approval-gate.ts`; design reference only, no source copied.
57
- - Pinned DSH 0.2.0-rc.2: `@deepseek-ai/dsh-user-approval/lib/index.js` (`ApprovalService.request`/`decide`) and `@deepseek-ai/dsh-sandbox` native `approveEscalation`; the native regression runs these installed packages.
58
- - [Git command controls](https://git-scm.com/docs/git), [diff controls](https://git-scm.com/docs/git-diff), and upstream source [`diff.c`](https://github.com/git/git/blob/master/diff.c), [`read-cache.c`](https://github.com/git/git/blob/master/read-cache.c), [`convert.c`](https://github.com/git/git/blob/master/convert.c), [`builtin/diff.c`](https://github.com/git/git/blob/master/builtin/diff.c), [`builtin/ls-files.c` at v2.46.0](https://github.com/git/git/blob/v2.46.0/builtin/ls-files.c): worktree conversions apply clean filters, while cached diff reads index/object content. The grammar remains a conservative supported subset, not universal Git safety.