fix:disable-agent-sandbox-at-runtime
This commit is contained in:
1 parent
3be45999f7
commit
c7d6c558e6
24 files changed
+147
-500
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { mkdtemp, mkdir, readFile, writeFile, rm, symlink } from 'node:fs/promises'
|
||||
import { mkdtemp, mkdir, readFile, writeFile, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { createInteractionBroker, respondToInteraction } from '../src/main/interaction-broker'
|
||||
@@ -13,13 +13,11 @@ import type { ChatEvent, ChatMessage } from '../src/shared/types'
|
||||
import type { ChatInteraction } from '../src/shared/conversation'
|
||||
import type { OmpKernel } from '../src/main/omp'
|
||||
import { assistantWireHistory } from '../src/main/agent/history'
|
||||
import { configureSandbox } from '../src/main/sandbox/configuration'
|
||||
|
||||
async function main() {
|
||||
const directory = await mkdtemp(join(tmpdir(), 'ycode-conversation-runtime-'))
|
||||
const workspace = join(directory, 'workspace')
|
||||
await mkdir(workspace)
|
||||
configureSandbox({ stateRoot: join(directory, 'sandbox-state'), resourceDirectory: join(directory, 'missing-sandbox-backend') })
|
||||
try {
|
||||
const events: ChatEvent[] = []
|
||||
const emitted = new EventEmitter()
|
||||
@@ -31,43 +29,30 @@ async function main() {
|
||||
const listener = (event: ChatEvent) => { if (event.type === 'interaction' && event.interaction.status === 'pending') { emitted.off('event', listener); resolve(event.interaction) } }
|
||||
emitted.on('event', listener)
|
||||
})
|
||||
const pendingApproval = nextInteraction()
|
||||
const write = executeConversationTool('write_file', { path: 'nested/new/file.txt', content: 'first' }, context)
|
||||
const approval = await pendingApproval
|
||||
assert.equal(approval.kind, 'approval')
|
||||
await assert.rejects(readFile(join(workspace, 'nested/new/file.txt')), { code: 'ENOENT' })
|
||||
assert.equal(respondToInteraction('another-run', approval.id, { kind: 'approval', optionId: 'allow-once' }, emit), false)
|
||||
assert.throws(() => respondToInteraction('run', approval.id, { kind: 'approval', optionId: 'invented' }, emit), /无效/)
|
||||
assert.equal(respondToInteraction('run', approval.id, { kind: 'approval', optionId: 'allow-once' }, emit), true)
|
||||
assert.equal(respondToInteraction('run', approval.id, { kind: 'approval', optionId: 'allow-once' }, emit), false)
|
||||
const written = await write
|
||||
assert.equal(written.ok, true)
|
||||
assert.equal(await readFile(join(workspace, 'nested/new/file.txt'), 'utf8'), 'first')
|
||||
assert.equal((written.data as { oldContent: unknown }).oldContent, null)
|
||||
assert.equal(events.some(event => event.type === 'interaction'), false, 'host file writes no longer create sandbox approvals')
|
||||
|
||||
const pendingEdit = nextInteraction()
|
||||
const edit = executeConversationTool('edit_file', { path: 'nested/new/file.txt', oldText: 'first', newText: 'second' }, context)
|
||||
const editApproval = await pendingEdit
|
||||
await writeFile(join(workspace, 'nested/new/file.txt'), 'external change')
|
||||
respondToInteraction('run', editApproval.id, { kind: 'approval', optionId: 'allow-once' }, emit)
|
||||
assert.equal((await edit).ok, false)
|
||||
assert.equal(await readFile(join(workspace, 'nested/new/file.txt'), 'utf8'), 'external change', 'stale approval cannot overwrite a newer file')
|
||||
assert.equal((await edit).ok, true)
|
||||
assert.equal(await readFile(join(workspace, 'nested/new/file.txt'), 'utf8'), 'second')
|
||||
const outside = join(directory, 'outside')
|
||||
await mkdir(outside)
|
||||
await writeFile(join(outside, 'secret.txt'), 'private')
|
||||
await symlink(outside, join(workspace, 'linked'), process.platform === 'win32' ? 'junction' : 'dir')
|
||||
assert.equal((await executeConversationTool('read_file', { path: 'linked/secret.txt' }, context)).ok, false)
|
||||
assert.equal((await executeConversationTool('write_file', { path: '../outside/deep/new.txt', content: 'x' }, context)).ok, false)
|
||||
const unavailableCommand = await executeConversationTool('execute_command', { command: 'echo SHOULD_NOT_RUN' }, context)
|
||||
assert.equal(unavailableCommand.ok, false, 'a missing native backend must fail closed')
|
||||
assert.match(unavailableCommand.content, /ENOENT|原生组件/)
|
||||
const hostCommand = await executeConversationTool('execute_command', { command: 'echo HOST_COMMAND_OK' }, context)
|
||||
assert.equal(hostCommand.ok, true)
|
||||
assert.match(hostCommand.content, /HOST_COMMAND_OK/)
|
||||
const fullController = new AbortController()
|
||||
const childScript = join(directory, 'child.cjs')
|
||||
const parentScript = join(directory, 'parent.cjs')
|
||||
const pidFile = join(directory, 'child-pid.txt')
|
||||
await writeFile(childScript, `require('node:fs').writeFileSync(${JSON.stringify(pidFile)},String(process.pid));setInterval(()=>{},1000)`)
|
||||
await writeFile(parentScript, `require('node:child_process').spawn(process.execPath,[${JSON.stringify(childScript)}],{windowsHide:true,stdio:'ignore'});process.stdout.write('child started');setInterval(()=>{},1000)`)
|
||||
const fullCommand = executeConversationTool('execute_command', { command: `"${process.execPath}" "${parentScript}"` }, { ...context, permissionMode: 'full-access', signal: fullController.signal })
|
||||
const fullCommand = executeConversationTool('execute_command', { command: `"${process.execPath}" "${parentScript}"` }, { ...context, signal: fullController.signal })
|
||||
let childPid = 0
|
||||
for (let attempt = 0; attempt < 100; attempt++) {
|
||||
childPid = Number(await readFile(pidFile, 'utf8').catch(() => '0'))
|
||||
|
||||
Reference in new issue
Block a user