返回源码地图

packages/sandbox/sandbox-policy/src/index.ts

main snapshot · da00f7f5358f · 正文引用章节 10;完整原文可核对,不声称全文件人工逐行审计

完整原文供逐行核对;页面收录不代表每行都经过人工语义审核。MIT 许可见 许可证。

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 @deepseek-ai/dsh-sandbox-policy
21 */
22
23import { isAbsolute } from 'node:path'
24import { Context, Service } from '@deepseek-ai/cordis'
25import { z as zod } from 'zod'
26import z from '@deepseek-ai/schemastery'
27import type {} from '@deepseek-ai/dsh-agent'
28import type { SandboxExecutionPolicy, SandboxMode } from '@deepseek-ai/dsh-sandbox'
29import type { Session } from '@deepseek-ai/dsh-session'
30import type {} from '@deepseek-ai/dsh-session-projection'
31import type {} from '@deepseek-ai/dsh-system-prompt'
32
33export { SANDBOX_MODES, setSandboxMode } from './session-mode.ts'
34
35/** Preserve execution-world spelling; enforcing providers resolve filesystem identity on their host. */
36function resolveWorkspaceRoot(path: string): string {
37 if (!isAbsolute(path)) throw new Error('sandbox-policy: workspace root must be an absolute execution-world path')
38 return path
39}
40
41/** Render the policy without claiming which capabilities are mounted. */
42function renderPolicyContext(policy: SandboxExecutionPolicy): string {
43 switch (policy.mode) {
44 case 'read-only':
45 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.'
46 case 'workspace-write':
47 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.`
48 case 'danger-full-access':
49 return 'Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.'
50 /* v8 ignore next 4 -- SandboxMode is a typed same-process closed union; this branch is only the static exhaustiveness guard. */
51 default: {
52 const mode: never = policy.mode
53 throw new Error(`unreachable sandbox mode: ${String(mode)}`)
54 }
55 }
56}
57
58declare module '@deepseek-ai/cordis' {
59 interface Context {
60 sandboxPolicy: SandboxPolicyService
61 }
62}
63
64/**
65 * Plugin config: the deployment's sandbox default. All optional — `Config`
66 * supplies the defaults (`mode: 'read-only'` is the fail-safe default; a
67 * deployment that wants a workspace-writable agent opts in explicitly). The
68 * runner choice is NOT here (it is the `ctx.sandbox` provider's config), nor
69 * is any per-family knob: this is the one shared policy home.
70 */
71export interface Config {
72 /** File-sandbox mode a session starts from (default: `read-only`). */
73 mode?: SandboxMode
74 /**
75 * Absolute fallback root for agentless calls and sessions without a cwd (default:
76 * `process.cwd()`). Normal agent calls use their session cwd instead.
77 */
78 workspaceRoot?: string
79}
80
81/** Inputs that select the sandbox policy for one capability call. */
82export interface SandboxPolicyRequest {
83 /** Calling session; its immutable cwd becomes the workspace boundary. */
84 session?: Session
85 /** Explicit approved mode override, which outranks session policy. */
86 mode?: SandboxMode
87}
88
89/** The sandbox-mode projection's state schema (state equals the public shape). */
90const sandboxModeStateSchema = zod.union([
91 zod.literal('read-only'),
92 zod.literal('workspace-write'),
93 zod.literal('danger-full-access'),
94]).nullable()
95
96type SandboxModeState = zod.infer<typeof sandboxModeStateSchema>
97declare module '@deepseek-ai/dsh-session-projection/types' {
98 interface SessionProjectionStateMap {
99 /** Last logged sandbox-mode override, or null before one (deployment default applies at resolve time). */
100 sandboxMode: SandboxModeState
101 }
102}
103
104/**
105 * The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment
106 * default mode, fallback workspace root, and current request-time policy
107 * section. Tool layers call {@link resolve} for each execution so a session's
108 * mode log and immutable cwd travel together to every enforcing capability.
109 */
110export class SandboxPolicyService extends Service {
111 // Inline schema call: the config catalog walks `static Config` statically.
112 static Config: z<Config> = z.object({
113 mode: z.union(['read-only', 'workspace-write', 'danger-full-access'] as const).default('read-only'),
114 // No schema default: process.cwd() is resolved in the constructor so the
115 // stored root is always absolute regardless of how it was supplied.
116 workspaceRoot: z.string(),
117 })
118
119 static inject = ['sessionProjections']
120
121 /** The deployment default mode — the fallback beneath a session override. */
122 readonly defaultMode: SandboxMode
123 /** The absolute `workspace-write` fallback root for calls without a session cwd. */
124 readonly workspaceRoot: string
125 constructor(ctx: Context, config: Config) {
126 super(ctx, 'sandboxPolicy')
127 // schemastery (static Config) already filled `mode`; the cast records that
128 // runtime fact. `workspaceRoot` has NO schema default, so its fallback to
129 // the process cwd is real branching, resolved absolute either way.
130 this.defaultMode = config.mode as SandboxMode
131 this.workspaceRoot = resolveWorkspaceRoot(config.workspaceRoot ?? process.cwd())
132
133 ctx.sessionProjections.register({
134 key: 'sandboxMode',
135 stateVersion: 1,
136 stateSchema: sandboxModeStateSchema,
137 init: () => null,
138 apply: (state, event) => (event.type === 'sandbox/mode' ? event.data.mode : state),
139 })
140
141 ctx.inject(['systemPrompt'], (scope: Context) => {
142 scope.systemPrompt.context({
143 name: 'sandbox:policy',
144 order: scope.systemPrompt.getContextOrder('SANDBOX_POLICY'),
145 text: (context) => {
146 const session = context.agent?.session
147 return session === undefined
148 ? ''
149 : renderPolicyContext(this.resolve({ session }))
150 },
151 })
152 })
153 }
154
155 /**
156 * Resolve the complete policy for one capability call. An approved explicit
157 * mode outranks the session's last `sandbox/mode` event, which outranks the
158 * deployment default. A session cwd is its workspace-write boundary; the
159 * configured root is the fallback for agentless calls and sessions without a
160 * cwd.
161 * @param request - optional session and approved mode override.
162 * @returns the fully resolved per-call mode and absolute workspace root.
163 */
164 resolve(request: SandboxPolicyRequest = {}): SandboxExecutionPolicy {
165 const { session } = request
166 return {
167 mode: request.mode ?? (session === undefined ? undefined : this.overrideOf(session)) ?? this.defaultMode,
168 workspaceRoot: resolveWorkspaceRoot(session?.header.cwd ?? this.workspaceRoot),
169 ...session === undefined ? {} : { sessionId: session.id },
170 }
171 }
172
173 /**
174 * Read the session override without applying the deployment default.
175 * @param session - session whose log supplies the override.
176 * @returns the last logged mode, or `undefined` without one.
177 */
178 overrideOf(session: Session): SandboxMode | undefined {
179 return this.ctx.sessionProjections.stateOf(session, 'sandboxMode') ?? undefined
180 }
181}
182
183export default SandboxPolicyService