@x1a0f3n9/dsh-sandbox-policy 0.1.5-alpha.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/sandbox/sandbox-policy/README.md
5
+ README.md: c29908937eb2b2cdae4ced5224d441d8fef3df34
6
+ README.zh.md: 2285360ea9c7f7e18b367e02636eb71601f475c1
package/README.md ADDED
@@ -0,0 +1,159 @@
1
+ ---
2
+ description: "The shared per-call sandbox policy resolver and current model context for users and maintainers composing, configuring, or debugging file-effect policy across enforcing capabilities."
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @x1a0f3n9/dsh-sandbox-policy
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ Use this package to apply one file-effect policy to every confined bash, filesystem, and terminal call. Deployments choose a default mode and fallback workspace root, while each session can switch modes independently. Session choices survive restart, and all enforcing capabilities use the same mode and workspace for a call. Before each model request, the model receives the effective policy and workspace without an inventory of mounted capabilities.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
27
+
28
+ Mount this package in any composition where sandbox-enforcing capabilities run: it owns the deployment default and the per-session overrides those capabilities consume, and it contributes the current policy to the model's runtime-context snapshot.
29
+
30
+ ### When to choose it
31
+
32
+ Choose it for every composition with confined capabilities (bash, filesystem, terminal) so one policy home keeps them from drifting into different modes or workspace roots. Skip it only when nothing enforces sandbox policy — with no consumers, the resolved policy has no effect.
33
+
34
+ ### Minimal configuration
35
+
36
+ Load the package with a default mode; the fail-safe default is `read-only`, and a deployment that wants a workspace-writable agent opts into `workspace-write` explicitly.
37
+
38
+ ```yaml
39
+ - name: '@x1a0f3n9/dsh-sandbox-policy'
40
+ config:
41
+ mode: workspace-write
42
+ workspaceRoot: /absolute/path/to/workspace
43
+ ```
44
+
45
+ | Field | Default | Meaning |
46
+ |---|---|---|
47
+ | `mode` | `read-only` | The deployment default mode a session starts from, validated at load |
48
+ | `workspaceRoot` | `process.cwd()` | The fallback root `workspace-write` may write under for agentless calls or sessions without a cwd; normal agent calls use the session's immutable cwd instead |
49
+
50
+ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-sandbox-policy) is the exhaustive source for every accepted field and its JSDoc.
51
+
52
+ ### Switching a session's mode
53
+
54
+ A session's mode can be switched at runtime through a UI policy control or an explicit switch; the switch is recorded in the session log and takes effect on the session's next confined call. The switch survives restart through replay, and each session keeps its own mode — two sessions never see each other's state. A switched session keeps its immutable workspace cwd as the writable boundary.
55
+
56
+ ### Failures and recovery
57
+
58
+ An invalid configured mode is rejected when the plugin loads, so a typo fails loud instead of silently changing policy. A session without a cwd, and agentless calls, fall back to the configured workspace root; a call with an approved explicit mode uses that mode for exactly that call.
59
+
60
+ -----
61
+
62
+ <a id="understand-the-implementation"></a>
63
+ ## Understand the implementation
64
+
65
+ <details>
66
+ <summary>Implementation internals — click to expand</summary>
67
+
68
+ This section explains policy resolution, the per-session store, and the model-visible contribution; the observable behavior is fully covered in [Use this package](#use-this-package).
69
+
70
+ ### Resolution precedence
71
+
72
+ `resolve({ session, mode })` returns one complete per-call policy: an approved explicit mode outranks the session's last `sandbox/mode` event, which outranks the deployment default. The session's immutable `cwd` is canonicalized with filesystem semantics before becoming the workspace root, so `symlink/..` agrees with process working-directory resolution; otherwise the configured fallback applies.
73
+
74
+ ### The per-session store
75
+
76
+ A runtime switch is one log-only `sandbox/mode` event on the session it applies to — the switch IS its event, and nothing mutates mode state out of band. `effective = explicit grant ?? fold(events) ?? deployment default`, so an override survives restart by replay and two sessions never see each other's state. Workspace identity needs no event: the immutable `SessionHeader.cwd` recorded at creation is the root for every call in that session. The event stays log-only; before each request, the owner contributes the current fact to the full runtime-context snapshot, and the agent loop logs that snapshot as a sourced `user/message`.
77
+
78
+ ### Model-visible text
79
+
80
+ The `sandbox:policy` contribution states the mode's capability-neutral file-effect contract and the canonical session workspace under `workspace-write`. It does not enumerate mounted capabilities; tool plugins retain operation-specific denial and escalation guidance, approval policy contributes separately to the same snapshot, and plan guidance remains `dsh-plan-mode`'s system section. The optional `./invariant` companion rejects a forged durable `sandbox/mode` event whose value falls outside the closed mode vocabulary.
81
+
82
+ ### Source map
83
+
84
+ | File | Role |
85
+ |---|---|
86
+ | [`src/index.ts`](src/index.ts) | Plugin entry: `SandboxPolicyService`, `Config` schema, policy resolution and context contribution |
87
+ | [`src/session-mode.ts`](src/session-mode.ts) | The `sandbox/mode` event, its fold, and the write path |
88
+ | [`src/invariant.ts`](src/invariant.ts) | Invariant companion: rejects `sandbox/mode` values outside the closed vocabulary |
89
+
90
+ </details>
91
+
92
+ -----
93
+
94
+ <a id="further-exploration"></a>
95
+ ## Further Exploration
96
+
97
+ Start with the subsystem reference for the shared vocabulary, then the seam contract and the cross-family decision.
98
+
99
+ - [Process sandbox subsystem](../../../docs/subsystems/sandbox.md) — modes, per-call policy, and enforcement semantics.
100
+ - [Sandbox seam package](../sandbox/README.md) — the confinement contract every enforcing capability implements.
101
+ - [Cross-family file sandbox decision](../../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md) — why one shared policy home exists.
102
+
103
+ -----
104
+
105
+ <a id="model-experience"></a>
106
+ ## Model Experience
107
+
108
+ ### Current file sandbox policy
109
+
110
+ #### What the model sees
111
+
112
+ One `sandbox:policy` contribution in the current runtime-context snapshot for every agent session. It does not enumerate mounted capabilities. Tool plugins retain operation and escalation guidance, approval policy contributes separately to the same snapshot, and plan guidance remains `dsh-plan-mode`'s system section.
113
+
114
+ ##### Read-only
115
+
116
+ ```markdown
117
+ Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.
118
+ ```
119
+
120
+ ##### Workspace-write
121
+
122
+ ```markdown
123
+ Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: "<workspace root>". Some platform temporary areas may also be writable.
124
+ ```
125
+
126
+ ##### Danger-full-access
127
+
128
+ ```markdown
129
+ Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.
130
+ ```
131
+
132
+ #### Token effect
133
+
134
+ One concise durable context message on the first request and each effective policy change; unchanged requests add nothing. `workspace-write` carries only the canonical session workspace path; platform-specific temporary paths are summarized without adding host-dependent bytes.
135
+
136
+ #### KV Cache effect
137
+
138
+ The stable system prompt remains byte-identical across mode changes. A changed full context snapshot is appended after retained history, preserving the prior cached prefix; subsequent unchanged requests reuse that retained snapshot.
139
+
140
+ ## Known Limitations and Deferred Work
141
+
142
+ <a id="known-limitations-and-deferred-work"></a>
143
+
144
+
145
+ These limits define the policy surface this package provides. They are current package constraints, not a general sandbox comparison or a task backlog.
146
+
147
+ - **One primary workspace root per session** — policy resolves `SessionHeader.cwd`; extra writable roots are not part of `SandboxExecutionPolicy`.
148
+ - **File-effect modes only** — `SandboxMode` governs file effects; network and process policy are outside its vocabulary, so no knob here restricts them.
149
+ - **Temporary areas are deliberately summarized** — enforcing backends grant different platform temporary areas, which are selected after policy resolution and therefore cannot be enumerated truthfully in the current context.
150
+
151
+ <a id="dev-note"></a>
152
+ ### Dev Note
153
+
154
+ <details>
155
+ <summary>Working context for maintainers — click to expand</summary>
156
+
157
+ None.
158
+
159
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,159 @@
1
+ ---
2
+ description: "面向组合、配置或排查跨执行能力文件效果策略的用户与维护者的共享逐调用沙箱策略解析器与当前模型上下文。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @x1a0f3n9/dsh-sandbox-policy
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ 使用本包可以让每次受限的 bash、文件系统和终端调用遵循同一份文件操作策略。部署方选择默认模式和回退工作区根目录,每个会话则可以独立切换模式。会话选择可跨重启保留,所有强制执行能力在一次调用中使用相同的模式和工作区。每次模型请求前,模型都会收到有效策略和工作区说明,但不会收到已挂载能力的清单。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
27
+
28
+ 在任何运行沙箱强制执行能力的组合中挂载此包:它拥有这些能力消费的部署默认值与逐会话覆盖,并把当前策略贡献给模型的运行时上下文快照。
29
+
30
+ ### 何时选择
31
+
32
+ 为每个带受限能力(bash、文件系统、终端)的组合选择它,让单一策略归属位置防止它们漂移到不同的模式或工作区根目录。只有没有任何沙箱策略强制执行时才跳过它——没有消费方时,解析出的策略不起作用。
33
+
34
+ ### 最小配置
35
+
36
+ 用默认模式加载本包;故障安全默认值是 `read-only`,想要可写工作区 agent 的部署需要显式选择 `workspace-write`。
37
+
38
+ ```yaml
39
+ - name: '@x1a0f3n9/dsh-sandbox-policy'
40
+ config:
41
+ mode: workspace-write
42
+ workspaceRoot: /absolute/path/to/workspace
43
+ ```
44
+
45
+ | 字段 | 默认值 | 含义 |
46
+ |---|---|---|
47
+ | `mode` | `read-only` | 会话起始的部署默认模式,加载时验证 |
48
+ | `workspaceRoot` | `process.cwd()` | 无 agent(智能体)调用或没有 cwd 的会话在 `workspace-write` 下可写入的回退根目录;普通 agent 调用改用会话的不可变 cwd |
49
+
50
+ 生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-sandbox-policy)是每个受支持字段及其 JSDoc 的穷尽式真源。
51
+
52
+ ### 切换会话模式
53
+
54
+ 会话的模式可以在运行时通过 UI 策略控件或显式切换来更改;切换记录在会话日志中,并在该会话的下一次受限调用时生效。切换通过回放跨重启保留,每个会话保持自己的模式——两个会话绝不会看到彼此状态。切换后的会话继续以不可变的工作区 cwd 作为写入边界。
55
+
56
+ ### 失败与恢复
57
+
58
+ 无效的配置模式会在插件加载时被拒绝,因此拼写错误会大声失败,而不是静默改变策略。没有 cwd 的会话与无 agent 调用回退到配置的工作区根目录;带已批准显式模式的调用只在该次调用中使用该模式。
59
+
60
+ -----
61
+
62
+ <a id="understand-the-implementation"></a>
63
+ ## 理解实现
64
+
65
+ <details>
66
+ <summary>实现细节——点击展开</summary>
67
+
68
+ 本节解释策略解析、逐会话存储与模型可见贡献;可观察行为已在[使用本包](#use-this-package)中完整说明。
69
+
70
+ ### 解析优先级
71
+
72
+ `resolve({ session, mode })` 返回一份完整的逐调用策略:已批准的显式模式优先于会话最后一条 `sandbox/mode` 事件,后者又优先于部署默认值。会话的不可变 `cwd` 先按文件系统语义规范化,再成为工作区根目录,因此 `symlink/..` 与进程工作目录解析一致;否则使用配置的回退值。
73
+
74
+ ### 逐会话存储
75
+
76
+ 运行时切换是在对应会话日志中追加的一条仅记录 `sandbox/mode` 事件——切换本身就是事件,任何机制都不会在带外修改模式状态。`effective = explicit grant ?? fold(events) ?? deployment default`,因此覆盖通过回放跨重启保留,两个会话也绝不会看到彼此状态。工作区标识无需事件:创建时记录的不可变 `SessionHeader.cwd` 是该会话每次调用使用的根。事件仍只进入日志;在每次请求前,归属方会把当前事实贡献给完整运行时上下文快照,agent loop(智能体循环)将该快照记录为一条带来源的 `user/message`。
77
+
78
+ ### 模型可见文本
79
+
80
+ `sandbox:policy` 贡献说明模式的与具体能力无关的文件操作约定,以及 `workspace-write` 下规范化的会话工作区。它不枚举已挂载能力;工具插件保留特定于操作的拒绝与升权引导,批准策略单独贡献给同一份快照,计划引导仍由 `dsh-plan-mode` 的系统段落管理。可选的 `./invariant` 配套组件会拒绝值超出封闭模式词汇的伪造持久 `sandbox/mode` 事件。
81
+
82
+ ### 源码地图
83
+
84
+ | 文件 | 职责 |
85
+ |---|---|
86
+ | [`src/index.ts`](src/index.ts) | 插件入口:`SandboxPolicyService`、`Config` schema、策略解析与上下文贡献 |
87
+ | [`src/session-mode.ts`](src/session-mode.ts) | `sandbox/mode` 事件、其 fold 与写入路径 |
88
+ | [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件:拒绝超出封闭词汇的 `sandbox/mode` 值 |
89
+
90
+ </details>
91
+
92
+ -----
93
+
94
+ <a id="further-exploration"></a>
95
+ ## 进一步探索
96
+
97
+ 先从子系统参考文档了解共享词汇,再看 seam 约定与跨家族决策。
98
+
99
+ - [进程沙箱子系统](../../../docs/subsystems/sandbox.zh.md)——模式、逐调用策略与强制执行语义。
100
+ - [沙箱 seam 包](../sandbox/README.zh.md)——每个强制执行能力实现的隔离约定。
101
+ - [跨家族文件沙箱决策](../../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.zh.md)——为何存在统一的共享策略归属位置。
102
+
103
+ -----
104
+
105
+ <a id="model-experience"></a>
106
+ ## 模型体验
107
+
108
+ ### 当前文件沙箱策略
109
+
110
+ #### 模型看到什么
111
+
112
+ 每个 agent 会话的当前运行时上下文快照中都有一项 `sandbox:policy` 贡献。它不枚举已挂载的能力。工具插件继续负责操作与升权引导,批准策略单独贡献给同一份快照,计划引导仍由 `dsh-plan-mode` 的系统段落管理。
113
+
114
+ ##### 只读
115
+
116
+ ```markdown
117
+ Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.
118
+ ```
119
+
120
+ ##### 工作区写入
121
+
122
+ ```markdown
123
+ Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: "<workspace root>". Some platform temporary areas may also be writable.
124
+ ```
125
+
126
+ ##### 完全访问
127
+
128
+ ```markdown
129
+ Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.
130
+ ```
131
+
132
+ #### Token 影响
133
+
134
+ 首次请求和有效策略每次变化时增加一条简洁的持久上下文消息;未变化的请求不增加内容。`workspace-write` 只携带规范化的会话工作区路径;平台特定的临时路径会以摘要表述,不会加入依赖主机的字节。
135
+
136
+ #### KV Cache 影响
137
+
138
+ 模式切换时,稳定的系统提示词仍逐字节相同。变化后的完整上下文快照会追加到保留的历史之后,从而保留此前已缓存的前缀;后续未变化的请求会复用该保留快照。
139
+
140
+ ## 已知限制与延期工作
141
+
142
+ <a id="known-limitations-and-deferred-work"></a>
143
+
144
+
145
+ 这些限制定义了本包提供的策略表面。它们是当前包约束,不是通用沙箱对比或任务积压。
146
+
147
+ - **每个会话只有一个主要工作区根目录**——策略解析 `SessionHeader.cwd`;额外可写根目录不属于 `SandboxExecutionPolicy`。
148
+ - **仅限文件操作模式**——`SandboxMode` 管控文件操作;网络和进程策略不在其词汇中,因此这里没有限制它们的旋钮。
149
+ - **有意概述临时区域**——强制执行后端会授予不同的平台临时区域,这些区域在策略解析后才会选定,因此无法在当前上下文中如实枚举。
150
+
151
+ <a id="dev-note"></a>
152
+ ### 开发备注
153
+
154
+ <details>
155
+ <summary>维护者的工作上下文——点击展开</summary>
156
+
157
+ 无。
158
+
159
+ </details>
package/lib/index.js ADDED
@@ -0,0 +1,159 @@
1
+ import { resolve } from "node:path";
2
+ import { Service } from "@deepseek-ai/cordis";
3
+ import { z } from "zod";
4
+ import z$1 from "@deepseek-ai/schemastery";
5
+ import { canonicalPath } from "@x1a0f3n9/dsh-sandbox";
6
+ //#region lib/types/session-mode.js
7
+ /**
8
+ * Per-session sandbox-mode override: the session log as the store. A runtime
9
+ * switch (a UI policy control or test scenario) is recorded as one
10
+ * `sandbox/mode` event on the session it applies to;
11
+ * `effective = projection state ?? the deployment default`, so an override
12
+ * survives restart by replay, two sessions can never see each other's state,
13
+ * and there is no external config store. The event is log-only (the
14
+ * `approval/*` precedent): the policy owner projects the fold into each model
15
+ * request, while enforcing tools report operation-specific boundary markers.
16
+ * EXECUTION honors the same fold through `ctx.sandboxPolicy.resolve()` — it
17
+ * stamps the mode together with the calling session's workspace root onto each
18
+ * capability call, weakest-precedence beneath an escalation grant.
19
+ *
20
+ * The override is policy state shared by every enforcing family (bash and
21
+ * filesystem alike), so it lives here in the policy package rather than in any
22
+ * one capability's seam.
23
+ *
24
+ * @module dsh-sandbox-policy/session-mode
25
+ */
26
+ /** Every {@link SandboxMode}, for option advertisement and runtime validation of untrusted mode strings. */
27
+ const SANDBOX_MODES = [
28
+ "read-only",
29
+ "workspace-write",
30
+ "danger-full-access"
31
+ ];
32
+ /**
33
+ * THE write path for a session's sandbox-mode override: appends exactly one
34
+ * `sandbox/mode` event — the switch IS its event; nothing mutates mode state
35
+ * out of band. Takes effect on the session's next confined call (bash or fs)
36
+ * — consumers read the shared projection state.
37
+ * @param session - the session the override belongs to.
38
+ * @param mode - the mode every subsequent confined call in this session runs
39
+ * under (until the next switch).
40
+ */
41
+ function setSandboxMode(session, mode) {
42
+ session.append("sandbox/mode", { mode });
43
+ }
44
+ //#endregion
45
+ //#region lib/types/index.js
46
+ /**
47
+ * The sandbox POLICY home (`ctx.sandboxPolicy`): the single owner of the
48
+ * deployment's sandbox fallbacks plus per-session resolution: the file-effect
49
+ * {@link SandboxMode}, the `workspace-write` root, and the override kit (the
50
+ * `sandbox/mode` event, its fold, and its write path; the fold is the
51
+ * `sandboxMode` session-projection unit registered here, while the event and
52
+ * write path come from `./session-mode.ts`).
53
+ * Before each agent request, the owner also contributes the resolved policy to
54
+ * the cache-safe runtime-context snapshot. The agent loop logs that snapshot as
55
+ * model history, so replay reconstructs the same mode and root the enforcing
56
+ * consumers resolve without rewriting the stable system prompt.
57
+ *
58
+ * Enforcing filesystem, one-shot bash, and terminal backends read the SAME
59
+ * resolved policy here. The context describes that policy without inventorying
60
+ * capabilities, while each backend retains its own enforcement dialect and each
61
+ * tool owns its operation-specific denial and escalation guidance. The service
62
+ * reads session state once at each operation boundary; executors and providers
63
+ * remain session-free.
64
+ *
65
+ * @module @x1a0f3n9/dsh-sandbox-policy
66
+ */
67
+ /** Resolve filesystem identity before lexical normalization can erase symlink-sensitive components. */
68
+ function resolveWorkspaceRoot(path) {
69
+ return resolve(canonicalPath(path));
70
+ }
71
+ /** Render the policy without claiming which capabilities are mounted. */
72
+ function renderPolicyContext(policy) {
73
+ switch (policy.mode) {
74
+ case "read-only": return "Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.";
75
+ case "workspace-write": return `Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: ${JSON.stringify(policy.workspaceRoot)}. Some platform temporary areas may also be writable.`;
76
+ case "danger-full-access": return "Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.";
77
+ /* v8 ignore next 4 -- SandboxMode is a typed same-process closed union; this branch is only the static exhaustiveness guard. */
78
+ default: {
79
+ const mode = policy.mode;
80
+ throw new Error(`unreachable sandbox mode: ${String(mode)}`);
81
+ }
82
+ }
83
+ }
84
+ /** The sandbox-mode projection's state schema (state equals the public shape). */
85
+ const sandboxModeStateSchema = z.union([
86
+ z.literal("read-only"),
87
+ z.literal("workspace-write"),
88
+ z.literal("danger-full-access")
89
+ ]).nullable();
90
+ /**
91
+ * The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment
92
+ * default mode, fallback workspace root, and current request-time policy
93
+ * section. Tool layers call {@link resolve} for each execution so a session's
94
+ * mode log and immutable cwd travel together to every enforcing capability.
95
+ */
96
+ var SandboxPolicyService = class extends Service {
97
+ static Config = z$1.object({
98
+ mode: z$1.union([
99
+ "read-only",
100
+ "workspace-write",
101
+ "danger-full-access"
102
+ ]).default("read-only"),
103
+ workspaceRoot: z$1.string()
104
+ });
105
+ static inject = ["sessionProjections"];
106
+ /** The deployment default mode — the fallback beneath a session override. */
107
+ defaultMode;
108
+ /** The absolute `workspace-write` fallback root for calls without a session cwd. */
109
+ workspaceRoot;
110
+ constructor(ctx, config) {
111
+ super(ctx, "sandboxPolicy");
112
+ this.defaultMode = config.mode;
113
+ this.workspaceRoot = resolveWorkspaceRoot(config.workspaceRoot ?? process.cwd());
114
+ ctx.sessionProjections.register({
115
+ key: "sandboxMode",
116
+ stateVersion: 1,
117
+ stateSchema: sandboxModeStateSchema,
118
+ init: () => null,
119
+ apply: (state, event) => event.type === "sandbox/mode" ? event.data.mode : state
120
+ });
121
+ ctx.inject(["systemPrompt"], (scope) => {
122
+ scope.systemPrompt.context({
123
+ name: "sandbox:policy",
124
+ order: scope.systemPrompt.getContextOrder("SANDBOX_POLICY"),
125
+ text: (context) => {
126
+ const session = context.agent?.session;
127
+ return session === void 0 ? "" : renderPolicyContext(this.resolve({ session }));
128
+ }
129
+ });
130
+ });
131
+ }
132
+ /**
133
+ * Resolve the complete policy for one capability call. An approved explicit
134
+ * mode outranks the session's last `sandbox/mode` event, which outranks the
135
+ * deployment default. A session cwd is its workspace-write boundary; the
136
+ * configured root is the fallback for agentless calls and sessions without a
137
+ * cwd.
138
+ * @param request - optional session and approved mode override.
139
+ * @returns the fully resolved per-call mode and absolute workspace root.
140
+ */
141
+ resolve(request = {}) {
142
+ const { session } = request;
143
+ return {
144
+ mode: request.mode ?? (session === void 0 ? void 0 : this.overrideOf(session)) ?? this.defaultMode,
145
+ workspaceRoot: resolveWorkspaceRoot(session?.header.cwd ?? this.workspaceRoot),
146
+ ...session === void 0 ? {} : { sessionId: session.id }
147
+ };
148
+ }
149
+ /**
150
+ * Read the session override without applying the deployment default.
151
+ * @param session - session whose log supplies the override.
152
+ * @returns the last logged mode, or `undefined` without one.
153
+ */
154
+ overrideOf(session) {
155
+ return this.ctx.sessionProjections.stateOf(session, "sandboxMode") ?? void 0;
156
+ }
157
+ };
158
+ //#endregion
159
+ export { SANDBOX_MODES, SandboxPolicyService, SandboxPolicyService as default, setSandboxMode };
@@ -0,0 +1,55 @@
1
+ //#region lib/types/session-mode.js
2
+ /**
3
+ * Per-session sandbox-mode override: the session log as the store. A runtime
4
+ * switch (a UI policy control or test scenario) is recorded as one
5
+ * `sandbox/mode` event on the session it applies to;
6
+ * `effective = projection state ?? the deployment default`, so an override
7
+ * survives restart by replay, two sessions can never see each other's state,
8
+ * and there is no external config store. The event is log-only (the
9
+ * `approval/*` precedent): the policy owner projects the fold into each model
10
+ * request, while enforcing tools report operation-specific boundary markers.
11
+ * EXECUTION honors the same fold through `ctx.sandboxPolicy.resolve()` — it
12
+ * stamps the mode together with the calling session's workspace root onto each
13
+ * capability call, weakest-precedence beneath an escalation grant.
14
+ *
15
+ * The override is policy state shared by every enforcing family (bash and
16
+ * filesystem alike), so it lives here in the policy package rather than in any
17
+ * one capability's seam.
18
+ *
19
+ * @module dsh-sandbox-policy/session-mode
20
+ */
21
+ /** Every {@link SandboxMode}, for option advertisement and runtime validation of untrusted mode strings. */
22
+ const SANDBOX_MODES = [
23
+ "read-only",
24
+ "workspace-write",
25
+ "danger-full-access"
26
+ ];
27
+ //#endregion
28
+ //#region lib/types/invariant.js
29
+ /** Package-owned session-event invariants for sandbox policy. @module @x1a0f3n9/dsh-sandbox-policy/invariant */
30
+ const PACKAGE_NAME = "@x1a0f3n9/dsh-sandbox-policy";
31
+ /** Cordis companion plugin name. */
32
+ const name = "sandbox-policy-invariant";
33
+ /** Service required before the companion can reserve package ownership. */
34
+ const inject = ["invariants"];
35
+ /** Validate the package-owned event fields and ignore unrelated events. */
36
+ function validateEvent(event, fail) {
37
+ if (event.type === "sandbox/mode" && !SANDBOX_MODES.includes(event.data.mode)) fail(`sandbox/mode carries unknown mode ${JSON.stringify(event.data.mode)}`);
38
+ }
39
+ /** Install validation for loaded and newly appended sandbox modes. */
40
+ const install = Object.assign((ctx, fail) => {
41
+ for (const session of ctx.sessions.list()) for (const event of session.snapshotEvents()) validateEvent(event, fail);
42
+ ctx.on("internal/dispatch", (_mode, eventName, args) => {
43
+ if (eventName !== "session/event") return;
44
+ const event = args[1];
45
+ validateEvent(event, fail);
46
+ }, { global: true });
47
+ }, { inject: ["sessions"] });
48
+ /**
49
+ * Register this package's invariant companion.
50
+ * @param ctx - Cordis context carrying the invariant service.
51
+ * @returns the installed registration's disposer after setup succeeds.
52
+ */
53
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
54
+ //#endregion
55
+ export { apply, inject, name };
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The sandbox POLICY home (`ctx.sandboxPolicy`): the single owner of the
3
+ * deployment's sandbox fallbacks plus per-session resolution: the file-effect
4
+ * {@link SandboxMode}, the `workspace-write` root, and the override kit (the
5
+ * `sandbox/mode` event, its fold, and its write path; the fold is the
6
+ * `sandboxMode` session-projection unit registered here, while the event and
7
+ * write path come from `./session-mode.ts`).
8
+ * Before each agent request, the owner also contributes the resolved policy to
9
+ * the cache-safe runtime-context snapshot. The agent loop logs that snapshot as
10
+ * model history, so replay reconstructs the same mode and root the enforcing
11
+ * consumers resolve without rewriting the stable system prompt.
12
+ *
13
+ * Enforcing filesystem, one-shot bash, and terminal backends read the SAME
14
+ * resolved policy here. The context describes that policy without inventorying
15
+ * capabilities, while each backend retains its own enforcement dialect and each
16
+ * tool owns its operation-specific denial and escalation guidance. The service
17
+ * reads session state once at each operation boundary; executors and providers
18
+ * remain session-free.
19
+ *
20
+ * @module @x1a0f3n9/dsh-sandbox-policy
21
+ */
22
+ import { Context, Service } from '@deepseek-ai/cordis';
23
+ import { z as zod } from 'zod';
24
+ import z from '@deepseek-ai/schemastery';
25
+ import { type SandboxExecutionPolicy, type SandboxMode } from '@x1a0f3n9/dsh-sandbox';
26
+ import type { Session } from '@x1a0f3n9/dsh-session';
27
+ export { SANDBOX_MODES, setSandboxMode } from './session-mode.ts';
28
+ declare module '@deepseek-ai/cordis' {
29
+ interface Context {
30
+ sandboxPolicy: SandboxPolicyService;
31
+ }
32
+ }
33
+ /**
34
+ * Plugin config: the deployment's sandbox default. All optional — `Config`
35
+ * supplies the defaults (`mode: 'read-only'` is the fail-safe default; a
36
+ * deployment that wants a workspace-writable agent opts in explicitly). The
37
+ * runner choice is NOT here (it is the `ctx.sandbox` provider's config), nor
38
+ * is any per-family knob: this is the one shared policy home.
39
+ */
40
+ export interface Config {
41
+ /** File-sandbox mode a session starts from (default: `read-only`). */
42
+ mode?: SandboxMode;
43
+ /**
44
+ * Fallback root for agentless calls and sessions without a cwd (default:
45
+ * `process.cwd()`). Normal agent calls use their session cwd instead.
46
+ */
47
+ workspaceRoot?: string;
48
+ }
49
+ /** Inputs that select the sandbox policy for one capability call. */
50
+ export interface SandboxPolicyRequest {
51
+ /** Calling session; its immutable cwd becomes the workspace boundary. */
52
+ session?: Session;
53
+ /** Explicit approved mode override, which outranks session policy. */
54
+ mode?: SandboxMode;
55
+ }
56
+ /** The sandbox-mode projection's state schema (state equals the public shape). */
57
+ declare const sandboxModeStateSchema: zod.ZodNullable<zod.ZodUnion<readonly [zod.ZodLiteral<"read-only">, zod.ZodLiteral<"workspace-write">, zod.ZodLiteral<"danger-full-access">]>>;
58
+ type SandboxModeState = zod.infer<typeof sandboxModeStateSchema>;
59
+ declare module '@x1a0f3n9/dsh-session-projection/types' {
60
+ interface SessionProjectionStateMap {
61
+ /** Last logged sandbox-mode override, or null before one (deployment default applies at resolve time). */
62
+ sandboxMode: SandboxModeState;
63
+ }
64
+ }
65
+ /**
66
+ * The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment
67
+ * default mode, fallback workspace root, and current request-time policy
68
+ * section. Tool layers call {@link resolve} for each execution so a session's
69
+ * mode log and immutable cwd travel together to every enforcing capability.
70
+ */
71
+ export declare class SandboxPolicyService extends Service {
72
+ static Config: z<Config>;
73
+ static inject: string[];
74
+ /** The deployment default mode — the fallback beneath a session override. */
75
+ readonly defaultMode: SandboxMode;
76
+ /** The absolute `workspace-write` fallback root for calls without a session cwd. */
77
+ readonly workspaceRoot: string;
78
+ constructor(ctx: Context, config: Config);
79
+ /**
80
+ * Resolve the complete policy for one capability call. An approved explicit
81
+ * mode outranks the session's last `sandbox/mode` event, which outranks the
82
+ * deployment default. A session cwd is its workspace-write boundary; the
83
+ * configured root is the fallback for agentless calls and sessions without a
84
+ * cwd.
85
+ * @param request - optional session and approved mode override.
86
+ * @returns the fully resolved per-call mode and absolute workspace root.
87
+ */
88
+ resolve(request?: SandboxPolicyRequest): SandboxExecutionPolicy;
89
+ /**
90
+ * Read the session override without applying the deployment default.
91
+ * @param session - session whose log supplies the override.
92
+ * @returns the last logged mode, or `undefined` without one.
93
+ */
94
+ overrideOf(session: Session): SandboxMode | undefined;
95
+ }
96
+ export default SandboxPolicyService;
97
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,13 @@
1
+ /** Package-owned session-event invariants for sandbox policy. @module @x1a0f3n9/dsh-sandbox-policy/invariant */
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ /** Cordis companion plugin name. */
4
+ export declare const name = "sandbox-policy-invariant";
5
+ /** Service required before the companion can reserve package ownership. */
6
+ export declare const inject: string[];
7
+ /**
8
+ * Register this package's invariant companion.
9
+ * @param ctx - Cordis context carrying the invariant service.
10
+ * @returns the installed registration's disposer after setup succeeds.
11
+ */
12
+ export declare const apply: (ctx: Context) => Promise<() => void>;
13
+ //# sourceMappingURL=invariant.d.ts.map
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Per-session sandbox-mode override: the session log as the store. A runtime
3
+ * switch (a UI policy control or test scenario) is recorded as one
4
+ * `sandbox/mode` event on the session it applies to;
5
+ * `effective = projection state ?? the deployment default`, so an override
6
+ * survives restart by replay, two sessions can never see each other's state,
7
+ * and there is no external config store. The event is log-only (the
8
+ * `approval/*` precedent): the policy owner projects the fold into each model
9
+ * request, while enforcing tools report operation-specific boundary markers.
10
+ * EXECUTION honors the same fold through `ctx.sandboxPolicy.resolve()` — it
11
+ * stamps the mode together with the calling session's workspace root onto each
12
+ * capability call, weakest-precedence beneath an escalation grant.
13
+ *
14
+ * The override is policy state shared by every enforcing family (bash and
15
+ * filesystem alike), so it lives here in the policy package rather than in any
16
+ * one capability's seam.
17
+ *
18
+ * @module dsh-sandbox-policy/session-mode
19
+ */
20
+ import type { Session } from '@x1a0f3n9/dsh-session';
21
+ import type { SandboxMode } from '@x1a0f3n9/dsh-sandbox';
22
+ declare module '@x1a0f3n9/dsh-session/types' {
23
+ interface SessionEventMap {
24
+ /**
25
+ * The session's sandbox mode was switched — log-only (like `approval/*`;
26
+ * NOT a surface event, carries no `surfaceOp`): durable and replayable,
27
+ * never in the model transcript. The LAST such event is the session's
28
+ * override (folded by the sandboxMode projection unit). `source: 'delegation'` marks
29
+ * an override seeded into a child; an absent source is a runtime switch.
30
+ */
31
+ 'sandbox/mode': {
32
+ mode: SandboxMode;
33
+ /** Marks an override seeded into a child at delegation. */
34
+ source?: 'delegation';
35
+ };
36
+ }
37
+ }
38
+ /** Every {@link SandboxMode}, for option advertisement and runtime validation of untrusted mode strings. */
39
+ export declare const SANDBOX_MODES: readonly SandboxMode[];
40
+ /**
41
+ * THE write path for a session's sandbox-mode override: appends exactly one
42
+ * `sandbox/mode` event — the switch IS its event; nothing mutates mode state
43
+ * out of band. Takes effect on the session's next confined call (bash or fs)
44
+ * — consumers read the shared projection state.
45
+ * @param session - the session the override belongs to.
46
+ * @param mode - the mode every subsequent confined call in this session runs
47
+ * under (until the next switch).
48
+ */
49
+ export declare function setSandboxMode(session: Session, mode: SandboxMode): void;
50
+ //# sourceMappingURL=session-mode.d.ts.map
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@x1a0f3n9/dsh-sandbox-policy",
3
+ "description": "Per-call sandbox policy resolver and current model context: deployment fallbacks plus each session's mode and workspace root, shared by every enforcing capability family",
4
+ "version": "0.1.5-alpha.2",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/sandbox/sandbox-policy"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./invariant": {
22
+ "types": "./lib/types/invariant.d.ts",
23
+ "default": "./lib/invariant.js"
24
+ },
25
+ "./src/*": "./src/*",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "lib/index.js",
30
+ "lib/invariant.js",
31
+ "lib/types/**/*.d.ts"
32
+ ],
33
+ "license": "MIT",
34
+ "peerDependencies": {
35
+ "@x1a0f3n9/dsh-agent": "^0.1.5-alpha.2",
36
+ "@x1a0f3n9/dsh-invariants": "^0.1.5-alpha.2",
37
+ "@x1a0f3n9/dsh-sandbox": "^0.1.5-alpha.2",
38
+ "@x1a0f3n9/dsh-session": "^0.1.5-alpha.2",
39
+ "@x1a0f3n9/dsh-system-prompt": "^0.1.5-alpha.2",
40
+ "@deepseek-ai/cordis": "^4.0.2",
41
+ "@x1a0f3n9/dsh-session-projection": "^0.1.5-alpha.2"
42
+ },
43
+ "dependencies": {
44
+ "zod": "^4.4.3",
45
+ "@deepseek-ai/schemastery": "^3.18.2"
46
+ },
47
+ "devDependencies": {
48
+ "@x1a0f3n9/dsh-agent": "^0.1.5-alpha.2",
49
+ "@x1a0f3n9/dsh-invariants": "^0.1.5-alpha.2",
50
+ "@x1a0f3n9/dsh-sandbox": "^0.1.5-alpha.2",
51
+ "@x1a0f3n9/dsh-session": "^0.1.5-alpha.2",
52
+ "@x1a0f3n9/dsh-system-prompt": "^0.1.5-alpha.2",
53
+ "@deepseek-ai/cordis": "^4.0.2",
54
+ "@x1a0f3n9/dsh-session-projection": "^0.1.5-alpha.2"
55
+ }
56
+ }