Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input
# Conflicts: # .agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml # .agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md # .agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md # .agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.i18n.yaml # .agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md # .agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.zh.md # THIRD_PARTY_NOTICES.md # apps/cli/composition.md # apps/cli/config/base.cordis.yml # apps/cli/package.json # apps/cli/src/app-cli-entry.ts # apps/cli/src/bin.ts # apps/cli/tests/args.spec.ts # apps/web/tests/built-boot.snapshot.ts # apps/web/tests/navigation-panes.e2e.ts # docs/architecture.i18n.yaml # docs/architecture.md # docs/architecture.zh.md # docs/config-catalog.md # docs/cordis-catalog/services.md # docs/core-data-structures/core.i18n.yaml # docs/core-data-structures/llm-streaming.i18n.yaml # docs/event-producer-consumer.md # docs/module-graph.md # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl # packages/README.i18n.yaml # packages/bundle/README.i18n.yaml # packages/client/connection/README.i18n.yaml # packages/client/connection/README.md # packages/client/connection/README.zh.md # packages/client/connection/src/client/fixture.ts # packages/client/connection/src/http-bridge.ts # packages/client/connection/src/index.ts # packages/client/connection/tests/fixture.spec.ts # packages/client/connection/tests/node-half.spec.ts # packages/client/runtime/README.i18n.yaml # packages/client/runtime/README.md # packages/client/runtime/README.zh.md # packages/client/runtime/src/client/contract/session.ts # packages/client/runtime/src/client/sessions/session.ts # packages/client/ui-conversation/README.i18n.yaml # packages/client/ui-conversation/README.md # packages/client/ui-conversation/README.zh.md # packages/client/ui-conversation/src/client/apply.ts # packages/client/ui-conversation/src/client/chat/AssistantMarkdown.tsx # packages/client/ui-conversation/src/client/chat/ChatView.tsx # packages/client/ui-conversation/src/client/chat/MessageItem.module.css # packages/client/ui-conversation/src/client/chat/MessageItem.tsx # packages/client/ui-conversation/src/client/contract/slots.ts # packages/client/ui-conversation/src/client/index.ts # packages/client/ui-conversation/src/client/input/contract.ts # packages/client/ui-conversation/src/client/input/facade.ts # packages/client/ui-conversation/src/client/input/hub.ts # packages/client/ui-conversation/src/client/locales.ts # packages/client/ui-conversation/src/client/service.ts # packages/client/ui-conversation/src/client/skeleton/ConversationSession.tsx # packages/client/ui-conversation/src/client/skeleton/InputBar.tsx # packages/client/ui-conversation/tests/apply-inject.spec.tsx # packages/client/ui-conversation/tests/input-bar.spec.tsx # packages/client/ui-conversation/tests/input-matrix.spec.tsx # packages/client/ui-conversation/tests/input-scenarios.spec.tsx # packages/client/ui-conversation/tests/service-orchestration.spec.ts # packages/client/ui-conversation/tests/skeleton.spec.tsx # packages/client/ui-trajectory/tests/views.spec.tsx # packages/compact/compact-basic/README.i18n.yaml # packages/cordis/tool-cordis/src/api-catalog.ts # packages/host/apiproxy/README.i18n.yaml # packages/host/apiproxy/README.md # packages/host/apiproxy/README.zh.md # packages/host/apiproxy/src/api-proxy.ts # packages/host/apiproxy/src/api/rpc.ts # packages/host/apiproxy/src/api/sessions.ts # packages/host/apiproxy/src/index.ts # packages/host/apiproxy/tests/api-proxy-models.spec.ts # packages/host/apiproxy/tests/rpc-schemas.spec.ts # packages/llm/llm-pi-ai/README.i18n.yaml # packages/llm/llm-pi-ai/README.md # packages/llm/llm-pi-ai/README.zh.md # packages/llm/llm-pi-ai/src/adapter.ts # packages/llm/llm/README.i18n.yaml # packages/ui/tui/README.md # packages/ui/tui/README.zh.md # packages/ui/tui/src/components/content.ts # packages/ui/tui/src/components/transcript.ts # packages/ui/tui/tests/tui.spec.ts # pnpm-lock.yaml
This commit is contained in:
@@ -12,6 +12,7 @@ import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import { LAUNCHER_FAILURE_EXIT } from 'node-addon-landlock-run'
|
||||
import { SANDBOX_UNAVAILABLE, SandboxUnavailableError } from '@deepseek-ai/dsh-sandbox'
|
||||
import type { SandboxPolicy } from '@deepseek-ai/dsh-sandbox'
|
||||
import {
|
||||
@@ -35,7 +36,7 @@ async function setup(config: Config = {}, internals: LocalSandboxProvider['inter
|
||||
function fakeLauncher(report = 'landlock: fully enforced'): string {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'dsh-fake-landlock-'))
|
||||
const launcher = join(dir, 'landlock-run')
|
||||
writeFileSync(launcher, `#!/bin/sh\nif [ "$1" = "--probe" ]; then echo "${report}"; exit 0; fi\nexit 125\n`, { mode: 0o755 })
|
||||
writeFileSync(launcher, `#!/bin/sh\nif [ "$1" = "--probe" ]; then echo "${report}"; exit 0; fi\nexit ${LAUNCHER_FAILURE_EXIT}\n`, { mode: 0o755 })
|
||||
return launcher
|
||||
}
|
||||
|
||||
@@ -111,16 +112,7 @@ describe('runnerCommand config', () => {
|
||||
// An operator runner's kernel mechanism is unknown: both Linux
|
||||
// file-denial dialects, never bare EPERM.
|
||||
denialSignatures: ['read-only file system', 'permission denied'],
|
||||
// The runner's own dialect is unknown, but the consumer re-joins the
|
||||
// wrap through an outer `bash -c 'exec …'` — a missing or
|
||||
// unexecutable runner fails with the OUTER shell's argv0-scoped
|
||||
// shapes, and those classify as sandbox failures like any rung.
|
||||
runnerFailureSignatures: [
|
||||
'fake-runner: profile rejected',
|
||||
'exec: fake-runner: not found',
|
||||
'fake-runner: No such file or directory',
|
||||
'fake-runner: Permission denied',
|
||||
],
|
||||
runnerFailureRules: [{ fatalSignatures: ['fake-runner: profile rejected'] }],
|
||||
})
|
||||
expect(probeBwrap).not.toHaveBeenCalled()
|
||||
expect(probeLandlock).not.toHaveBeenCalled()
|
||||
@@ -146,11 +138,14 @@ describe('runnerCommand config', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects blank configured-runner failure signatures', async () => {
|
||||
await expect(setup({ runnerCommand: ['fake-runner'], runnerFailureSignatures: [' '] })).rejects.toThrow(
|
||||
'runnerFailureSignatures entries must be non-empty',
|
||||
)
|
||||
})
|
||||
it.each([' ', 'fatal\ncontinued', 'fatal\rcontinued'])(
|
||||
'rejects an unusable configured-runner failure signature %j',
|
||||
async (signature) => {
|
||||
await expect(setup({ runnerCommand: ['fake-runner'], runnerFailureSignatures: [signature] })).rejects.toThrow(
|
||||
'runnerFailureSignatures entries must be non-empty single-line strings',
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
describe('the platform chains', () => {
|
||||
@@ -163,7 +158,7 @@ describe('the platform chains', () => {
|
||||
argv: ['bwrap', ...bwrapProfileArgs(RO), '--', 'true'],
|
||||
enforcement: 'full',
|
||||
denialSignatures: ['read-only file system'],
|
||||
runnerFailureSignatures: ['bwrap: '],
|
||||
runnerFailureRules: [{ fatalSignatures: ['bwrap: '] }],
|
||||
})
|
||||
expect(probeLandlock).not.toHaveBeenCalled()
|
||||
})
|
||||
@@ -178,14 +173,18 @@ describe('the platform chains', () => {
|
||||
argv: [launcher, ...landlockProfileArgs(WW), '--', 'bash', '-c', 'echo hi'],
|
||||
enforcement: 'full',
|
||||
denialSignatures: ['permission denied'],
|
||||
runnerFailureSignatures: ['landlock-run: '],
|
||||
runnerFailureRules: [{
|
||||
allowedExitCodes: [LAUNCHER_FAILURE_EXIT],
|
||||
fatalSignatures: ['landlock-run: '],
|
||||
informationalLines: ['landlock-run: partial enforcement (older Landlock ABI)'],
|
||||
}],
|
||||
})
|
||||
expect(probeLandlock).toHaveBeenCalledWith(launcher)
|
||||
})
|
||||
|
||||
it('darwin selects its sole candidate WITHOUT probing: nothing to arbitrate', async () => {
|
||||
// The safety property moves to execution time: an unusable sandbox-exec
|
||||
// refuses to run the command, and the wrap's runnerFailureSignatures let
|
||||
// refuses to run the command, and the wrap's runnerFailureRules let
|
||||
// the consumer classify that as a sandbox failure, not a task failure.
|
||||
const probeSeatbelt = vi.fn(() => true)
|
||||
const { sandbox } = await setup({}, { platform: 'darwin', probeSeatbelt })
|
||||
@@ -194,7 +193,7 @@ describe('the platform chains', () => {
|
||||
argv: ['sandbox-exec', ...seatbeltProfileArgs(RO), '--', 'bash', '-c', 'echo hi'],
|
||||
enforcement: 'full',
|
||||
denialSignatures: ['operation not permitted'],
|
||||
runnerFailureSignatures: ['sandbox-exec: '],
|
||||
runnerFailureRules: [{ fatalSignatures: ['sandbox-exec: '] }],
|
||||
})
|
||||
expect(probeSeatbelt).not.toHaveBeenCalled()
|
||||
})
|
||||
@@ -311,7 +310,7 @@ describe('the default landlock probe (launcher CLI contract)', () => {
|
||||
it('reads a failing launcher as unusable: the chain ends and fails closed', async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'dsh-fake-landlock-'))
|
||||
const launcher = join(dir, 'landlock-run')
|
||||
writeFileSync(launcher, '#!/bin/sh\nexit 125\n', { mode: 0o755 })
|
||||
writeFileSync(launcher, `#!/bin/sh\nexit ${LAUNCHER_FAILURE_EXIT}\n`, { mode: 0o755 })
|
||||
const { sandbox } = await setup({}, { platform: 'linux', probeBwrap: () => false, landlockLauncher: launcher })
|
||||
expect(() => sandbox.confine(['true'], RO)).toThrow(expect.objectContaining({ code: SANDBOX_UNAVAILABLE }))
|
||||
})
|
||||
@@ -360,7 +359,7 @@ describe('the default seatbelt probe (sandbox-exec contract)', () => {
|
||||
argv: [exec, ...seatbeltProfileArgs(RO), '--', 'true'],
|
||||
enforcement: 'full',
|
||||
denialSignatures: ['operation not permitted'],
|
||||
runnerFailureSignatures: ['sandbox-exec: '],
|
||||
runnerFailureRules: [{ fatalSignatures: ['sandbox-exec: '] }],
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user