-
Notifications
You must be signed in to change notification settings - Fork 17.1k
Expand file tree
/
Copy pathindex.ts
More file actions
154 lines (141 loc) · 7.05 KB
/
Copy pathindex.ts
File metadata and controls
154 lines (141 loc) · 7.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/**
* The sandbox POLICY home (`ctx.sandboxPolicy`): the single owner of the
* deployment's sandbox fallbacks plus per-session resolution: the file-effect
* {@link SandboxMode}, the `workspace-write` root, and the override kit (the
* `sandbox/mode` event, its fold, and its write path, from `./session-mode.ts`).
* Before each agent request, the owner also contributes the resolved policy to
* the cache-safe runtime-context snapshot. The agent loop logs that snapshot as
* model history, so replay reconstructs the same mode and root the enforcing
* consumers resolve without rewriting the stable system prompt.
*
* Enforcing filesystem, one-shot bash, and terminal backends read the SAME
* resolved policy here. The context describes that policy without inventorying
* capabilities, while each backend retains its own enforcement dialect and each
* tool owns its operation-specific denial and escalation guidance. The service
* reads session state once at each operation boundary; executors and providers
* remain session-free.
*
* @module @deepseek-ai/dsh-sandbox-policy
*/
import { resolve as resolvePath } from 'node:path'
import { Context, Service } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'
import type {} from '@deepseek-ai/dsh-agent'
import { canonicalPath, type SandboxExecutionPolicy, type SandboxMode } from '@deepseek-ai/dsh-sandbox'
import type { Session } from '@deepseek-ai/dsh-session'
import type {} from '@deepseek-ai/dsh-system-prompt'
import { effectiveSandboxMode } from './session-mode.ts'
export { SANDBOX_MODES, effectiveSandboxMode, setSandboxMode } from './session-mode.ts'
/** Resolve filesystem identity before lexical normalization can erase symlink-sensitive components. */
function resolveWorkspaceRoot(path: string): string {
return resolvePath(canonicalPath(path))
}
/** Render the policy without claiming which capabilities are mounted. */
function renderPolicyContext(policy: SandboxExecutionPolicy): string {
switch (policy.mode) {
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.'
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.`
case 'danger-full-access':
return 'Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.'
/* v8 ignore next 4 -- SandboxMode is a typed same-process closed union; this branch is only the static exhaustiveness guard. */
default: {
const mode: never = policy.mode
throw new Error(`unreachable sandbox mode: ${String(mode)}`)
}
}
}
declare module '@deepseek-ai/cordis' {
interface Context {
sandboxPolicy: SandboxPolicyService
}
}
/**
* Plugin config: the deployment's sandbox default. All optional — `Config`
* supplies the defaults (`mode: 'read-only'` is the fail-safe default; a
* deployment that wants a workspace-writable agent opts in explicitly). The
* runner choice is NOT here (it is the `ctx.sandbox` provider's config), nor
* is any per-family knob: this is the one shared policy home.
*/
export interface Config {
/** File-sandbox mode a session starts from (default: `read-only`). */
mode?: SandboxMode
/**
* Fallback root for agentless calls and sessions without a cwd (default:
* `process.cwd()`). Normal agent calls use their session cwd instead.
*/
workspaceRoot?: string
}
/** Inputs that select the sandbox policy for one capability call. */
export interface SandboxPolicyRequest {
/** Calling session; its immutable cwd becomes the workspace boundary. */
session?: Session
/** Explicit approved mode override, which outranks session policy. */
mode?: SandboxMode
}
/**
* The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment
* default mode, fallback workspace root, and current request-time policy
* section. Tool layers call {@link resolve} for each execution so a session's
* mode log and immutable cwd travel together to every enforcing capability.
*/
export class SandboxPolicyService extends Service {
// Inline schema call: the config catalog walks `static Config` statically.
static Config: z<Config> = z.object({
mode: z.union(['read-only', 'workspace-write', 'danger-full-access'] as const).default('read-only'),
// No schema default: process.cwd() is resolved in the constructor so the
// stored root is always absolute regardless of how it was supplied.
workspaceRoot: z.string(),
})
/** The deployment default mode — the fallback beneath a session override. */
readonly defaultMode: SandboxMode
/** The absolute `workspace-write` fallback root for calls without a session cwd. */
readonly workspaceRoot: string
constructor(ctx: Context, config: Config) {
super(ctx, 'sandboxPolicy')
// schemastery (static Config) already filled `mode`; the cast records that
// runtime fact. `workspaceRoot` has NO schema default, so its fallback to
// the process cwd is real branching, resolved absolute either way.
this.defaultMode = config.mode as SandboxMode
this.workspaceRoot = resolveWorkspaceRoot(config.workspaceRoot ?? process.cwd())
ctx.inject(['systemPrompt'], (scope: Context) => {
scope.systemPrompt.context({
name: 'sandbox:policy',
order: 110,
text: (context) => {
const session = context.agent?.session
return session === undefined
? ''
: renderPolicyContext(this.resolve({ session }))
},
})
})
}
/**
* Resolve the complete policy for one capability call. An approved explicit
* mode outranks the session's last `sandbox/mode` event, which outranks the
* deployment default. A session cwd is its workspace-write boundary; the
* configured root is the fallback for agentless calls and sessions without a
* cwd.
* @param request - optional session and approved mode override.
* @returns the fully resolved per-call mode and absolute workspace root.
*/
resolve(request: SandboxPolicyRequest = {}): SandboxExecutionPolicy {
const { session } = request
return {
mode: request.mode ?? (session === undefined ? undefined : this.overrideOf(session)) ?? this.defaultMode,
workspaceRoot: resolveWorkspaceRoot(session?.header.cwd ?? this.workspaceRoot),
...session === undefined ? {} : { sessionId: session.id },
}
}
/**
* Read the session override without applying the deployment default.
* @param session - session whose log supplies the override.
* @returns the last logged mode, or `undefined` without one.
*/
overrideOf(session: Session): SandboxMode | undefined {
return effectiveSandboxMode(session.events)
}
}
export default SandboxPolicyService