From cacb41fe283035d97492ce32a669e7ef058c7c97 Mon Sep 17 00:00:00 2001 From: Julian lechner Date: Fri, 18 Sep 2026 16:10:17 +0200 Subject: [PATCH] fix(claude): disable unavailable Windows sandbox --- adapters/claude/config/settings.json | 6 +----- scripts/checks/validate-config.py | 4 ++-- scripts/commands/build.ps1 | 3 ++- scripts/commands/build.sh | 3 +++ scripts/tests/test-shell-packages.ps1 | 4 +++- scripts/tests/test-shell-packages.sh | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/adapters/claude/config/settings.json b/adapters/claude/config/settings.json index fa833ab..5c72ea9 100644 --- a/adapters/claude/config/settings.json +++ b/adapters/claude/config/settings.json @@ -11,11 +11,7 @@ "Bash(Remove-Item -Recurse*)" ] }, - "sandbox": { - "enabled": true, - "allowUnsandboxedCommands": false, - "failIfUnavailable": true - }, + "sandbox": __CLAUDE_SANDBOX__, "env": { "CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY": "__CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY__" }, diff --git a/scripts/checks/validate-config.py b/scripts/checks/validate-config.py index c2f40a8..c1e1f48 100644 --- a/scripts/checks/validate-config.py +++ b/scripts/checks/validate-config.py @@ -40,8 +40,8 @@ def validate_claude(path: Path) -> None: if not isinstance(permissions.get(key), list) or not all(isinstance(item, str) for item in permissions[key]): fail(f"permissions.{key} must be an array of strings") sandbox = require_keys(settings.get("sandbox"), CLAUDE_SANDBOX_KEYS, "sandbox") - if sandbox.get("enabled") is not True or sandbox.get("allowUnsandboxedCommands") is not False or sandbox.get("failIfUnavailable") is not True: - fail("sandbox must be enabled, fail closed, and disallow unsandboxed command retries") + if sandbox != {"enabled": False} and sandbox != {"enabled": True, "allowUnsandboxedCommands": False, "failIfUnavailable": True}: + fail("sandbox must be disabled or enabled with fail-closed settings") env = settings.get("env") if not isinstance(env, dict) or not all(isinstance(key, str) and isinstance(value, str) for key, value in env.items()): fail("env must be an object of string values") diff --git a/scripts/commands/build.ps1 b/scripts/commands/build.ps1 index 5f403ea..ef1a53b 100644 --- a/scripts/commands/build.ps1 +++ b/scripts/commands/build.ps1 @@ -194,9 +194,10 @@ foreach ($shell in @('powershell','bash')) { $script = if ($shell -eq 'powershell') { 'flashbang.ps1' } else { 'flashbang.sh' } $statusLineScript = if ($shell -eq 'powershell') { 'statusline.ps1' } else { 'statusline.sh' } $content = Get-Content -LiteralPath $path -Raw + $sandbox = if ($shell -eq 'powershell') { '{"enabled": false}' } else { '{"enabled": true, "allowUnsandboxedCommands": false, "failIfUnavailable": true}' } $compactScript = if ($shell -eq 'powershell') { 'Record-Compact.ps1' } else { 'record-compact.sh' } $pointerScript = if ($shell -eq 'powershell') { 'Show-SessionStatePointer.ps1' } else { 'show-session-state-pointer.sh' } - $content = $content.Replace('__HOOK_COMMAND__', $command).Replace('__POWERSHELL_HOOK_COMMAND__', $command).Replace('__HOOK_SCRIPT__', $script).Replace('__POWERSHELL_HOOK_SCRIPT__', $script).Replace('__COMPACT_SCRIPT__', $compactScript).Replace('__SESSION_POINTER_SCRIPT__', $pointerScript).Replace('__STATUSLINE_COMMAND__', $command).Replace('__STATUSLINE_SCRIPT__', $statusLineScript) + $content = $content.Replace('__HOOK_COMMAND__', $command).Replace('__POWERSHELL_HOOK_COMMAND__', $command).Replace('__HOOK_SCRIPT__', $script).Replace('__POWERSHELL_HOOK_SCRIPT__', $script).Replace('__COMPACT_SCRIPT__', $compactScript).Replace('__SESSION_POINTER_SCRIPT__', $pointerScript).Replace('__STATUSLINE_COMMAND__', $command).Replace('__STATUSLINE_SCRIPT__', $statusLineScript).Replace('__CLAUDE_SANDBOX__', $sandbox) Set-Content -LiteralPath $path -Value $content -Encoding UTF8 } diff --git a/scripts/commands/build.sh b/scripts/commands/build.sh index 889c231..d054d62 100644 --- a/scripts/commands/build.sh +++ b/scripts/commands/build.sh @@ -200,6 +200,8 @@ for client in codex claude; do fi path="$output/$client-$shell/$file" content=$(<"$path") + sandbox='{"enabled": true, "allowUnsandboxedCommands": false, "failIfUnavailable": true}' + [ "$shell" != powershell ] || sandbox='{"enabled": false}' content=${content//__HOOK_COMMAND__/$command} content=${content//__POWERSHELL_HOOK_COMMAND__/$command} content=${content//__HOOK_SCRIPT__/$script} @@ -208,6 +210,7 @@ for client in codex claude; do content=${content//__SESSION_POINTER_SCRIPT__/$pointer_script} content=${content//__STATUSLINE_COMMAND__/$command} content=${content//__STATUSLINE_SCRIPT__/$statusline_script} + content=${content//__CLAUDE_SANDBOX__/$sandbox} printf '%s\n' "$content" > "$path" done diff --git a/scripts/tests/test-shell-packages.ps1 b/scripts/tests/test-shell-packages.ps1 index e96b6ad..2d4ab2f 100644 --- a/scripts/tests/test-shell-packages.ps1 +++ b/scripts/tests/test-shell-packages.ps1 @@ -48,7 +48,9 @@ foreach ($shell in @('powershell','bash')) { throw "Claude finish hook is incorrect in $package" } elseif ($content -notmatch '"defaultMode"\s*:\s*"auto"') { throw "Claude auto permission mode is missing in $package" - } elseif (-not $settings.sandbox.enabled -or $settings.sandbox.allowUnsandboxedCommands -ne $false -or $settings.sandbox.failIfUnavailable -ne $true) { + } elseif ($shell -eq 'powershell' -and ($settings.sandbox.enabled -ne $false -or $settings.sandbox.PSObject.Properties.Count -ne 1)) { + throw "Claude sandbox must be disabled in $package" + } elseif ($shell -eq 'bash' -and (-not $settings.sandbox.enabled -or $settings.sandbox.allowUnsandboxedCommands -ne $false -or $settings.sandbox.failIfUnavailable -ne $true)) { throw "Claude strict sandbox is missing in $package" } elseif (-not (Test-Path -LiteralPath (Join-Path $package "statusline/statusline.$(if ($shell -eq 'powershell') { 'ps1' } else { 'sh' })")) -or $settings.statusLine.command -notmatch "statusline\.$(if ($shell -eq 'powershell') { 'ps1' } else { 'sh' })") { throw "Claude status line is incorrect in $package" diff --git a/scripts/tests/test-shell-packages.sh b/scripts/tests/test-shell-packages.sh index e62c9ca..ff34567 100644 --- a/scripts/tests/test-shell-packages.sh +++ b/scripts/tests/test-shell-packages.sh @@ -65,7 +65,7 @@ for shell in powershell bash; do ! grep -q 'flashbang-if-input' "$package/$file" ! grep -q '"async"[[:space:]]*:[[:space:]]*true' "$package/$file" grep -q '"defaultMode"[[:space:]]*:[[:space:]]*"auto"' "$package/$file" - python3 -c 'import json,sys; settings=json.load(open(sys.argv[1], encoding="utf-8-sig")); assert settings["sandbox"] == {"enabled": True, "allowUnsandboxedCommands": False, "failIfUnavailable": True}' "$package/$file" + python3 -c 'import json,sys; settings=json.load(open(sys.argv[1], encoding="utf-8-sig")); expected={"enabled": False} if sys.argv[2] == "powershell" else {"enabled": True, "allowUnsandboxedCommands": False, "failIfUnavailable": True}; assert settings["sandbox"] == expected' "$package/$file" "$shell" statusline_extension='sh' [ "$shell" != powershell ] || statusline_extension=ps1 test -f "$package/statusline/statusline.$statusline_extension"