Files
windmill/backend/windmill-worker/src/lib.rs
T
Ruben FiszelandClaude Opus 4.8 1727271e19 feat: sandboxed daemonless container runtime via '# sandbox <image>' (#9453)
* feat: add sandboxed docker v2 runtime via '# docker <image>'

Run a container image as a subprogram of the job's own nsjail sandbox:
extract the image rootfs with podman (rootless) and run it chrooted inside the
job's nsjail, so the container inherits the job's confinement and is safe under
nsjail / for untrusted code. Selected by '# docker <image>'; a bare '# docker'
keeps the v1 (dind) path untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: default to daemonless docker (drop dind from compose, allow docker on cloud)

docker-compose no longer ships the dind sidecar (v2 is daemonless: podman + nsjail
in the worker); removed the dind service, DOCKER_HOST env, depends_on and volume.
Removed the language-picker guard that blocked Docker scripts on the multi-tenant
platform, now that v2 makes docker safe to run sandboxed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: select sandboxed container via # sandbox <image>; add pull policy + size guards

- Surface moved from '# docker <image>' to '# sandbox <image>' (groups under the
  sandbox annotation; '# docker' stays v1-only, '# sandbox' stays nsjail-bash).
- SANDBOX_IMAGE_PULL_POLICY (default 'newer') so moving tags don't go stale.
- SANDBOX_IMAGE_MAX_SIZE_MB rejects oversized images before extraction.
- SANDBOX_IMAGE_CACHE_MAX_MB best-effort LRU eviction of podman's image store.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(sandbox): support # volume, honor nsjail tmp instance settings, v2 docker template

- Thread shared_mount into the sandbox container nsjail config so '# volume' mounts
  (and the same-worker /tmp/shared folder) apply inside the container.
- Use resolve_nsjail_tmp_mount_block for the container's /tmp so it honors the same
  nsjail_tmp_backing / nsjail_tmpfs_size_mb instance settings as other nsjail jobs.
- docker-compose comment + the editor's Docker template now use '# sandbox <image>'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(sandbox): make image size/cache/pull-policy UI instance settings

Convert SANDBOX_IMAGE_* from worker env vars to DB-backed instance settings
(sandbox_image_max_size_mb, sandbox_image_cache_max_mb, sandbox_image_pull_policy),
hot-reloaded via the same mechanism as nsjail_tmpfs_size_mb and configurable in
#superadmin-settings. No worker restart needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(sandbox): windmill-managed registry — default registry + private auth

Two new instance settings:
- sandbox_image_default_registry: prepended to unqualified image refs (alpine ->
  <registry>/alpine); fully-qualified refs untouched.
- sandbox_registry_auth: docker/podman auth.json blob written to a per-job authfile
  (0600, removed with the job) and passed to podman --authfile for private registries.
Both hot-reloaded and configurable in #superadmin-settings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(sandbox): protobuf-safe proto_str escaper, atomic 0600 authfile, registry tests

Addresses local-review P2s: proto_str now emits valid protobuf octal escapes for
control/non-ASCII bytes (not Rust \u{..} that nsjail would reject); the registry
authfile is created 0600 atomically (no world-readable window); add a
registry_qualified table test + a non-ASCII proto_str case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(sandbox): P0 — deliver image env via nsjail envar:, never the launcher process env

CI review (P0): the image's OCI Env (attacker-controlled keys+values) was applied to
the nsjail launcher process via .envs(), so a hostile image could set LD_PRELOAD/
LD_LIBRARY_PATH/LD_AUDIT on nsjail itself and execute code as the worker outside the
jail. Now the image env is rendered as proto-escaped 'envar:' directives (child-only)
and nsjail's process env carries only windmill-trusted keys (reserved vars + proxy).
Also: warn instead of silently bypassing the size guard on inspect failure; reset the
eviction guard via a Drop guard (no stuck flag on panic/early-return). +render_envars test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(sandbox): P0 symlink-write escape via rootfs script; P1 redact registry-auth logging

CI review:
- P0 (Codex): the body was written into the image-controlled rootfs as
  .windmill_docker_main.sh via write_file (follows symlinks) — a hostile image could
  plant that path as a symlink to a host file and capture the worker's write before
  nsjail starts. Now the body is passed straight to 'sh -c <body> sh <args>'; no file
  is written into the rootfs at all.
- P1 (Codex): sandbox_registry_auth flowed through the generic setting loader which
  logs the value (raw auth.json credentials). Replaced with a secret-aware reload that
  loads directly and logs only a redacted 'configured=' message.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(sandbox): redact sandbox_registry_auth in instance-settings write log too

The settings API also logs 'Set global setting <key> to <value>' via format_setting_value;
add sandbox_registry_auth to SENSITIVE_SETTINGS so the credential is redacted there as
well as on reload.

* fix(sandbox): don't silently disable cache eviction on podman images parse error

Re-review (cubic/Claude P2): serde_json::from_slice(...).unwrap_or_default() meant any
parse hiccup (e.g. podman omitting Size/Created via omitempty for a zero value, or
schema drift) silently degraded to an empty Vec and disabled eviction with no log.
Now Size/Created are #[serde(default)] (a missing omitempty key -> 0, not a whole-array
parse failure) and a real parse error warns + breaks instead of being swallowed.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 08:35:51 +00:00

116 lines
3.0 KiB
Rust

#[cfg(all(feature = "enterprise", feature = "bigquery"))]
mod bigquery_executor;
#[cfg(all(feature = "enterprise", feature = "mssql"))]
mod mssql_executor;
#[cfg(feature = "enterprise")]
mod snowflake_executor;
mod agent_workers;
#[cfg(feature = "python")]
mod ansible_executor;
mod bash_executor;
mod pwsh_executor;
#[cfg(feature = "java")]
mod java_executor;
#[cfg(feature = "ruby")]
mod ruby_executor;
#[cfg(feature = "rlang")]
mod r_executor;
mod ai;
mod ai_executor;
mod bun_executor;
pub mod common;
mod config;
mod csharp_executor;
#[cfg(feature = "private")]
mod dedicated_worker_ee;
mod dedicated_worker_oss;
mod deno_executor;
mod docker_v2;
#[cfg(feature = "duckdb")]
mod duckdb_executor;
mod global_cache;
mod go_executor;
mod graphql_executor;
mod handle_child;
pub mod job_logger;
#[cfg(feature = "private")]
pub mod job_logger_ee;
mod job_logger_oss;
mod js_eval;
pub mod memory_common;
#[cfg(feature = "private")]
pub mod memory_ee;
pub mod memory_oss;
#[cfg(feature = "mysql")]
mod mysql_executor;
#[cfg(feature = "nu")]
mod nu_executor;
#[cfg(feature = "oracledb")]
mod oracledb_executor;
#[cfg(feature = "private")]
pub mod otel_ee;
mod otel_oss;
#[cfg(all(feature = "private", feature = "enterprise"))]
mod otel_tracing_proxy_ee;
mod otel_tracing_proxy_oss;
pub mod pg_executor;
mod pg_raw_output;
#[cfg(feature = "php")]
mod php_executor;
mod prepare_deps;
#[cfg(feature = "python")]
mod python_executor;
#[cfg(feature = "python")]
mod python_versions;
pub mod result_processor;
#[cfg(feature = "rust")]
mod rust_executor;
mod sanitized_sql_params;
mod schema;
mod sql_s3_input;
pub mod sql_utils;
mod universal_pkg_installer;
#[cfg(feature = "private")]
mod volume_ee;
mod volume_oss;
pub mod wac_executor;
mod worker;
mod worker_flow;
mod worker_lockfiles;
mod worker_utils;
#[cfg(all(feature = "private", feature = "enterprise"))]
pub use otel_tracing_proxy_ee::start_jobs_otel_tracing;
#[cfg(all(feature = "private", feature = "enterprise", feature = "deno_core"))]
pub use otel_tracing_proxy_ee::{load_internal_otel_exporter, DENO_OTEL_INITIALIZED};
pub use worker::*;
pub use bun_executor::{
build_loader, compute_bundle_local_and_remote_path, ensure_bundle_output_exists,
generate_bun_bundle, get_common_bun_proc_envs, install_bun_lockfile, prebundle_bun_script,
prepare_job_dir, LoaderMode, BUN_DEDICATED_WORKER_ARGS, RELATIVE_BUN_BUILDER,
RELATIVE_BUN_LOADER,
};
#[cfg(any(feature = "private", test))]
pub use bun_executor::{
compute_ts_codegen, generate_multi_script_wrapper, TsScriptCodegen, TsScriptEntry,
};
#[cfg(any(feature = "private", test))]
pub use deno_executor::generate_dedicated_worker_wrapper as generate_deno_dedicated_worker_wrapper;
pub use deno_executor::{generate_deno_lock, DENO_UNSTABLE_ARGS};
pub use prepare_deps::run_prepare_deps_cli;
#[cfg(all(feature = "python", any(feature = "private", test)))]
pub use python_executor::{
compute_py_codegen, generate_multi_script_wrapper as generate_py_multi_script_wrapper,
PyScriptCodegen, PyScriptEntry,
};
#[cfg(feature = "python")]
pub use python_versions::PyV;