feat: add force_sandboxing global setting and #sandbox bash annotation (#7816)

* feat: add force_sandboxing global setting and #sandbox bash annotation

Add a new global setting `force_sandboxing` to enable nsjail sandboxing
for all jobs, overriding the DISABLE_NSJAIL environment variable.
Also add a #sandbox annotation for bash scripts to enable sandboxing
on a per-script basis.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: decouple nsjail probing from DISABLE_NSJAIL and apply force_sandboxing to all executors

NSJAIL_AVAILABLE now always probes for the nsjail binary at startup
regardless of the DISABLE_NSJAIL policy flag, fixing #sandbox annotation
and force_sandboxing being unusable in default (DISABLE_NSJAIL=true)
configuration. All language executors now use is_sandboxing_enabled()
instead of checking DISABLE_NSJAIL directly, so force_sandboxing
applies to all job types, not just bash.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: clarify force_sandboxing and DISABLE_NSJAIL relationship

Neither setting overrides the other — sandboxing is enabled when either
force_sandboxing=true OR DISABLE_NSJAIL=false. Updated frontend
description and backend comments to reflect this.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* all

* all

* revert typo

* all

* all

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-02-12 19:20:22 +01:00
committed by GitHub
parent d07934f82c
commit 2646629194
78 changed files with 340 additions and 273 deletions

View File

@@ -132,9 +132,10 @@ use crate::{
},
get_proxy_envs_for_lang,
handle_child::handle_child,
is_sandboxing_enabled,
worker_utils::ping_job_status,
PyV, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV, PIP_EXTRA_INDEX_URL,
PIP_INDEX_URL, PROXY_ENVS, PY_INSTALL_DIR, TRACING_PROXY_CA_CERT_PATH, TZ_ENV, UV_CACHE_DIR,
PyV, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL,
PROXY_ENVS, PY_INSTALL_DIR, TRACING_PROXY_CA_CERT_PATH, TZ_ENV, UV_CACHE_DIR,
UV_INDEX_STRATEGY,
};
use windmill_common::client::AuthedClient;
@@ -320,7 +321,6 @@ pub async fn uv_pip_compile(
#[cfg(unix)]
let uv_cmd = UV_PATH.as_str();
let mut child_cmd = Command::new(uv_cmd);
child_cmd
.current_dir(job_dir)
@@ -776,7 +776,7 @@ except BaseException as e:
#[cfg(windows)]
let additional_python_paths_folders = additional_python_paths_folders.replace(":", ";");
if !*DISABLE_NSJAIL {
if is_sandboxing_enabled() {
let shared_deps = additional_python_paths
.into_iter()
.map(|pp| {
@@ -820,7 +820,7 @@ mount {{
job.id
);
let child = if !*DISABLE_NSJAIL {
let child = if is_sandboxing_enabled() {
let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str());
nsjail_cmd
.current_dir(job_dir)
@@ -885,7 +885,7 @@ mount {{
mem_peak,
canceled_by,
child,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
worker_name,
&job.workspace_id,
"python run",
@@ -1355,9 +1355,11 @@ async fn spawn_uv_install(
worker_dir: &str,
) -> Result<Box<dyn TokioChildWrapper>, Error> {
let uv_index_strategy_guard = UV_INDEX_STRATEGY.read().await.clone();
let uv_index_strategy = uv_index_strategy_guard.as_deref().unwrap_or("unsafe-best-match");
let uv_index_strategy = uv_index_strategy_guard
.as_deref()
.unwrap_or("unsafe-best-match");
if !*DISABLE_NSJAIL {
if is_sandboxing_enabled() {
tracing::info!(
workspace_id = %w_id,
"starting nsjail"
@@ -1707,7 +1709,7 @@ pub async fn handle_python_reqs(
let mut local_mem_peak = 0;
for pid_o in pids.lock().await.iter() {
if pid_o.is_some(){
let mem = crate::handle_child::get_mem_peak(*pid_o, !*DISABLE_NSJAIL).await;
let mem = crate::handle_child::get_mem_peak(*pid_o, is_sandboxing_enabled()).await;
if mem < 0 {
tracing::warn!(
workspace_id = %w_id_2,
@@ -1817,7 +1819,7 @@ pub async fn handle_python_reqs(
}
// Do we use Nsjail?
if !*DISABLE_NSJAIL {
if is_sandboxing_enabled() {
logs.push_str(&format!(
"\nStarting isolated installation... ({} tasks in parallel) \n",
parallel_limit
@@ -2065,7 +2067,7 @@ pub async fn handle_python_reqs(
#[cfg(not(all(feature = "enterprise", feature = "parquet", unix)))]
let s3_push = false;
if !*DISABLE_NSJAIL {
if is_sandboxing_enabled() {
let _ = std::fs::remove_file(format!("{job_dir}/{req}.config.proto"));
}