* feat(debugger): install debug session deps from the instance registry settings
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(debugger): keep install-time registry credentials out of the session-visible tree
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: drop em dashes from the debugger registry docs and comments
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(debugger): stop installing for a session that went away during the settings fetch
Also serves nativets sessions the npm settings their installer reads.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: register mounted CA certificates in windmill_extra at startup
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: only claim a CA update when update-ca-certificates can read the mount
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: detect mounted CA certificates the way update-ca-certificates finds them
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`windmill prepare-deps` was spawned with Bun's raw `spawn` in both the Python and
the TypeScript session, bypassing the nsjail wrapping the debugged script itself
gets. With ENABLE_NSJAIL=true, `uv pip install` (source distributions run their
build backend) and `bun install` (postinstall scripts) therefore executed
package-supplied code unconfined, next to the LSP, multiplayer and gateway
services in the windmill-extra container.
Both installers now go through the same nsjail wrapper as the debuggee, which the
two files no longer build separately. The jail keeps the environment
(`keep_env`), which is what carries the registry credentials and CA settings into
the installer; the debugged script's environment is unchanged and still holds
neither.
Killing the installer also did not reap the `uv` or `bun` it had spawned: those
were reparented to init and kept downloading, so both the timeout and the
cancel-on-disconnect only half-worked. The installer now runs in its own process
group and is signalled as a group, reading the group id back from /proc rather
than assuming it, since a group kill aimed at the service's own group would take
down every service in the container.
Two things that cancellation exposed: a kill was reported to the client as an
install failure, since it ends the read with nothing to parse - blaming the user
for their own Stop; and the standalone Bun server's close handler only dropped
the session from its map, so nothing there was ever cleaned up. The teardown flag
is also scoped to a launch rather than the session, because cleanup() runs when a
program finishes normally too.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: forward proxy and TLS settings to debugger subprocesses
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: reach uv and the bun debugger with the forwarded network settings
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: map every CA variable spelling onto the one uv reads
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep package-index credentials out of debugged user code
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: install debugger dependencies outside the interpreter running user code
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: sandbox and bound the debugger dependency installer
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: correct the installer timeout rationale
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: scope the uv --cert note to the commands prepare-deps runs
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: build the debug venv against the interpreter that runs the script
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: do not start the debuggee for a session that already went away
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: remove the debug script when the session is gone before it starts
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(debugger): return PrepareResult when the service prepared the venv
`prepare_dependencies()` returns `PrepareResult` on every path except the
service-prepared short-circuit, which returned the venv path as a bare `str`.
`handle_launch` reads `prepared.error` on it, so every Python session whose
script has a third-party import raised `AttributeError`, hung, and failed at
180s with `Debugpy command timeout: launch`.
The two consumers of a prepare-deps response also read a `stderr` key the CLI
does not emit; the field is `install_stderr`, and it carries the same text
`error` already wraps in a sentence, so take one rather than joining both.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(debugger): keep the failing step in the launch message, drop the dead branch
Preferring the raw `install_stderr` made `_first_line` pick uv's opening
progress line, so a refused launch reported "Using Python 3.12.13 environment
at: venv" — which reads like success. `error` is the same text prefixed with the
step that failed, so it is the better of the two to condense.
The installer-diagnostics pass over a `success: true` response is unreachable:
every `success: true` site in prepare_deps.rs sets `install_stderr: None`, and
its comment claimed the opposite of what that file documents. It existed to work
around a producer that warned and returned success on a failed `uv pip install`;
that producer now returns `success: false`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: report python debugger dependency install failures instead of timing out
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: surface swallowed installer errors and stream debugger prepare progress
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: reap the python debugger on a failed launch and bound prepare-deps
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: match uv failure output by stripping progress instead of matching errors
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: treat uv build, download and warning lines as install progress
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: honor python index settings in prepare-deps and report install failures
* fix: forward python registry env to the debugger's prepare-deps
* fix: scope registry credentials to the prepare-deps subprocess
* fix: install python debug dependencies from the service, not the session
* fix: bound the debugger dependency install and keep the proxy bypass default
* docs: name the nsjail config that isolates debug sessions
The /ws_debug debugger WebSocket gated JWT signature verification on inline
`code` being present (`if (code && REQUIRE_SIGNED_REQUESTS)`), so a
`program`-mode launch (naming an arbitrary server-side file path that is read
and executed) skipped verification entirely — even with
REQUIRE_SIGNED_DEBUG_REQUESTS=true. The WS handshake also performed no Origin
check, allowing cross-origin (CSWSH) drive-by from a malicious page.
- Enforce signing on every launch in both handlers (Python + Bun/TS): reject
program-mode outright and require+verify a token for inline code.
- Add opt-in DEBUG_ALLOWED_ORIGINS allowlist enforced at the WS handshake.
- Default docker-compose REQUIRE_SIGNED_DEBUG_REQUESTS to true.
- Update THREAT_MODEL T8/EP15 to reflect the root cause and mitigation.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirrors the main Windmill Dockerfile pattern: creates a windmill user
(UID/GID 1000) and makes cache/work directories world-writable so the
image runs cleanly under Kubernetes securityContext.runAsNonRoot or
runAsUser: 1000 without permission errors on Bun, pip, or windmill
cache writes.
Fixes WIN-1969
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Fix debugger HTTP health endpoint to also match /ws_debug/health
(ingress forwards the full path, not just /health)
- Add request logging to all three extra services (LSP, multiplayer,
debugger) for HTTP and WebSocket ping/upgrade events
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add ws_base_url instance setting to override WebSocket base URL
Allow deployments behind reverse proxies to route WebSocket traffic
(LSP, debugger, multiplayer) to a different host/port than the main
frontend via a new instance setting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: move ws_base_url to Advanced section with toggle and connectivity test
- Move setting from Core to Advanced > WebSocket section
- Render as toggle "Custom websocket base url from frontend to
multiplayer/lsp/debugger" with conditional URL text field
- Add Test connectivity button (always visible) that checks HTTP health
and WebSocket ping for all three services (LSP, Multiplayer, Debugger)
- Add /ws/ping and /ws/health endpoints to LSP service
- Add /ws_mp/health HTTP and __ping__ WS handlers to multiplayer service
- Add /ping WS handler to debugger service
- Add CORS headers to health endpoints for cross-origin testing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: toggle enabled check and testWs promise resolution
- Fix enabled derived to check only for null (not empty string),
otherwise the toggle never turns on since toggleEnabled sets ''
- Fix testWs onclose handler to resolve(false) so the promise
doesn't hang if the server closes without sending a message
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: make connectivity test work with existing services
- HTTP test: accept plain text "ok"/"okay" (old services) in addition
to JSON {"status": "ok"} (new services), reject HTML (SPA fallback)
- WS test: resolve on onopen (connection established) instead of
waiting for a specific pong message, so the test works even with
services that don't have the new /ping handler yet
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: make nsjail available in all standard images (CE)
Include nsjail binary and runtime deps in the main Dockerfile and
DockerfileSlim so sandboxing is available out of the box. Flip
DISABLE_NSJAIL default to false so nsjail is enabled by default.
Remove DockerfileNsjail (now redundant) and the build_ee_nsjail CI job,
pointing publish_ecr_s3 at the base EE image instead. Add iptables to
DockerfileFullEe to preserve the functionality from the removed nsjail
image.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* revert: keep DISABLE_NSJAIL default as true
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: pin publish_ecr_s3 to exact commit hash
Add type=sha tag to build_ee so it pushes a commit-pinned image tag.
Restore git hash lookup in publish_ecr_s3 to reference the exact image
for that commit, avoiding race conditions with the mutable dev tag.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: publish_ecr_s3 depends on build_ee_full, uses release tag
Only publish to S3 on tag releases, extracting static frontend from the
ee-full image using the semver tag.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: remove stale windmill-ee-nsjail references, add nsjail to EE slim
The windmill-ee-nsjail image is no longer published since DockerfileNsjail
was deleted. Update all references to use the base EE image (which now
includes nsjail), remove redundant nsjail deps from DockerfileExtra, and
add nsjail build to DockerfileSlimEe for consistency with CE slim.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
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>
The nsjail debugger mode was failing with "chdir('/tmp'): No such file
or directory" because without a config file, nsjail uses minimal mounts
that don't include /tmp or other necessary directories.
Added nsjail.debug.config.proto with proper mounts:
- /bin, /lib, /lib64, /usr, /etc (system directories)
- /tmp as tmpfs (for script execution)
- /dev/null, /dev/random, /dev/urandom (device nodes)
- /root as tmpfs (for bun cache)
Also updated:
- entrypoint-extra.sh: Pass --nsjail-config when ENABLE_NSJAIL=true
- DockerfileExtra: Copy nsjail config, update ports to 3003
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The public key decoding from JWKS was missing base64url padding, causing
JWT signature verification to fail with "invalid jwt token" errors in
production. The `jwk.x` value needs proper padding before base64 decoding.
Fixed by using the existing `base64urlDecode` helper function which
correctly adds padding, instead of manually doing the conversion.
This resolves JWT verification failures when REQUIRE_SIGNED_DEBUG_REQUESTS
is enabled.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updates the debugger service to use port 3003 instead of 5679 across
all configuration files, documentation, and code references. This aligns
the debugger with the other windmill-extra services which use ports
3001 (LSP) and 3002 (Multiplayer).
Changes:
- docker-compose.yml: Update port exposure and add DEBUGGER_PORT env
- docker/entrypoint-extra.sh: Change default port from 5679 to 3003
- debugger/dap_debug_service.ts: Update default port in code and docs
- debugger/README.md: Update port documentation
- debugger/test_debug_service.ts: Update test URLs
- docker/test_windmill_extra.ts: Update test configuration
- .github/workflows/publish_extra.yml: Update test container ports
- frontend/src/lib/components/debug/*: Update frontend examples and defaults
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>