Cloudflare was honoring the VM client's Accept-Encoding: gzip header and
returning compressed responses, but the proxy didn't forward the
Content-Encoding header, causing Claude Code to fail with "Failed to
parse JSON" when trying to parse raw gzip bytes as JSON.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add qemu-user-static, binfmt-support, and mosquitto-clients to template
- Auto-update Claude Code or OpenCode to latest version before each session
- Add LSP plugins, ENABLE_LSP_TOOL support, and clangd/gopls/pyright/ts-lsp
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously _claude_vm_start_github_mcp bailed out immediately if the
repo had no origin remote. Now the main repo is optional — if there's
no remote, we still scan .gitmodules for GitHub submodule URLs and
request tokens for those. Also show all configured repos in the scope
message instead of just the main repo.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
If limactl delete leaves behind a directory with a missing lima.yaml
(e.g. after a Lima crash), all future limactl commands for that
instance break. Fall back to rm -rf to ensure the next VM creation
starts clean.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Detect GitHub submodules from .gitmodules, request a token for each,
and pass a JSON dict of {repo: token} to both proxies. The git proxy
injects per-repo Basic auth based on request path. The MCP proxy
selects the correct Bearer token based on owner/repo in tool call
arguments. Both proxies fall back to single-repo env vars for
backward compatibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
USB passthrough (--usb flag) resolves /dev/ttyACM0 or VID:PID to sysfs,
unbinds host drivers, wraps QEMU to inject -device usb-host args, and
rebinds on cleanup. The non-cloud kernel (linux-image-amd64) is needed
because the default cloud kernel lacks USB/serial drivers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Expiring user tokens with refresh broke when multiple repos were used:
each device flow revoked previous refresh tokens, causing 500 errors
on every refresh attempt. Switched to non-expiring tokens (opt out of
"User-to-server token expiration" in the GitHub App settings) which
coexist across multiple device flows without revoking each other.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add clipboard-pty.py, a pty wrapper that detects Ctrl+V (0x16) in the
host terminal input stream and saves the host clipboard image as a PNG
to the shared mount. Inside the VM, xclip and wl-paste shims read that
file, so Claude Code and OpenCode pick up pasted images transparently.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Uses pbcopy/xclip/xsel/wl-copy if available. Only shows
"(copied to clipboard)" when a tool was found and succeeded.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of forwarding raw HTML error pages from GitHub to the client
(which dumps unreadable HTML on screen), save them to timestamped files
and retry with exponential backoff (1s, 2s, 4s, up to 4 attempts).
After exhausting retries, return a clean short error message.
Applies to all three GitHub-facing scripts:
- github-mcp-proxy.py (MCP tool calls)
- github-git-proxy.py (git push/pull)
- github_app_token_demo.py (OAuth token generation)
Also fixes connection leaks on exception paths in both proxy files
and adds URLError handling in the token demo for network-level failures.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On a random port (bind to 0), there is no TIME_WAIT issue, so
SO_REUSEADDR is unnecessary and could allow another process to
bind to the same port and intercept traffic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Security fixes from comprehensive audit:
- CRITICAL: Add path allowlist (/v1/ only) to prevent VM accessing
arbitrary endpoints on api.anthropic.com with injected credentials
- HIGH: Sanitize upstream error messages (no exception details to VM)
- HIGH: Add 32MB request body size limit to prevent memory exhaustion
- HIGH: Restrict log file permissions to 0o600
- MEDIUM: Allowlist upstream response headers forwarded to VM (strip
Set-Cookie, Server, and other potentially sensitive headers)
- MEDIUM: Restrict HTTP methods to GET and POST only
- LOW: Validate Content-Length (reject negative/non-numeric values)
- LOW: Add 60s inbound connection timeout (Slowloris defense)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace curl_cffi dependency with Python stdlib http.client for OAuth
token refresh against platform.claude.com. Verified reliable over 10
consecutive refreshes with token rotation.
Security hardening from code review:
- Fix connection leak: use try/finally for conn.close()
- Fix permission window: chmod temp file before atomic replace
- Fix credential leak: generic error messages to VM, details to logs only
- Fix lock contention: fast-path token check outside refresh lock
- Replace fragile ":" in host heuristic with explicit _token_use_tls flag
- Log credential save failures to stderr (not just debug)
Also adds test_proxy_refresh.py with 32 unit tests covering token
expiry, refresh flow, credential save/restore, and error paths.
Real endpoint test gated behind RUN_REAL_TESTS=1 env var.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- github-git-proxy.py: host-side HTTP proxy that injects GitHub token
(Basic auth) for the configured repo, forwards to github.com over HTTPS.
Other repos are forwarded without credentials.
- Integrate into claude-vm.sh: starts alongside MCP proxy, configures
git url.insteadOf (scoped to repo only), copies user.name/email from host
- Writes CLAUDE.md instructions in VM so Claude knows git push/pull works
- Logs to file (not stderr), with GITHUB_GIT_PROXY_DEBUG env var
- Update README with full GitHub integration docs (token generation,
git proxy, MCP proxy, defense-in-depth, configuration)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The VM is already sandboxed, so the interactive confirmation for
--dangerously-skip-permissions is unnecessary. Pass IS_SANDBOX=1
env var when launching claude CLI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove unused github_app_token.sh (only github_app_token_demo.py is
used by claude-vm.sh)
- Document token generation flow: GitHub App device flow with
repository_id scoping
- Rename "Security layers" to "Defense-in-depth" to clarify these
are additional layers on top of GitHub's token scoping
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of storing OAuth tokens inside the VM template, credentials now
stay on the host. A lightweight Python proxy (claude-vm-proxy.py) reads
the host's ~/.claude/.credentials.json, injects the real auth headers
(Bearer + oauth beta for OAuth, x-api-key for API keys), and forwards
requests to api.anthropic.com. The VM gets only ANTHROPIC_BASE_URL
pointing at the proxy and dummy credentials so Claude Code detects
the correct subscription tier.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Snapshot security-sensitive files before each VM session and check for
unauthorized modifications afterward. Mount .git/hooks and .git/config
as read-only at the Lima level to prevent the VM from tampering with
host-side git hooks or config.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>