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
@@ -12,8 +12,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -17,8 +17,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -46,11 +46,11 @@
]
},
"nullable": [
true,
true,
true,
true,
true,
false,
false,
false,
false,
false,
true,
true
]
@@ -30,8 +30,7 @@
"sqs",
"gcp",
"mqtt",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -24,8 +24,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -122,8 +122,7 @@
"sqs",
"gcp",
"mqtt",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -24,8 +24,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -40,8 +40,7 @@
"sqs",
"gcp",
"mqtt",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -34,8 +34,7 @@
"sqs",
"gcp",
"mqtt",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -68,8 +67,7 @@
"sqs",
"gcp",
"mqtt",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -24,8 +24,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -40,8 +40,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -16,8 +16,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -11,8 +11,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -11,8 +11,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -15,8 +15,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -12,8 +12,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -12,8 +12,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -16,8 +16,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -52,8 +51,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -24,8 +24,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -30,8 +30,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -37,8 +37,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -32,8 +32,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -71,8 +70,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -16,8 +16,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -245,8 +245,7 @@
"sqs",
"gcp",
"mqtt",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -35,8 +35,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -29,8 +29,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -24,8 +24,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -40,8 +40,7 @@
"sqs",
"gcp",
"mqtt",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -27,8 +27,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -24,8 +24,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -35,8 +35,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -17,8 +17,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -24,8 +24,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -17,8 +17,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -32,8 +32,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -30,8 +30,7 @@
"sqs",
"gcp",
"mqtt",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -1,17 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET deployment_msg = EXCLUDED.deployment_msg",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Int8",
"Text"
]
},
"nullable": []
},
"hash": "9f07510019ebe6f0c5fa17bf31c2d14755474cba82b3b388a47585a8bb325b1a"
}
@@ -155,8 +155,7 @@
"sqs",
"gcp",
"mqtt",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -185,8 +185,7 @@
"sqs",
"gcp",
"mqtt",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -160,8 +160,7 @@
"sqs",
"gcp",
"mqtt",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -24,8 +24,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -21,8 +21,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -72,8 +71,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -105,8 +105,7 @@
"sqs",
"gcp",
"mqtt",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -31,8 +31,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -11,8 +11,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -24,8 +24,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -12,8 +12,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -105,8 +105,7 @@
"sqs",
"gcp",
"mqtt",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -25,8 +25,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -185,8 +185,7 @@
"sqs",
"gcp",
"mqtt",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -31,8 +31,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -24,8 +24,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -21,8 +21,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -72,8 +71,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -24,8 +24,7 @@
"mqtt",
"gcp",
"default_email",
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -21,8 +21,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
@@ -72,8 +71,7 @@
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
"nextcloud"
]
}
}
+5 -3
View File
@@ -41,7 +41,7 @@ use windmill_common::{
CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS,
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
HUB_API_SECRET_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING,
JOB_ISOLATION_SETTING, HUB_API_SECRET_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING,
INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING,
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MAVEN_REPOS_SETTING,
MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NO_DEFAULT_MAVEN_SETTING,
@@ -99,8 +99,9 @@ use crate::monitor::{
reload_app_workspaced_route_setting, reload_base_url_setting,
reload_bunfig_install_scopes_setting, reload_critical_alert_mute_ui_setting,
reload_critical_error_channels_setting, reload_extra_pip_index_url_setting,
reload_hub_api_secret_setting, reload_hub_base_url_setting, reload_job_default_timeout_setting,
reload_jwt_secret_setting, reload_license_key, reload_npm_config_registry_setting,
reload_job_isolation_setting, reload_hub_api_secret_setting, reload_hub_base_url_setting,
reload_job_default_timeout_setting, reload_jwt_secret_setting, reload_license_key,
reload_npm_config_registry_setting,
reload_otel_tracing_proxy_setting, reload_pip_index_url_setting,
reload_retention_period_setting, reload_scim_token_setting, reload_smtp_config,
reload_uv_index_strategy_setting, reload_worker_config, MonitorIteration,
@@ -1555,6 +1556,7 @@ async fn process_notify_event(
reload_delete_logs_periodically_setting(conn).await
}
JOB_DEFAULT_TIMEOUT_SECS_SETTING => reload_job_default_timeout_setting(conn).await,
JOB_ISOLATION_SETTING => reload_job_isolation_setting(conn).await,
#[cfg(feature = "parquet")]
OBJECT_STORE_CONFIG_SETTING => {
if !disable_s3_store {
+38 -9
View File
@@ -54,10 +54,11 @@ use windmill_common::{
DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING,
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
HUB_API_SECRET_SETTING, HUB_BASE_URL_SETTING, INSTANCE_PYTHON_VERSION_SETTING,
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING,
LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING,
NUGET_CONFIG_SETTING, OTEL_SETTING, OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING,
POWERSHELL_REPO_PAT_SETTING, POWERSHELL_REPO_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JOB_ISOLATION_SETTING, JWT_SECRET_SETTING,
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING,
NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OTEL_SETTING,
OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING, POWERSHELL_REPO_PAT_SETTING,
POWERSHELL_REPO_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
UV_INDEX_STRATEGY_SETTING,
@@ -85,11 +86,12 @@ use windmill_common::{
use windmill_common::{client::AuthedClient, global_settings::APP_WORKSPACED_ROUTE_SETTING};
use windmill_queue::{cancel_job, get_queued_job_v2, SameWorkerPayload};
use windmill_worker::{
result_processor::handle_job_error, JobCompletedSender, OtelTracingProxySettings,
SameWorkerSender, BUNFIG_INSTALL_SCOPES, CARGO_REGISTRIES, INSTANCE_PYTHON_VERSION,
JOB_DEFAULT_TIMEOUT, KEEP_JOB_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN, NPM_CONFIG_REGISTRY,
NUGET_CONFIG, OTEL_TRACING_PROXY_SETTINGS, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL,
POWERSHELL_REPO_PAT, POWERSHELL_REPO_URL, UV_INDEX_STRATEGY,
result_processor::handle_job_error, JobCompletedSender, JobIsolationLevel,
OtelTracingProxySettings, SameWorkerSender, BUNFIG_INSTALL_SCOPES, CARGO_REGISTRIES,
INSTANCE_PYTHON_VERSION, JOB_DEFAULT_TIMEOUT, JOB_ISOLATION, KEEP_JOB_DIR, MAVEN_REPOS,
NO_DEFAULT_MAVEN, NPM_CONFIG_REGISTRY, NSJAIL_AVAILABLE, NUGET_CONFIG,
OTEL_TRACING_PROXY_SETTINGS, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, POWERSHELL_REPO_PAT,
POWERSHELL_REPO_URL, UV_INDEX_STRATEGY,
};
#[cfg(feature = "parquet")]
@@ -316,6 +318,7 @@ pub async fn initial_load(
if worker_mode {
reload_job_default_timeout_setting(&conn).await;
reload_job_isolation_setting(&conn).await;
reload_extra_pip_index_url_setting(&conn).await;
reload_pip_index_url_setting(&conn).await;
reload_uv_index_strategy_setting(&conn).await;
@@ -1418,6 +1421,32 @@ pub async fn reload_job_default_timeout_setting(conn: &Connection) {
.await;
}
pub async fn reload_job_isolation_setting(conn: &Connection) {
let value =
match load_value_from_global_settings_with_conn(conn, JOB_ISOLATION_SETTING, true).await {
Ok(Some(v)) => JobIsolationLevel::from_str(v.as_str().unwrap_or("")),
Ok(None) => JobIsolationLevel::Undefined,
Err(e) => {
tracing::error!("Error reloading job_isolation setting: {:?}", e);
return;
}
};
let old_value = JobIsolationLevel::from_u8(JOB_ISOLATION.swap(value as u8, Ordering::Relaxed));
if old_value != value {
tracing::info!(
"job_isolation setting changed from {:?} to {:?}",
old_value,
value
);
}
if value == JobIsolationLevel::NsjailSandboxing && NSJAIL_AVAILABLE.is_none() {
tracing::error!(
"job_isolation is set to nsjail_sandboxing but nsjail is not available on this worker. \
All jobs will fail until nsjail is installed or the setting is changed."
);
}
}
pub async fn reload_request_size(conn: &Connection) {
if let Err(e) = reload_setting(
conn,
@@ -35,6 +35,7 @@ pub const EXPOSE_METRICS_SETTING: &str = "expose_metrics";
pub const EXPOSE_DEBUG_METRICS_SETTING: &str = "expose_debug_metrics";
pub const KEEP_JOB_DIR_SETTING: &str = "keep_job_dir";
pub const REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING: &str = "require_preexisting_user_for_oauth";
pub const JOB_ISOLATION_SETTING: &str = "job_isolation";
pub const OBJECT_STORE_CONFIG_SETTING: &str = "object_store_cache_config";
pub const HUB_API_SECRET_SETTING: &str = "hub_api_secret";
+1
View File
@@ -688,6 +688,7 @@ pub struct SqlAnnotations {
#[annotations("#")]
pub struct BashAnnotations {
pub docker: bool,
pub sandbox: bool,
}
#[derive(Debug, Clone, Copy, PartialEq)]
@@ -34,7 +34,7 @@ use crate::{
},
handle_child::handle_child,
python_executor::{create_dependencies_dir, handle_python_reqs, uv_pip_compile},
DISABLE_NSJAIL, DISABLE_NUSER, GIT_PATH, HOME_ENV, NSJAIL_PATH, PATH_ENV, PROXY_ENVS,
is_sandboxing_enabled, DISABLE_NUSER, GIT_PATH, HOME_ENV, NSJAIL_PATH, PATH_ENV, PROXY_ENVS,
PY_INSTALL_DIR, TZ_ENV,
};
use windmill_common::client::AuthedClient;
@@ -466,7 +466,7 @@ pub async fn install_galaxy_collections(
mem_peak,
canceled_by,
child,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
worker_name,
w_id,
"ansible-galaxy role install",
@@ -505,7 +505,7 @@ pub async fn install_galaxy_collections(
mem_peak,
canceled_by,
child,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
worker_name,
w_id,
"ansible-galaxy collection install",
@@ -1164,7 +1164,7 @@ pub async fn handle_ansible_job(
get_reserved_variables(job, &client.token, conn, parent_runnable_path).await?;
let additional_python_paths_folders = additional_python_paths.join(":");
if !*DISABLE_NSJAIL {
if is_sandboxing_enabled() {
let shared_deps = additional_python_paths
.into_iter()
.map(|pp| {
@@ -1206,7 +1206,7 @@ mount {{
cmd_args.extend(inventories.iter().map(|s| s.as_str()));
cmd_args.extend(cmd_options.iter().map(|s| s.as_str()));
let child = if !*DISABLE_NSJAIL {
let child = if is_sandboxing_enabled() {
let wrapper = format!(
r#"set -eou pipefail
{0} "$@"
@@ -1279,7 +1279,7 @@ fi
mem_peak,
canceled_by,
child,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
worker_name,
&job.workspace_id,
"python run",
+16 -2
View File
@@ -40,7 +40,8 @@ use crate::{
},
get_proxy_envs_for_lang,
handle_child::handle_child,
DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV, TRACING_PROXY_CA_CERT_PATH,
is_sandboxing_enabled, DISABLE_NUSER, HOME_ENV, NSJAIL_AVAILABLE, NSJAIL_PATH, PATH_ENV,
TRACING_PROXY_CA_CERT_PATH,
};
use windmill_common::client::AuthedClient;
use windmill_common::scripts::ScriptLang;
@@ -77,10 +78,22 @@ pub async fn handle_bash_job(
) -> Result<Box<RawValue>, Error> {
let annotation = windmill_common::worker::BashAnnotations::parse(&content);
// Check if sandbox annotation is used but nsjail is not available
if annotation.sandbox && NSJAIL_AVAILABLE.is_none() {
return Err(Error::ExecutionErr(
"Script has #sandbox annotation but nsjail is not available on this worker. \
Please ensure nsjail is installed or remove the #sandbox annotation."
.to_string(),
));
}
let mut logs1 = "\n\n--- BASH CODE EXECUTION ---\n".to_string();
if annotation.docker {
logs1.push_str("docker mode\n");
}
if annotation.sandbox {
logs1.push_str("sandbox mode (nsjail)\n");
}
append_logs(&job.id, &job.workspace_id, logs1, &conn).await;
write_file(job_dir, "main.sh", &format!("set -e\n{content}"))?;
@@ -171,7 +184,8 @@ exit $exit_status
})
.unwrap_or(true);
let nsjail = !*DISABLE_NSJAIL && is_regular_job;
// Use nsjail if globally enabled OR if script has #sandbox annotation
let nsjail = (is_sandboxing_enabled() || annotation.sandbox) && is_regular_job;
let child = if nsjail {
let _ = write_file(
job_dir,
+5 -5
View File
@@ -21,7 +21,7 @@ use crate::{
get_proxy_envs_for_lang,
handle_child::handle_child,
BUNFIG_INSTALL_SCOPES, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, BUN_NO_CACHE, BUN_PATH,
DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NODE_BIN_PATH, NODE_PATH, NPM_CONFIG_REGISTRY,
is_sandboxing_enabled, DISABLE_NUSER, HOME_ENV, NODE_BIN_PATH, NODE_PATH, NPM_CONFIG_REGISTRY,
NPM_PATH, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, TRACING_PROXY_CA_CERT_PATH, TZ_ENV,
};
use windmill_common::{
@@ -1022,7 +1022,7 @@ pub async fn handle_bun_job(
}
}
MaybeLock::Unresolved { ref workspace_dependencies } => {
// if !*DISABLE_NSJAIL || !empty_trusted_deps || has_custom_config_registry {
// if is_sandboxing_enabled() || !empty_trusted_deps || has_custom_config_registry {
let logs1 = "\n\n--- BUN INSTALL ---\n".to_string();
append_logs(&job.id, &job.workspace_id, logs1, conn).await;
gen_bun_lockfile(
@@ -1410,7 +1410,7 @@ try {{
append_logs(&job.id, &job.workspace_id, init_logs, conn).await;
//do not cache local dependencies
let child = if !*DISABLE_NSJAIL {
let child = if is_sandboxing_enabled() {
let _ = write_file(
job_dir,
"run.config.proto",
@@ -1548,7 +1548,7 @@ try {{
mem_peak,
canceled_by,
child,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
worker_name,
&job.workspace_id,
"bun run",
@@ -1733,7 +1733,7 @@ pub async fn start_worker(
.await?;
tracing::info!("dedicated worker requirements installed: {reqs}");
}
} else if !*DISABLE_NSJAIL {
} else if is_sandboxing_enabled() {
logs.push_str("\n\n--- BUN INSTALL ---\n");
let _ = gen_bun_lockfile(
&mut mem_peak,
+2 -2
View File
@@ -660,7 +660,7 @@ lazy_static! {
pub fn build_command_with_isolation(program: &str, args: &[&str]) -> Command {
use tokio::process::Command;
if *crate::ENABLE_UNSHARE_PID {
if crate::is_unshare_enabled() {
if let Some(unshare_path) = crate::UNSHARE_PATH.as_ref() {
let mut cmd = Command::new(unshare_path);
@@ -687,7 +687,7 @@ pub fn build_command_with_isolation(program: &str, args: &[&str]) -> Command {
cmd
} else {
panic!(
"BUG: ENABLE_UNSHARE_PID is true but UNSHARE_PATH is None. \
"BUG: unshare isolation is enabled but UNSHARE_PATH is None. \
This should have been caught at worker startup."
);
}
@@ -32,7 +32,7 @@ use crate::{
},
get_proxy_envs_for_lang,
handle_child::handle_child,
CSHARP_CACHE_DIR, DISABLE_NSJAIL, DISABLE_NUSER, DOTNET_PATH, HOME_ENV, NSJAIL_PATH,
CSHARP_CACHE_DIR, is_sandboxing_enabled, DISABLE_NUSER, DOTNET_PATH, HOME_ENV, NSJAIL_PATH,
NUGET_CONFIG, PATH_ENV, TRACING_PROXY_CA_CERT_PATH, TZ_ENV,
};
#[cfg(feature = "csharp")]
@@ -563,7 +563,7 @@ pub async fn handle_csharp_job(
let reserved_variables =
get_reserved_variables(job, &client.token, conn, parent_runnable_path).await?;
let child = if !*DISABLE_NSJAIL {
let child = if is_sandboxing_enabled() {
write_file(
job_dir,
"run.config.proto",
@@ -661,7 +661,7 @@ pub async fn handle_csharp_job(
mem_peak,
canceled_by,
child,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
worker_name,
&job.workspace_id,
"csharp run",
+3 -3
View File
@@ -13,7 +13,7 @@ use crate::{
},
get_proxy_envs_for_lang,
handle_child::handle_child,
DENO_CACHE_DIR, DENO_PATH, DISABLE_NSJAIL, HOME_ENV, NPM_CONFIG_REGISTRY, PATH_ENV, TZ_ENV,
is_sandboxing_enabled, DENO_CACHE_DIR, DENO_PATH, HOME_ENV, NPM_CONFIG_REGISTRY, PATH_ENV, TZ_ENV,
};
use windmill_common::client::AuthedClient;
@@ -364,7 +364,7 @@ try {{
let mut common_deno_proc_envs =
get_common_deno_proc_envs(&client.token, base_internal_url).await;
if !*DISABLE_NSJAIL {
if is_sandboxing_enabled() {
common_deno_proc_envs.insert("HOME".to_string(), job_dir.to_string());
}
@@ -405,7 +405,7 @@ try {{
for flag in deno_flags {
args.push(flag);
}
} else if !*DISABLE_NSJAIL {
} else if is_sandboxing_enabled() {
args.push("--allow-net");
args.push("--allow-sys");
args.push(allow_read.as_str());
+3 -3
View File
@@ -24,7 +24,7 @@ use crate::{
read_result, start_child_process, OccupancyMetrics, DEV_CONF_NSJAIL,
},
handle_child::handle_child,
DISABLE_NSJAIL, DISABLE_NUSER, GOPRIVATE, GOPROXY, GO_BIN_CACHE_DIR, GO_CACHE_DIR, HOME_ENV,
is_sandboxing_enabled, DISABLE_NUSER, GOPRIVATE, GOPROXY, GO_BIN_CACHE_DIR, GO_CACHE_DIR, HOME_ENV,
NSJAIL_PATH, PATH_ENV, PROXY_ENVS, TRACING_PROXY_CA_CERT_PATH, TZ_ENV,
};
use windmill_common::client::AuthedClient;
@@ -338,7 +338,7 @@ func Run(req Req) (interface{{}}, error){{
let reserved_variables =
get_reserved_variables(job, &client.token, conn, parent_runnable_path).await?;
let child = if !*DISABLE_NSJAIL {
let child = if is_sandboxing_enabled() {
let _ = write_file(
job_dir,
"run.config.proto",
@@ -414,7 +414,7 @@ func Run(req Req) (interface{{}}, error){{
mem_peak,
canceled_by,
child,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
worker_name,
&job.workspace_id,
"go run",
+5 -5
View File
@@ -26,7 +26,7 @@ use crate::{
},
handle_child,
universal_pkg_installer::{par_install_language_dependencies_all_at_once, RequiredDependency},
COURSIER_CACHE_DIR, DISABLE_NSJAIL, DISABLE_NUSER, JAVA_CACHE_DIR, JAVA_HOME_DIR,
is_sandboxing_enabled, COURSIER_CACHE_DIR, DISABLE_NUSER, JAVA_CACHE_DIR, JAVA_HOME_DIR,
JAVA_REPOSITORY_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN, NSJAIL_PATH, PATH_ENV, PROXY_ENVS,
};
use windmill_common::client::AuthedClient;
@@ -420,7 +420,7 @@ async fn install<'a>(
&job.id,
&job.workspace_id,
worker_name,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
conn,
)
.await?;
@@ -529,7 +529,7 @@ async fn compile<'a>(
mem_peak,
canceled_by,
child,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
worker_name,
&job.workspace_id,
"javac",
@@ -585,7 +585,7 @@ async fn run<'a>(
let reserved_variables =
get_reserved_variables(job, &client.token, conn, parent_runnable_path.clone()).await?;
let child = if !cfg!(windows) && !*DISABLE_NSJAIL {
let child = if !cfg!(windows) && is_sandboxing_enabled() {
append_logs(
&job.id,
&job.workspace_id,
@@ -717,7 +717,7 @@ async fn run<'a>(
mem_peak,
canceled_by,
child,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
worker_name,
&job.workspace_id,
"java",
+4 -4
View File
@@ -16,7 +16,7 @@ use crate::{
build_command_with_isolation, create_args_and_out_file, get_reserved_variables,
read_result, start_child_process, OccupancyMetrics, DEV_CONF_NSJAIL,
},
get_proxy_envs_for_lang, handle_child, DISABLE_NSJAIL, DISABLE_NUSER, NSJAIL_PATH, PATH_ENV,
get_proxy_envs_for_lang, handle_child, is_sandboxing_enabled, DISABLE_NUSER, NSJAIL_PATH, PATH_ENV,
TRACING_PROXY_CA_CERT_PATH,
};
use windmill_common::client::AuthedClient;
@@ -127,7 +127,7 @@ pub async fn handle_nu_job<'a>(mut args: JobHandlerInput<'a>) -> Result<Box<RawV
// // mem_peak,
// // canceled_by,
// // child,
// // !*DISABLE_NSJAIL,
// // is_sandboxing_enabled(),
// // worker_name,
// // &job.workspace_id,
// // "cargo",
@@ -236,7 +236,7 @@ async fn run<'a>(
) -> Result<(), Error> {
let reserved_variables =
get_reserved_variables(job, &client.token, conn, parent_runnable_path.clone()).await?;
let child = if !cfg!(windows) && !*DISABLE_NSJAIL {
let child = if !cfg!(windows) && is_sandboxing_enabled() {
append_logs(
&job.id,
&job.workspace_id,
@@ -335,7 +335,7 @@ async fn run<'a>(
mem_peak,
canceled_by,
child,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
worker_name,
&job.workspace_id,
"nu",
+3 -3
View File
@@ -22,7 +22,7 @@ use crate::{
get_reserved_variables, read_result, start_child_process, MaybeLock, OccupancyMetrics,
},
handle_child::handle_child,
COMPOSER_CACHE_DIR, COMPOSER_PATH, DISABLE_NSJAIL, DISABLE_NUSER, NSJAIL_PATH, PHP_PATH,
COMPOSER_CACHE_DIR, COMPOSER_PATH, is_sandboxing_enabled, DISABLE_NUSER, NSJAIL_PATH, PHP_PATH,
};
use windmill_common::client::AuthedClient;
@@ -293,7 +293,7 @@ try {{
let (reserved_variables, _) = tokio::try_join!(reserved_variables_args_out_f, write_wrapper_f)?;
let child = if !*DISABLE_NSJAIL {
let child = if is_sandboxing_enabled() {
let _ = write_file(
job_dir,
"run.config.proto",
@@ -347,7 +347,7 @@ try {{
mem_peak,
canceled_by,
child,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
worker_name,
&job.workspace_id,
"php run",
+3 -3
View File
@@ -27,7 +27,7 @@ use crate::{
read_file_content, start_child_process, OccupancyMetrics,
},
handle_child::handle_child,
DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV, POWERSHELL_CACHE_DIR,
is_sandboxing_enabled, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV, POWERSHELL_CACHE_DIR,
POWERSHELL_PATH, POWERSHELL_REPO_PAT, POWERSHELL_REPO_URL, PROXY_ENVS, TZ_ENV,
};
@@ -501,7 +501,7 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"",
})
.unwrap_or(true);
let nsjail = !*DISABLE_NSJAIL && is_regular_job;
let nsjail = is_sandboxing_enabled() && is_regular_job;
let child = if nsjail {
let _ = write_file(
job_dir,
@@ -617,7 +617,7 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"",
mem_peak,
canceled_by,
child,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
worker_name,
&job.workspace_id,
"powershell run",
+13 -11
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"));
}
+6 -6
View File
@@ -29,7 +29,7 @@ use crate::{
get_proxy_envs_for_lang,
handle_child::{self},
universal_pkg_installer::{par_install_language_dependencies_seq, RequiredDependency},
DISABLE_NSJAIL, DISABLE_NUSER, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, RUBY_CACHE_DIR, RUBY_REPOS,
is_sandboxing_enabled, DISABLE_NUSER, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, RUBY_CACHE_DIR, RUBY_REPOS,
TRACING_PROXY_CA_CERT_PATH,
};
use windmill_common::scripts::ScriptLang;
@@ -330,7 +330,7 @@ Your Gemfile syntax will continue to work as-is."
)
.await;
let mut cmd = if !cfg!(windows) && !*DISABLE_NSJAIL {
let mut cmd = if !cfg!(windows) && is_sandboxing_enabled() {
let nsjail_proto = format!("{}.lock.config.proto", Uuid::new_v4());
let _ = write_file(
&job_dir,
@@ -401,7 +401,7 @@ Your Gemfile syntax will continue to work as-is."
mem_peak,
canceled_by,
child,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
worker_name,
w_id,
"bundle",
@@ -589,7 +589,7 @@ async fn install<'a>(
}
let job_dir = job_dir.to_owned();
let jailed = !cfg!(windows) && !*DISABLE_NSJAIL;
let jailed = !cfg!(windows) && is_sandboxing_enabled();
let RubyAnnotations { verbose } = RubyAnnotations::parse(&inner_content);
let repos = RUBY_REPOS.read().await.clone().unwrap_or_default();
let (envs, reserved_variables) = (
@@ -751,7 +751,7 @@ async fn run<'a>(
let reserved_variables =
get_reserved_variables(job, &client.token, conn, parent_runnable_path.clone()).await?;
let child = if !cfg!(windows) && !*DISABLE_NSJAIL {
let child = if !cfg!(windows) && is_sandboxing_enabled() {
append_logs(
&job.id,
&job.workspace_id,
@@ -858,7 +858,7 @@ mount {{
mem_peak,
canceled_by,
child,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
worker_name,
&job.workspace_id,
"ruby",
+6 -6
View File
@@ -25,8 +25,8 @@ use crate::{
},
get_proxy_envs_for_lang,
handle_child::handle_child,
CARGO_REGISTRIES, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV, PROXY_ENVS,
RUST_CACHE_DIR, TRACING_PROXY_CA_CERT_PATH, TZ_ENV,
is_sandboxing_enabled, CARGO_REGISTRIES, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV,
PROXY_ENVS, RUST_CACHE_DIR, TRACING_PROXY_CA_CERT_PATH, TZ_ENV,
};
use windmill_common::client::AuthedClient;
use windmill_common::scripts::ScriptLang;
@@ -215,7 +215,7 @@ async fn get_build_dir(
if !is_preview || *NO_SHARED_BUILD_DIR {
None
} else {
if *DISABLE_NSJAIL {
if !is_sandboxing_enabled() {
// If nsjail is disabled then entire worker has shared build directory
// It drastically improves cache hit-rate.
Some((format!("{RUST_CACHE_DIR}/build/{worker_name}"), true))
@@ -332,7 +332,7 @@ pub async fn build_rust_crate(
let build_dir = get_build_dir(job, job_dir, conn, worker_name, is_preview).await?;
let child = if !*DISABLE_NSJAIL {
let child = if is_sandboxing_enabled() {
let _ = write_file(
job_dir,
"download.config.proto",
@@ -541,7 +541,7 @@ pub async fn handle_rust_job(
let logs2 = format!("{cache_logs}\n\n--- RUST CODE EXECUTION ---\n");
append_logs(&job.id, &job.workspace_id, logs2, conn).await;
let child = if !*DISABLE_NSJAIL {
let child = if is_sandboxing_enabled() {
let _ = write_file(
job_dir,
"run.config.proto",
@@ -599,7 +599,7 @@ pub async fn handle_rust_job(
mem_peak,
canceled_by,
child,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
worker_name,
&job.workspace_id,
"rust run",
@@ -12,7 +12,7 @@ use tokio::{
use uuid::Uuid;
use windmill_common::{error, worker::Connection};
use crate::{common::start_child_process, DISABLE_NSJAIL};
use crate::{common::start_child_process, is_sandboxing_enabled};
#[allow(dead_code)]
#[derive(Clone, Debug)]
@@ -151,7 +151,7 @@ pub async fn par_install_language_dependencies_all_at_once<
// TODO: Return canceld_by_ref
&mut None,
child,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
&worker_name,
&w_id,
&installer_executable_name,
@@ -580,7 +580,7 @@ async fn try_install_one_detached<'a, T: Clone + std::marker::Send + Sync + 'a +
// TODO: Return canceld_by_ref
&mut None,
child,
!*DISABLE_NSJAIL,
is_sandboxing_enabled(),
&worker_name,
&w_id,
&installer_executable_name,
+116 -44
View File
@@ -59,7 +59,7 @@ use std::{
collections::{HashMap, HashSet},
fmt::Display,
sync::{
atomic::{AtomicBool, AtomicU16, Ordering},
atomic::{AtomicBool, AtomicU8, AtomicU16, Ordering},
Arc,
},
time::Duration,
@@ -325,11 +325,19 @@ lazy_static::lazy_static! {
.and_then(|x| x.parse::<bool>().ok())
.unwrap_or(true);
/// Global setting for job isolation mode. 0=undefined (use env vars), 1=none, 2=unshare, 3=nsjail
pub static ref JOB_ISOLATION: AtomicU8 = AtomicU8::new(JobIsolationLevel::Undefined as u8);
pub static ref ENABLE_UNSHARE_PID: bool = std::env::var("ENABLE_UNSHARE_PID")
.ok()
.and_then(|x| x.parse::<bool>().ok())
.unwrap_or(false);
pub static ref FAVOR_UNSHARE_PID: bool = std::env::var("FAVOR_UNSHARE_PID")
.ok()
.and_then(|x| x.parse::<bool>().ok())
.unwrap_or(false);
pub static ref UNSHARE_TINI_PATH: String = {
std::env::var("UNSHARE_TINI_PATH").unwrap_or_else(|_| "tini".to_string())
};
@@ -467,47 +475,41 @@ lazy_static::lazy_static! {
};
pub static ref NSJAIL_AVAILABLE: Option<String> = {
if *DISABLE_NSJAIL {
None
} else {
let nsjail_path = NSJAIL_PATH.as_str();
let nsjail_path = NSJAIL_PATH.as_str();
let test_result = std::process::Command::new(nsjail_path)
.arg("--help")
.output();
let test_result = std::process::Command::new(nsjail_path)
.arg("--help")
.output();
match test_result {
Ok(output) if output.status.success() => {
tracing::info!("NSJAIL sandboxing available at: {}", nsjail_path);
Some(nsjail_path.to_string())
},
Ok(output) => {
let stderr = String::from_utf8_lossy(&output.stderr);
tracing::warn!(
"nsjail test failed: {}. Jobs will run without nsjail sandboxing. \
nsjail should be included in all standard windmill images. \
Check that the nsjail binary is installed and working correctly.",
stderr.trim()
match test_result {
Ok(output) if output.status.success() => {
tracing::info!("nsjail available at: {}", nsjail_path);
Some(nsjail_path.to_string())
},
Ok(output) => {
let stderr = String::from_utf8_lossy(&output.stderr);
tracing::warn!(
"nsjail test failed: {}. \
nsjail should be included in all standard windmill images. \
Check that the nsjail binary is installed and working correctly.",
stderr.trim()
);
None
},
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
tracing::info!(
"nsjail not found at '{}'. Sandboxing will not be available.",
nsjail_path
);
} else {
tracing::warn!(
"Failed to test nsjail at '{}': {}.",
nsjail_path,
e
);
None
},
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
tracing::warn!(
"nsjail not found at '{}'. Jobs will run without nsjail sandboxing. \
nsjail should be included in all standard windmill images. \
Check that the nsjail binary is installed at the expected path.",
nsjail_path
);
} else {
tracing::warn!(
"Failed to test nsjail at '{}': {}. Jobs will run without nsjail sandboxing.",
nsjail_path,
e
);
}
None
}
None
}
}
};
@@ -631,6 +633,69 @@ lazy_static::lazy_static! {
type Envs = Vec<(String, String)>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum JobIsolationLevel {
/// Not set via global setting; fall back to env vars (DISABLE_NSJAIL, FAVOR_UNSHARE_PID)
Undefined = 0,
/// No isolation
None = 1,
/// PID namespace isolation via unshare
Unshare = 2,
/// Full nsjail sandboxing
NsjailSandboxing = 3,
}
impl JobIsolationLevel {
pub fn from_u8(v: u8) -> Self {
match v {
1 => Self::None,
2 => Self::Unshare,
3 => Self::NsjailSandboxing,
_ => Self::Undefined,
}
}
pub fn from_str(s: &str) -> Self {
match s {
"none" => Self::None,
"unshare" => Self::Unshare,
"nsjail_sandboxing" => Self::NsjailSandboxing,
_ => Self::Undefined,
}
}
}
pub fn get_job_isolation() -> JobIsolationLevel {
JobIsolationLevel::from_u8(JOB_ISOLATION.load(Ordering::Relaxed))
}
/// Returns true if nsjail sandboxing should be used for job execution.
/// DISABLE_NSJAIL=false forces nsjail regardless of the global setting.
pub fn is_sandboxing_enabled() -> bool {
if !*DISABLE_NSJAIL {
return true;
}
match get_job_isolation() {
JobIsolationLevel::NsjailSandboxing => true,
_ => false,
}
}
/// Returns true if unshare PID isolation should be used (when not using nsjail).
/// ENABLE_UNSHARE_PID forces unshare regardless of the global setting.
/// FAVOR_UNSHARE_PID uses unshare only when the global setting is not set.
pub fn is_unshare_enabled() -> bool {
if *ENABLE_UNSHARE_PID {
return true;
}
match get_job_isolation() {
JobIsolationLevel::Unshare => true,
JobIsolationLevel::Undefined => *FAVOR_UNSHARE_PID,
_ => false,
}
}
/// Check if OTEL tracing proxy is enabled for a specific language (EE only)
pub async fn is_otel_tracing_proxy_enabled_for_lang(lang: &ScriptLang) -> bool {
cfg!(all(feature = "private", feature = "enterprise")) && {
@@ -1269,7 +1334,7 @@ pub async fn run_worker(
base_internal_url: &str,
) {
#[cfg(not(feature = "enterprise"))]
if !*DISABLE_NSJAIL {
if is_sandboxing_enabled() {
tracing::warn!(
worker = %worker_name, hostname = %hostname,
"NSJAIL to sandbox process in untrusted environments is an enterprise feature but allowed to be used for testing purposes"
@@ -1278,8 +1343,7 @@ pub async fn run_worker(
// Force UNSHARE_PATH initialization now to fail-fast if unshare doesn't work
// This ensures we panic at startup rather than lazily when first accessed during job execution
if *ENABLE_UNSHARE_PID {
// Access UNSHARE_PATH to trigger lazy_static initialization and test
if is_unshare_enabled() || *ENABLE_UNSHARE_PID || *FAVOR_UNSHARE_PID {
let _ = &*UNSHARE_PATH;
}
@@ -1330,7 +1394,7 @@ pub async fn run_worker(
create_directory_async(&worker_dir).await;
if !*DISABLE_NSJAIL {
if is_sandboxing_enabled() {
let _ = write_file(
&worker_dir,
"download_deps.py.sh",
@@ -3105,9 +3169,17 @@ pub async fn handle_queued_job(
let mut canceled_by: Option<CanceledBy> = None;
// println!("handle queue {:?}", SystemTime::now());
let isolation_label = if is_sandboxing_enabled() {
"nsjail"
} else if is_unshare_enabled() {
"unshare"
} else {
"none"
};
logs.push_str(&format!(
"job={} {}={} worker={} hostname={}\n",
&job.id, *LOG_TAG_NAME, &job.tag, &worker_name, &hostname
"job={} {}={} worker={} hostname={} isolation={}\n",
&job.id, *LOG_TAG_NAME, &job.tag, &worker_name, &hostname, isolation_label
));
if *NO_LOGS_AT_ALL {
+4 -5
View File
@@ -182,10 +182,9 @@ pub async fn insert_ping(
let vcpus = get_vcpus();
let memory = get_memory();
// Determine job isolation method
let job_isolation = if crate::NSJAIL_AVAILABLE.is_some() {
let job_isolation = if crate::is_sandboxing_enabled() {
Some("nsjail".to_string())
} else if *crate::ENABLE_UNSHARE_PID && crate::UNSHARE_PATH.is_some() {
} else if crate::is_unshare_enabled() {
Some("unshare".to_string())
} else {
Some("none".to_string())
@@ -256,9 +255,9 @@ pub async fn update_worker_ping_from_job(
let occupancy_rate_5m = occupancy.as_ref().and_then(|x| x.occupancy_rate_5m);
let occupancy_rate_30m = occupancy.as_ref().and_then(|x| x.occupancy_rate_30m);
let job_isolation = if crate::NSJAIL_AVAILABLE.is_some() {
let job_isolation = if crate::is_sandboxing_enabled() {
Some("nsjail".to_string())
} else if *crate::ENABLE_UNSHARE_PID && crate::UNSHARE_PATH.is_some() {
} else if crate::is_unshare_enabled() {
Some("unshare".to_string())
} else {
Some("none".to_string())
+1 -1
View File
@@ -10,7 +10,7 @@
</script>
<Tooltip>
<Badge verySmall color="blue" class="px-2">EE only</Badge>
<Badge verySmall color="blue" class="px-2">EE</Badge>
{#snippet text()}
{#if children}
{@render children()}
@@ -193,6 +193,28 @@ export const settings: Record<string, Setting[]> = {
}
],
Jobs: [
{
label: 'Job Isolation',
key: 'job_isolation',
fieldType: 'select',
description:
'Isolation mode for job execution. None: no isolation. Unshare: PID namespace isolation via unshare. Nsjail: full nsjail sandboxing. <a href="https://www.windmill.dev/docs/advanced/security_isolation">Learn more</a>',
storage: 'setting',
select_items: [
{
label: 'None',
value: 'none'
},
{
label: 'Unshare',
value: 'unshare'
},
{
label: 'Nsjail',
value: 'nsjail_sandboxing'
}
]
},
{
label: 'Default timeout',
key: 'job_default_timeout',
@@ -596,6 +618,7 @@ export const settingsKeys = Object.keys(settings)
// --- Sidebar navigation for instance settings ---
export const instanceSettingsNavigationGroups = [
{
title: 'Core',
items: [
{
id: 'users',
@@ -608,6 +631,12 @@ export const instanceSettingsNavigationGroups = [
label: 'General',
aiId: 'instance-settings-general',
aiDescription: 'Instance general settings'
},
{
id: 'jobs',
label: 'Jobs',
aiId: 'instance-settings-jobs',
aiDescription: 'Instance jobs settings'
}
]
},
@@ -688,12 +717,6 @@ export const instanceSettingsNavigationGroups = [
{
title: 'Advanced',
items: [
{
id: 'jobs',
label: 'Jobs',
aiId: 'instance-settings-jobs',
aiDescription: 'Instance jobs settings'
},
{
id: 'private_hub',
label: 'Private Hub',