fix(debugger): fix nsjail sandbox for debugger execution

Multiple fixes to make nsjail work correctly:

1. Use absolute paths for python3 and bun binaries (/usr/bin/python3,
   /usr/bin/bun) since nsjail's execve doesn't use PATH

2. Update cwd to use temp directory when code is written there, so
   nsjail can find the script files (was using /debugger as cwd before)

3. Bind-mount /tmp from host instead of using tmpfs, so the temp
   directories with scripts are accessible inside the sandbox

4. Add /debugger directory mount so Python debugger server script
   is accessible inside nsjail

5. Add PATH environment variable to nsjail config

All debugger tests now pass with ENABLE_NSJAIL=true.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-01-14 11:43:38 +00:00
parent 31c07d9352
commit 14cfce3fd6
3 changed files with 24 additions and 11 deletions

View File

@@ -76,8 +76,8 @@ function parseConfig(): ServiceConfig {
binaryPath: process.env.DAP_NSJAIL_PATH || 'nsjail',
extraArgs: []
},
pythonPath: process.env.DAP_PYTHON_PATH || 'python3',
bunPath: process.env.DAP_BUN_PATH || 'bun',
pythonPath: process.env.DAP_PYTHON_PATH || '/usr/bin/python3',
bunPath: process.env.DAP_BUN_PATH || '/usr/bin/bun',
windmillPath: process.env.DAP_WINDMILL_PATH,
debug: process.env.DAP_DEBUG === 'true'
}
@@ -355,7 +355,7 @@ function spawnProcess(options: SpawnOptions): Subprocess {
// Add any extra nsjail arguments
nsjailCmd.push(...config.nsjail.extraArgs)
// Add working directory binding if specified
// Add working directory if specified
if (options.cwd) {
nsjailCmd.push('--cwd', options.cwd)
}
@@ -892,7 +892,7 @@ class PythonDebugSession extends BaseDebugSession {
const args = request.arguments || {}
let code = args.code as string | undefined
this.scriptPath = args.program as string | undefined
const cwd = (args.cwd as string) || process.cwd()
let cwd = (args.cwd as string) || process.cwd()
this.callMain = (args.callMain as boolean) || false
this.mainArgs = (args.args as Record<string, unknown>) || {}
this.envVars = (args.env as Record<string, string>) || {}
@@ -950,6 +950,9 @@ sys.stdout.flush()
this.tempFile = join(this.tempDir, 'script.py')
await writeFile(this.tempFile, code)
this.scriptPath = this.tempFile
// Use temp directory as cwd so debugger can find the script
cwd = this.tempDir
logger.info(`Wrote Python code to ${this.tempFile}, cwd=${cwd}`)
} catch (error) {
this.sendResponse(request, false, {}, `Failed to create temp file: ${error}`)
return

View File

@@ -619,7 +619,7 @@ export class DebugSession {
private nsjailConfig?: NsjailConfig
// Custom bun binary path (can be overridden)
private bunPath: string = 'bun'
private bunPath: string = '/usr/bin/bun'
// Windmill binary path for prepare-deps CLI (optional, for dependency installation)
private windmillPath?: string
@@ -630,7 +630,7 @@ export class DebugSession {
constructor(ws: WebSocket, options?: { nsjailConfig?: NsjailConfig; bunPath?: string; windmillPath?: string }) {
this.ws = ws
this.nsjailConfig = options?.nsjailConfig
this.bunPath = options?.bunPath || 'bun'
this.bunPath = options?.bunPath || '/usr/bin/bun'
this.windmillPath = options?.windmillPath
}
@@ -1314,7 +1314,7 @@ export class DebugSession {
const args = request.arguments || {}
let code = args.code as string | undefined
this.scriptPath = args.program as string | undefined
const cwd = (args.cwd as string) || process.cwd()
let cwd = (args.cwd as string) || process.cwd()
this.callMain = (args.callMain as boolean) || false
this.mainArgs = (args.args as Record<string, unknown>) || {}
this.envVars = (args.env as Record<string, string>) || {}
@@ -1393,7 +1393,9 @@ export class DebugSession {
this.tempFile = join(this.tempDir, 'script.ts')
await writeFile(this.tempFile, code)
this.scriptPath = this.tempFile
logger.info(`Wrote code to ${this.tempFile}`)
// Use temp directory as cwd so bun can find the script and node_modules
cwd = this.tempDir
logger.info(`Wrote code to ${this.tempFile}, cwd=${cwd}`)
// Log lines around breakpoint for debugging
const lines = code.split('\n')
for (let i = 24; i < Math.min(30, lines.length); i++) {

View File

@@ -55,12 +55,19 @@ mount {
mandatory: false
}
# Temporary filesystem for /tmp (writable)
# Bind-mount /tmp from host (job directories are created here)
mount {
src: "/tmp"
dst: "/tmp"
fstype: "tmpfs"
is_bind: true
rw: true
options: "size=500000000"
}
# Debugger scripts directory (for Python debugger server)
mount {
src: "/debugger"
dst: "/debugger"
is_bind: true
}
# Device nodes
@@ -94,3 +101,4 @@ iface_no_lo: true
envar: "HOME=/root"
envar: "TMPDIR=/tmp"
envar: "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"