test(ptc): make sandbox denial recording portable and cover execution descriptors
This commit is contained in:
1 parent
a29991144a
commit
4d4e333676
7 files changed
+17
-5
No files matched your search
@@ -19,6 +19,7 @@ const UI_EXPECTED = join(SNAPSHOT_DIR, 'approval.expected.md')
|
||||
const MODE = webSnapshotMode()
|
||||
const PROMPT = 'Use run_code with timeoutMs 120000 and direct Node filesystem access to create approved.txt in the working directory containing exactly "approved\\n". '
|
||||
+ 'Use await import("node:fs/promises") and writeFile; do not call nested tools. First attempt the write under the current read-only sandbox without escalation. '
|
||||
+ 'In that first program, catch only filesystem errors with code EPERM, EACCES or EROFS and return exactly "EXPECTED_SANDBOX_DENIAL"; rethrow any other error. '
|
||||
+ 'If the sandbox denies it, explicitly retry the program with sandbox_permissions "workspace-write" and justification "Create the file requested by the user". '
|
||||
+ 'I will answer the approval prompt. After the file is written, reply DONE and stop.'
|
||||
|
||||
@@ -67,6 +68,8 @@ describe('web e2e: PTC program sandbox escalation', () => {
|
||||
expect(calls.every(event => event.data.name === 'run_code')).toBe(true)
|
||||
expect(JSON.stringify(calls[0]?.data)).toContain('node:fs/promises')
|
||||
expect(JSON.stringify(calls.at(-1)?.data)).toContain('workspace-write')
|
||||
const results = events.filter(event => event.type === 'tool/result')
|
||||
expect(JSON.stringify(results[0]?.data)).toContain('EXPECTED_SANDBOX_DENIAL')
|
||||
const file = join(scaffold.workspaceCwd, 'workspace', 'approved.txt')
|
||||
await expect(readFile(file, 'utf8')).rejects.toMatchObject({ code: 'ENOENT' })
|
||||
if (MODE !== 'record') {
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/code-runtime.md
|
||||
code-runtime.md: 085d51e1c51fefe37b9b5913d1a2d1e69fbf5fc8
|
||||
code-runtime.zh.md: 887e0dabc6c1c5cf9faa0a9c47813cc0681ccbfa
|
||||
code-runtime.md: 19eb28750cddc6b2660d74f24927f65888ba175e
|
||||
code-runtime.zh.md: a8fa809846f5ecb57c1bf29fc30f53b42c5b4fdf
|
||||
@@ -192,7 +192,7 @@ interface CodeRunFailure {
|
||||
|
||||
## The service
|
||||
|
||||
`CodeRuntime` is defined in [`src/index.ts`](../../packages/code-runtime/code-runtime/src/index.ts). `resolve(request)` returns complete execution inputs, and `run(spec)` executes them. `executionInstructions` supplies provider-owned usage guidance for consumer presentation. `language` selects supported program presentation; `isolation` describes the substrate without claiming security. `sandboxMode` advertises file-policy support, with `undefined` for a provider that does not supply confinement. Each implementation keeps program state separate between runs and terminates and awaits active executions during disposal.
|
||||
`CodeRuntime` is defined in [`src/index.ts`](../../packages/code-runtime/code-runtime/src/index.ts). `resolve(request)` returns complete execution inputs, and `run(spec)` executes them. `executionInstructions` supplies provider-owned usage guidance for consumer presentation. `timeout` reports the configured elapsed-time default and maximum when per-call overrides are supported; `resolve` still validates and caps each request. `language` selects supported program presentation; `isolation` describes the substrate without claiming security. `sandboxMode` advertises file-policy support, with `undefined` for a provider that does not supply confinement. Each implementation keeps program state separate between runs and terminates and awaits active executions during disposal.
|
||||
|
||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ interface CodeRunFailure {
|
||||
|
||||
## 服务
|
||||
|
||||
`CodeRuntime` 定义于 [`src/index.ts`](../../packages/code-runtime/code-runtime/src/index.ts)。`resolve(request)` 返回完整执行输入,`run(spec)` 执行它们。`executionInstructions` 提供由运行时拥有的使用说明,供消费方呈现。`language` 选择支持的程序呈现;`isolation` 描述执行基底,不作安全声明。`sandboxMode` 声明文件策略支持,不提供约束的提供方返回 `undefined`。每个实现将各次运行的程序状态分离,并在资源释放期间终止且等待活跃执行。
|
||||
`CodeRuntime` 定义于 [`src/index.ts`](../../packages/code-runtime/code-runtime/src/index.ts)。`resolve(request)` 返回完整执行输入,`run(spec)` 执行它们。`executionInstructions` 提供由运行时拥有的使用说明,供消费方呈现。支持逐次覆盖时,`timeout` 报告配置的经过时间默认值和上限;每次请求仍由 `resolve` 验证并截断。`language` 选择支持的程序呈现;`isolation` 描述执行基底,不作安全声明。`sandboxMode` 声明文件策略支持,不提供约束的提供方返回 `undefined`。每个实现将各次运行的程序状态分离,并在资源释放期间终止且等待活跃执行。
|
||||
|
||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||
|
||||
|
||||
@@ -132,6 +132,7 @@ describe('Node program process', () => {
|
||||
|
||||
it('uses the default deadline and caps explicit requests', async () => {
|
||||
const { runtime } = await setup()
|
||||
expect(runtime.timeout).toEqual({ defaultMs: 120_000, maxMs: 600_000 })
|
||||
expect(runtime.executionInstructions).toBe('Each call runs in a fresh Node process. Node APIs are available through await import(...). Relative paths use the supplied working directory; process.env starts empty. Direct filesystem access follows this execution\'s sandbox policy.')
|
||||
expect(runtime.resolve({ program: '', bindings: [] }).timeoutMs).toBe(120_000)
|
||||
expect(runtime.resolve({ program: '', bindings: [], timeoutMs: 900_000 }).timeoutMs).toBe(600_000)
|
||||
@@ -139,6 +140,12 @@ describe('Node program process', () => {
|
||||
await expect(runtime.run({ program: '', bindings: [], cwd: process.cwd(), timeoutMs: 1000 })).rejects.toThrow('sandbox policy')
|
||||
})
|
||||
|
||||
it('advertises the capped default when the deployment maximum is lower', async () => {
|
||||
const { runtime } = await setup({ timeoutMs: 2000, maxTimeoutMs: 1000 })
|
||||
expect(runtime.timeout).toEqual({ defaultMs: 1000, maxMs: 1000 })
|
||||
expect(runtime.timeout.defaultMs).toBe(runtime.resolve({ program: '', bindings: [] }).timeoutMs)
|
||||
})
|
||||
|
||||
it.each(['for (;;) {}', 'await new Promise(() => {})'])('ends an unfinished program at its elapsed deadline: %s', async (program) => {
|
||||
const { run } = await setup({ timeoutMs: 600, graceMs: 50 })
|
||||
expect((await run({ program, bindings: [] })).error?.kind).toBe('timeout')
|
||||
|
||||
@@ -741,7 +741,8 @@ export function createRunCodeTool(registry: ToolRuntime, options: RunCodeBridgeO
|
||||
const runtime = peekRuntime()
|
||||
const instructions = runtime?.executionInstructions
|
||||
return resolveFlavor(peekRuntime).description
|
||||
+ (instructions ? ` ${instructions} The working directory is the Session's current directory.` : '')
|
||||
+ (instructions ? ` ${instructions}` : '')
|
||||
+ (runtime === undefined ? '' : " The working directory is the Session's current directory.")
|
||||
+ escalationGuidance(runtime)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -2034,6 +2034,7 @@ describe('per-program execution controls', () => {
|
||||
expect(JSON.stringify(schema.parameters)).not.toContain('timeoutMs')
|
||||
expect(JSON.stringify(schema.parameters)).not.toContain('sandbox_permissions')
|
||||
expect(schema.description).not.toContain('Programs start with an empty environment.')
|
||||
expect(schema.description).toContain("The working directory is the Session's current directory.")
|
||||
const rejected = await python.tools.execute({
|
||||
callId: ToolCallId('hidden-timeout'), name: RUN_CODE_NAME, signal: testToolSignal,
|
||||
arguments: { code: 'pass', description: 'Try unsupported timeout', timeoutMs: 5 },
|
||||
|
||||
Reference in new issue
Block a user