VYPR
High severity8.1NVD Advisory· Published May 19, 2026· Updated May 20, 2026

CVE-2026-47107

CVE-2026-47107

Description

Windmill prior to 1.703.2 contains an incorrect default permissions vulnerability in nsjail sandbox configuration files where /etc is bind-mounted without read-write restrictions, allowing authenticated users to write arbitrary entries to /etc/hosts, /etc/resolv.conf, and /etc/ssl/certs/ca-certificates.crt from within script execution sandboxes. Attackers can exploit persistent poisoned entries across all subsequent script executions on the same worker pod to redirect hostnames, intercept DNS queries, perform transparent HTTPS man-in-the-middle attacks, and intercept WM_TOKEN JWTs to gain workspace-admin access to other users' workspaces.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Windmill prior to 1.703.2 allows authenticated users to write to /etc/hosts, /etc/resolv.conf, and ca-certificates.crt from nsjail sandboxes, enabling persistent cross-tenant DNS poisoning and HTTPS interception.

Vulnerability

Windmill versions prior to 1.703.2 contain an incorrect default permissions vulnerability in the nsjail sandbox configuration files. The /etc directory is bind-mounted as read-only, but on Kubernetes container runtimes, individual files such as /etc/hosts, /etc/resolv.conf, and /etc/hostname are separate kubelet bind-mounts over /etc; the nsjail read-only remount is non-recursive, leaving these files writable inside the sandbox [1][3]. This allows authenticated users who can execute scripts to write arbitrary entries to /etc/hosts, /etc/resolv.conf, and /etc/ssl/certs/ca-certificates.crt from within script execution sandboxes [4].

Exploitation

An attacker must have valid authentication to a Windmill instance and the ability to execute arbitrary scripts. From within the sandboxed script execution environment, the attacker can write entries to /etc/hosts to redirect hostnames, modify /etc/resolv.conf to intercept DNS queries, and replace /etc/ssl/certs/ca-certificates.crt with a custom certificate authority file. These poisoned entries persist across all subsequent script executions on the same worker pod for the pod's lifetime [4]. The attacker can use this capability to intercept WM_TOKEN JWTs, which are used for authentication to Windmill workspaces.

Impact

Successful exploitation allows an attacker to redirect hostnames, intercept DNS queries, perform transparent HTTPS man-in-the-middle attacks, and capture WM_TOKEN JWTs from other users' script executions. This can lead to workspace-admin access to victim workspaces across tenants, enabling full compromise of target workspaces [4].

Mitigation

The vulnerability is fixed in Windmill version 1.703.2, released on 2026-05-17 [2]. The fix adds explicit bind-mount entries for /etc/resolv.conf, /etc/hosts, and /etc/hostname inside the nsjail configuration to override the writable kubelet submounts [1][3]. All users running Windmill prior to 1.703.2 should upgrade to the fixed version. No workarounds are documented in the available references.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Windmill/Windmillreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • (no CPE)range: <1.703.2

Patches

1
f8467f38c8a0

fix: prevent cross-tenant DNS poisoning via writable /etc in nsjail (#9194)

https://github.com/windmill-labs/windmillRuben FiszelMay 17, 2026via nvd-ref
18 files changed · +432 0
  • backend/windmill-worker/nsjail/download.py.config.proto+24 0 modified
    @@ -55,6 +55,30 @@ mount {
     	is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
     	src: "/dev/null"
     	dst: "/dev/null"
    
  • backend/windmill-worker/nsjail/download.ruby.config.proto+24 0 modified
    @@ -55,6 +55,30 @@ mount {
     	is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
     	src: "/dev/null"
     	dst: "/dev/null"
    
  • backend/windmill-worker/nsjail/download.rust.config.proto+24 0 modified
    @@ -62,6 +62,30 @@ mount {
     	is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
         src: "/dev/random"
         dst: "/dev/random"
    
  • backend/windmill-worker/nsjail/install.r.config.proto+24 0 modified
    @@ -55,6 +55,30 @@ mount {
     	is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
     	src: "/dev/null"
     	dst: "/dev/null"
    
  • backend/windmill-worker/nsjail/lock.ruby.config.proto+24 0 modified
    @@ -55,6 +55,30 @@ mount {
     	is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
     	src: "/dev/null"
     	dst: "/dev/null"
    
  • backend/windmill-worker/nsjail/run.ansible.config.proto+24 0 modified
    @@ -123,6 +123,30 @@ mount {
     	is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
         src: "/dev/random"
         dst: "/dev/random"
    
  • backend/windmill-worker/nsjail/run.bash.config.proto+24 0 modified
    @@ -95,6 +95,30 @@ mount {
     	is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
         src: "/dev/random"
         dst: "/dev/random"
    
  • backend/windmill-worker/nsjail/run.bun.config.proto+24 0 modified
    @@ -153,6 +153,30 @@ mount {
     	is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
         src: "/dev/random"
         dst: "/dev/random"
    
  • backend/windmill-worker/nsjail/run.csharp.config.proto+24 0 modified
    @@ -91,6 +91,30 @@ mount {
     	is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
         src: "/dev/random"
         dst: "/dev/random"
    
  • backend/windmill-worker/nsjail/run.go.config.proto+24 0 modified
    @@ -84,6 +84,30 @@ mount {
     	is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
         src: "/dev/random"
         dst: "/dev/random"
    
  • backend/windmill-worker/nsjail/run.java.config.proto+24 0 modified
    @@ -92,6 +92,30 @@ mount {
         is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
         src: "/dev/random"
         dst: "/dev/random"
    
  • backend/windmill-worker/nsjail/run.nu.config.proto+24 0 modified
    @@ -89,6 +89,30 @@ mount {
     	is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
         src: "/dev/random"
         dst: "/dev/random"
    
  • backend/windmill-worker/nsjail/run.php.config.proto+24 0 modified
    @@ -78,6 +78,30 @@ mount {
     	is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
         src: "/dev/random"
         dst: "/dev/random"
    
  • backend/windmill-worker/nsjail/run.powershell.config.proto+24 0 modified
    @@ -91,6 +91,30 @@ mount {
     	is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
         src: "/dev/random"
         dst: "/dev/random"
    
  • backend/windmill-worker/nsjail/run.python3.config.proto+24 0 modified
    @@ -107,6 +107,30 @@ mount {
     	is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
         dst: "/dev/shm"
         fstype: "tmpfs"
    
  • backend/windmill-worker/nsjail/run.r.config.proto+24 0 modified
    @@ -92,6 +92,30 @@ mount {
         is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
         src: "/sys/devices/system/cpu"
         dst: "/sys/devices/system/cpu"
    
  • backend/windmill-worker/nsjail/run.ruby.config.proto+24 0 modified
    @@ -92,6 +92,30 @@ mount {
         is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
         src: "/dev/random"
         dst: "/dev/random"
    
  • backend/windmill-worker/nsjail/run.rust.config.proto+24 0 modified
    @@ -84,6 +84,30 @@ mount {
     	is_bind: true
     }
     
    +# Container runtimes bind exactly these 3 files as separate submounts over
    +# /etc; nsjail's ro remount of /etc is non-recursive so they stay writable.
    +# Load-bearing -- do not remove as redundant with the /etc bind above.
    +mount {
    +    src: "/etc/resolv.conf"
    +    dst: "/etc/resolv.conf"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hosts"
    +    dst: "/etc/hosts"
    +    is_bind: true
    +    mandatory: false
    +}
    +
    +mount {
    +    src: "/etc/hostname"
    +    dst: "/etc/hostname"
    +    is_bind: true
    +    mandatory: false
    +}
    +
     mount {
         src: "/dev/random"
         dst: "/dev/random"
    

Vulnerability mechanics

Root cause

"In nsjail sandbox configuration files, /etc is bind-mounted read-only but the remount is non-recursive, leaving Kubernetes kubelet submounts (/etc/hosts, /etc/resolv.conf, /etc/hostname) writable from within the sandbox."

Attack vector

An authenticated user submits a script to a Windmill worker pod. Inside the nsjail sandbox, the /etc directory appears read-only, but the three kubelet submounts (/etc/hosts, /etc/resolv.conf, /etc/hostname) remain writable because nsjail's read-only remount is non-recursive [patch_id=626083]. The attacker's script writes arbitrary entries to these files (e.g., redirecting a victim workspace's hostname to an attacker-controlled IP). Because the files are on the host filesystem, the poisoned entries persist across all subsequent script executions on the same worker pod, affecting scripts from any tenant. The attacker can then redirect DNS queries to intercept WM_TOKEN JWTs, gaining workspace-admin access to victim workspaces.

Affected code

All nsjail configuration protobuf files under backend/windmill-worker/nsjail/ are affected: download.py.config.proto, download.ruby.config.proto, download.rust.config.proto, install.r.config.proto, lock.ruby.config.proto, run.ansible.config.proto, run.bash.config.proto, run.bun.config.proto, run.csharp.config.proto, run.go.config.proto, and likely others. Each file had a read-only bind mount for /etc but lacked explicit per-file bind mounts for the kubelet submounts /etc/resolv.conf, /etc/hosts, and /etc/hostname [patch_id=626083].

What the fix does

The patch adds explicit per-file bind mounts for /etc/resolv.conf, /etc/hosts, and /etc/hostname in every nsjail config proto file [patch_id=626083]. Each new mount block binds the file read-only (the default when no `rw: true` is specified) and uses `mandatory: false` so the sandbox still starts if a file is absent. This ensures that even though the parent /etc bind mount's read-only flag does not propagate to the kubelet submounts, the individual file binds enforce read-only access. The commit message explicitly warns future maintainers not to remove these binds as redundant.

Preconditions

  • authAttacker must be an authenticated Windmill user with the ability to submit scripts for execution.
  • configWindmill worker pods must be running on Kubernetes (or a container runtime that creates separate submounts for /etc/hosts, /etc/resolv.conf, /etc/hostname).
  • networkAttacker must control a network endpoint capable of intercepting or redirecting traffic from the worker pod.

Generated on May 19, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.