1
0

landlock.e2e.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import { spawnSync } from 'node:child_process'
  2. import { existsSync, readFileSync } from 'node:fs'
  3. import { mkdtemp, rm } from 'node:fs/promises'
  4. import { homedir, tmpdir } from 'node:os'
  5. import { join } from 'node:path'
  6. import { afterEach, describe, expect, it } from 'vitest'
  7. import { Context } from '@deepseek-ai/cordis'
  8. import { launcherPath } from '@deepseek-ai/node-addon-system/landlock-run'
  9. import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local'
  10. import { SandboxPolicyService } from '@deepseek-ai/dsh-sandbox-policy'
  11. import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
  12. import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox'
  13. import LocalSubprocessRuntime from '@deepseek-ai/dsh-subprocess-local'
  14. /**
  15. * KEYLESS consumer-integration proof: the REAL `LocalSandboxProvider` (bwrap
  16. * rung forced off, so the workspace `landlock-run` launcher confines) underneath the
  17. * REAL `SandboxBashExecutor`, driven through the executor's public run/start
  18. * paths. Verifies the WORLD (files exist or don't) plus the stamped result
  19. * facts; the backend-only confinement proofs live with
  20. * `@deepseek-ai/dsh-sandbox-local`.
  21. *
  22. * Self-skips when the running kernel does not enforce Landlock. CI builds the launcher from
  23. * `native/system` before running this file.
  24. */
  25. const probe = spawnSync(launcherPath(), ['--probe'], { timeout: 5_000, encoding: 'utf8' })
  26. const landlockUsable = probe.status === 0
  27. /** The kernel's enforcement level from the probe report — stamped facts below must match it. */
  28. const enforcement = /partially enforced/.test(probe.stdout ?? '') ? 'partial' : 'full'
  29. let ctx: Context | undefined
  30. const tempDirs: string[] = []
  31. afterEach(async () => {
  32. await ctx?.fiber.dispose()
  33. ctx = undefined
  34. await Promise.all(tempDirs.splice(0).map(dir => rm(dir, { recursive: true, force: true })))
  35. })
  36. async function tempDir(base: string): Promise<string> {
  37. const dir = await mkdtemp(join(base, 'dsh-landlock-e2e-'))
  38. tempDirs.push(dir)
  39. return dir
  40. }
  41. async function sandboxedBash(workspace: string, mode: 'read-only' | 'workspace-write'): Promise<SandboxBashExecutor> {
  42. ctx = new Context()
  43. await ctx.plugin(LocalSandboxProvider, {})
  44. ;(ctx.sandbox as LocalSandboxProvider).internals = { probeBwrap: () => false }
  45. await ctx.plugin(SessionProjectionRegistry)
  46. await ctx.plugin(SandboxPolicyService, { mode, workspaceRoot: workspace })
  47. await ctx.plugin(LocalSubprocessRuntime)
  48. await ctx.plugin(SandboxBashExecutor, { cwd: workspace, timeoutMs: 30_000 })
  49. return ctx.shell as SandboxBashExecutor
  50. }
  51. describe.skipIf(!landlockUsable)('bash-sandbox: real Landlock confinement through ctx.shell', () => {
  52. it('read-only denies a write — the file must NOT exist, the result carries denial + enforcement facts', async () => {
  53. const workdir = await tempDir(tmpdir())
  54. const bash = await sandboxedBash(workdir, 'read-only')
  55. const result = await bash.run(bash.resolve({ command: `echo hi > ${workdir}/denied.txt` }))
  56. expect(result.exitCode).not.toBe(0)
  57. expect(result.sandbox).toEqual({ mode: 'read-only', denied: true, enforcement })
  58. expect(existsSync(join(workdir, 'denied.txt'))).toBe(false)
  59. })
  60. it('workspace-write lands a write inside the workspace root and still denies one beside it', async () => {
  61. const workdir = await tempDir(homedir())
  62. const outside = await tempDir(homedir())
  63. const bash = await sandboxedBash(workdir, 'workspace-write')
  64. const inside = await bash.run(bash.resolve({ command: `printf landlock-ok > ${workdir}/allowed.txt` }))
  65. expect(inside.exitCode).toBe(0)
  66. expect(inside.sandbox).toEqual({ mode: 'workspace-write', denied: false, enforcement })
  67. expect(readFileSync(join(workdir, 'allowed.txt'), 'utf8')).toBe('landlock-ok')
  68. const denied = await bash.run(bash.resolve({ command: `echo hi > ${outside}/denied.txt` }))
  69. expect(denied.exitCode).not.toBe(0)
  70. expect(denied.sandbox).toEqual({ mode: 'workspace-write', denied: true, enforcement })
  71. expect(existsSync(join(outside, 'denied.txt'))).toBe(false)
  72. })
  73. it('classifies a background denial once the task settles', async () => {
  74. const workdir = await tempDir(homedir())
  75. const bash = await sandboxedBash(workdir, 'read-only')
  76. const task = bash.start(bash.resolve({ command: `echo hi > ${workdir}/bg-denied.txt` }))
  77. await task.done
  78. expect(task.sandbox).toEqual({ mode: 'read-only', denied: true, enforcement })
  79. expect(existsSync(join(workdir, 'bg-denied.txt'))).toBe(false)
  80. })
  81. it('an approved escalated retry — the spec-level workspace-write override — lands the exact write read-only denied', async () => {
  82. const workdir = await tempDir(homedir())
  83. const bash = await sandboxedBash(workdir, 'read-only')
  84. const command = `printf escalated > ${workdir}/escalated.txt`
  85. const strict = await bash.run(bash.resolve({ command }))
  86. expect(strict.exitCode).not.toBe(0)
  87. expect(strict.sandbox).toEqual({ mode: 'read-only', denied: true, enforcement: enforcement })
  88. expect(existsSync(join(workdir, 'escalated.txt'))).toBe(false)
  89. const retried = await bash.run(bash.resolve({ command, sandboxPolicy: { mode: 'workspace-write', workspaceRoot: workdir } }))
  90. expect(retried.exitCode).toBe(0)
  91. expect(retried.sandbox).toEqual({ mode: 'workspace-write', denied: false, enforcement: enforcement })
  92. expect(readFileSync(join(workdir, 'escalated.txt'), 'utf8')).toBe('escalated')
  93. })
  94. })