From ad5e598f3f611d0841312894135c5ef1233dc55c Mon Sep 17 00:00:00 2001 From: Mert Bozfakioglu Date: Mon, 8 Jun 2026 13:38:40 -0700 Subject: [PATCH 1/5] Run code-executor unprivileged with seccomp on k8s >= 1.33 On Kubernetes 1.33+ (where the ProcMountType and UserNamespacesSupport feature gates are on by default), the code-executor now runs unprivileged using a localhost seccomp profile, NET_ADMIN, an unmasked /proc, and user namespaces - mirroring how the JS executor sandboxes itself. The nsjail seccomp profile is installed onto the node by an install-seccomp init container. On older clusters it falls back to the existing privileged mode, so the chart still installs without requiring 1.33+. Setting codeExecutor.securityContext explicitly continues to override this behavior for either mode. Co-authored-by: Cursor --- charts/retool/Chart.yaml | 2 +- .../templates/configmap_code_executor.yaml | 13 ++++ .../templates/deployment_code_executor.yaml | 64 +++++++++++++++++-- charts/retool/values.yaml | 19 ++++-- 4 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 charts/retool/templates/configmap_code_executor.yaml diff --git a/charts/retool/Chart.yaml b/charts/retool/Chart.yaml index dbedb808..7d8ed5ee 100644 --- a/charts/retool/Chart.yaml +++ b/charts/retool/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: retool description: A Helm chart for Kubernetes type: application -version: 6.11.0 +version: 6.12.0 maintainers: - name: Retool Engineering email: engineering+helm@retool.com diff --git a/charts/retool/templates/configmap_code_executor.yaml b/charts/retool/templates/configmap_code_executor.yaml new file mode 100644 index 00000000..9252af09 --- /dev/null +++ b/charts/retool/templates/configmap_code_executor.yaml @@ -0,0 +1,13 @@ +{{- if include "retool.workflows.enabled" . }} +{{- if and (not .Values.codeExecutor.securityContext) (semverCompare ">=1.33-0" .Capabilities.KubeVersion.Version) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "retool.fullname" . }}-code-executor-seccomp + labels: + {{- include "retool.labels" . | nindent 4 }} +data: + nsjail-seccomp.json: | + {{- .Files.Get "files/nsjail-seccomp.json" | nindent 4 }} +{{- end }} +{{- end }} diff --git a/charts/retool/templates/deployment_code_executor.yaml b/charts/retool/templates/deployment_code_executor.yaml index 6750e615..e2ba0dc6 100644 --- a/charts/retool/templates/deployment_code_executor.yaml +++ b/charts/retool/templates/deployment_code_executor.yaml @@ -1,4 +1,5 @@ {{- if include "retool.workflows.enabled" . }} +{{- $useSecComp := and (not .Values.codeExecutor.securityContext) (semverCompare ">=1.33-0" .Capabilities.KubeVersion.Version) -}} apiVersion: apps/v1 kind: Deployment metadata: @@ -23,6 +24,9 @@ spec: template: metadata: annotations: +{{- if $useSecComp }} + checksum/seccomp: {{ .Files.Get "files/nsjail-seccomp.json" | sha256sum }} +{{- end }} {{- if .Values.podAnnotations }} {{ toYaml .Values.podAnnotations | indent 8 }} {{- end }} @@ -44,11 +48,43 @@ spec: {{- if .Values.priorityClassName }} priorityClassName: "{{ .Values.priorityClassName }}" {{- end }} -{{- if .Values.initContainers }} +{{- if $useSecComp }} + hostUsers: false +{{- end }} +{{- if or $useSecComp .Values.initContainers }} initContainers: +{{- if $useSecComp }} + - name: install-seccomp + image: busybox:1.37.0@sha256:b3255e7dfbcd10cb367af0d409747d511aeb66dfac98cf30e97e87e4207dd76f + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] + resources: + requests: + cpu: 1m + memory: 4Mi + limits: + cpu: 10m + memory: 16Mi + command: + - /bin/sh + - -c + - | + DEST="/host-seccomp/{{ .Values.codeExecutor.seccompLocalhostProfile }}" + mkdir -p "$(dirname "$DEST")" + cp /seccomp-profile/nsjail-seccomp.json "$DEST" + echo "seccomp profile installed at $DEST" + volumeMounts: + - name: seccomp-profile + mountPath: /seccomp-profile + - name: host-seccomp + mountPath: /host-seccomp +{{- end }} {{- range $key, $value := .Values.initContainers }} - - name: "{{ $key }}" -{{ toYaml $value | indent 8 }} + - name: "{{ $key }}" +{{ toYaml $value | indent 10 }} {{- end }} {{- end }} containers: @@ -56,11 +92,18 @@ spec: image: "{{ .Values.codeExecutor.image.repository }}:{{ include "retool.codeExecutor.image.tag" . }}" imagePullPolicy: {{ .Values.image.pullPolicy }} securityContext: - {{ if .Values.codeExecutor.securityContext }} + {{- if .Values.codeExecutor.securityContext }} {{ toYaml .Values.codeExecutor.securityContext | indent 10 }} - {{ else }} + {{- else if $useSecComp }} + capabilities: + add: ["NET_ADMIN"] + procMount: Unmasked + seccompProfile: + type: Localhost + localhostProfile: {{ .Values.codeExecutor.seccompLocalhostProfile }} + {{- else }} privileged: true - {{ end }} + {{- end }} {{- if .Values.securityContext.extraContainerSecurityContext }} {{ toYaml .Values.securityContext.extraContainerSecurityContext | indent 10 }} {{- end }} @@ -128,6 +171,15 @@ spec: {{ tpl . $ | indent 6 }} {{- end }} volumes: +{{- if $useSecComp }} + - name: seccomp-profile + configMap: + name: {{ template "retool.fullname" . }}-code-executor-seccomp + - name: host-seccomp + hostPath: + path: /var/lib/kubelet/seccomp + type: DirectoryOrCreate +{{- end }} {{- if .Values.codeExecutor.volumes }} {{ toYaml .Values.codeExecutor.volumes | indent 8 }} {{- end }} diff --git a/charts/retool/values.yaml b/charts/retool/values.yaml index 33f80bbb..ac7b56d7 100644 --- a/charts/retool/values.yaml +++ b/charts/retool/values.yaml @@ -798,11 +798,20 @@ codeExecutor: cpu: 1000m memory: 1024Mi - # code executor uses nsjail to sandbox code execution. nsjail requires privileged container access. - # If your deployment does not support privileged access, you can set `privileged` to false to not - # use nsjail. Without nsjail, all code is run without sandboxing within your deployment. - securityContext: - privileged: true + # The code executor uses nsjail to sandbox code execution. + # On Kubernetes >= 1.33 it runs unprivileged using a localhost seccomp profile, + # NET_ADMIN, an unmasked /proc, and user namespaces (the profile is installed onto + # the node by an init container, the same way the JS executor does it). On older + # clusters it falls back to privileged mode, which nsjail also requires. + # Uncomment securityContext below to force a specific mode regardless of cluster + # version (e.g. `privileged: true`, or `privileged: false` to disable nsjail + # sandboxing entirely). + # securityContext: + # privileged: true + + # Path (relative to the kubelet seccomp root, /var/lib/kubelet/seccomp) at which + # the seccomp profile is installed and referenced when running unprivileged. + seccompLocalhostProfile: profiles/nsjail-seccomp.json # JS Executor jsExecutor: From 2cc7c2e594c67a05eaf10a8c544e949c2d401a6a Mon Sep 17 00:00:00 2001 From: Mert Bozfakioglu Date: Mon, 8 Jun 2026 13:42:44 -0700 Subject: [PATCH 2/5] Keep root values.yaml in sync with charts/retool/values.yaml Co-authored-by: Cursor --- values.yaml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/values.yaml b/values.yaml index 33f80bbb..ac7b56d7 100644 --- a/values.yaml +++ b/values.yaml @@ -798,11 +798,20 @@ codeExecutor: cpu: 1000m memory: 1024Mi - # code executor uses nsjail to sandbox code execution. nsjail requires privileged container access. - # If your deployment does not support privileged access, you can set `privileged` to false to not - # use nsjail. Without nsjail, all code is run without sandboxing within your deployment. - securityContext: - privileged: true + # The code executor uses nsjail to sandbox code execution. + # On Kubernetes >= 1.33 it runs unprivileged using a localhost seccomp profile, + # NET_ADMIN, an unmasked /proc, and user namespaces (the profile is installed onto + # the node by an init container, the same way the JS executor does it). On older + # clusters it falls back to privileged mode, which nsjail also requires. + # Uncomment securityContext below to force a specific mode regardless of cluster + # version (e.g. `privileged: true`, or `privileged: false` to disable nsjail + # sandboxing entirely). + # securityContext: + # privileged: true + + # Path (relative to the kubelet seccomp root, /var/lib/kubelet/seccomp) at which + # the seccomp profile is installed and referenced when running unprivileged. + seccompLocalhostProfile: profiles/nsjail-seccomp.json # JS Executor jsExecutor: From e256eb1f5c81ddd19c9ce7e098873987bc902f5a Mon Sep 17 00:00:00 2001 From: Mert Bozfakioglu Date: Mon, 8 Jun 2026 13:44:15 -0700 Subject: [PATCH 3/5] Drop codeExecutor securityContext comments Co-authored-by: Cursor --- charts/retool/values.yaml | 13 ------------- values.yaml | 13 ------------- 2 files changed, 26 deletions(-) diff --git a/charts/retool/values.yaml b/charts/retool/values.yaml index ac7b56d7..951d9ec3 100644 --- a/charts/retool/values.yaml +++ b/charts/retool/values.yaml @@ -798,19 +798,6 @@ codeExecutor: cpu: 1000m memory: 1024Mi - # The code executor uses nsjail to sandbox code execution. - # On Kubernetes >= 1.33 it runs unprivileged using a localhost seccomp profile, - # NET_ADMIN, an unmasked /proc, and user namespaces (the profile is installed onto - # the node by an init container, the same way the JS executor does it). On older - # clusters it falls back to privileged mode, which nsjail also requires. - # Uncomment securityContext below to force a specific mode regardless of cluster - # version (e.g. `privileged: true`, or `privileged: false` to disable nsjail - # sandboxing entirely). - # securityContext: - # privileged: true - - # Path (relative to the kubelet seccomp root, /var/lib/kubelet/seccomp) at which - # the seccomp profile is installed and referenced when running unprivileged. seccompLocalhostProfile: profiles/nsjail-seccomp.json # JS Executor diff --git a/values.yaml b/values.yaml index ac7b56d7..951d9ec3 100644 --- a/values.yaml +++ b/values.yaml @@ -798,19 +798,6 @@ codeExecutor: cpu: 1000m memory: 1024Mi - # The code executor uses nsjail to sandbox code execution. - # On Kubernetes >= 1.33 it runs unprivileged using a localhost seccomp profile, - # NET_ADMIN, an unmasked /proc, and user namespaces (the profile is installed onto - # the node by an init container, the same way the JS executor does it). On older - # clusters it falls back to privileged mode, which nsjail also requires. - # Uncomment securityContext below to force a specific mode regardless of cluster - # version (e.g. `privileged: true`, or `privileged: false` to disable nsjail - # sandboxing entirely). - # securityContext: - # privileged: true - - # Path (relative to the kubelet seccomp root, /var/lib/kubelet/seccomp) at which - # the seccomp profile is installed and referenced when running unprivileged. seccompLocalhostProfile: profiles/nsjail-seccomp.json # JS Executor From ef7ac7dd27597ee4cfd0d28dfcf9d2757d556d05 Mon Sep 17 00:00:00 2001 From: Mert Bozfakioglu Date: Mon, 8 Jun 2026 13:51:54 -0700 Subject: [PATCH 4/5] Document why code-executor uses seccomp on k8s 1.33+ Co-authored-by: Cursor --- charts/retool/templates/deployment_code_executor.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/charts/retool/templates/deployment_code_executor.yaml b/charts/retool/templates/deployment_code_executor.yaml index e2ba0dc6..ef316a7e 100644 --- a/charts/retool/templates/deployment_code_executor.yaml +++ b/charts/retool/templates/deployment_code_executor.yaml @@ -1,4 +1,15 @@ {{- if include "retool.workflows.enabled" . }} +{{- /* + Workflows are run securely within Code Executor via nsjail sandboxes. Creating + those sandboxes requires the container to have elevated privileges. By default + these privileges are granted by running the container as privileged + (securityContext.privileged: true). On Kubernetes 1.33+ we can instead grant only + what nsjail needs, far more granularly than the privileged flag: a slightly + relaxed version of Docker's default seccomp profile, the NET_ADMIN capability for + network isolation, and an unmasked /proc for process resource monitoring. + $useSecComp selects this less-privileged path when the operator has not pinned a + securityContext and the cluster is >= 1.33; otherwise we fall back to privileged. +*/ -}} {{- $useSecComp := and (not .Values.codeExecutor.securityContext) (semverCompare ">=1.33-0" .Capabilities.KubeVersion.Version) -}} apiVersion: apps/v1 kind: Deployment From 8e2af46235d2d296c3d4ea7a11570aaca50f8634 Mon Sep 17 00:00:00 2001 From: Mert Bozfakioglu Date: Mon, 8 Jun 2026 13:53:41 -0700 Subject: [PATCH 5/5] Note 1.33+ upgrade for fine-grained privileges Co-authored-by: Cursor --- charts/retool/templates/deployment_code_executor.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/charts/retool/templates/deployment_code_executor.yaml b/charts/retool/templates/deployment_code_executor.yaml index ef316a7e..4ae2fc88 100644 --- a/charts/retool/templates/deployment_code_executor.yaml +++ b/charts/retool/templates/deployment_code_executor.yaml @@ -9,6 +9,8 @@ network isolation, and an unmasked /proc for process resource monitoring. $useSecComp selects this less-privileged path when the operator has not pinned a securityContext and the cluster is >= 1.33; otherwise we fall back to privileged. + To use the more fine-grained privileges, please upgrade your k8s cluster to 1.33 + or higher. */ -}} {{- $useSecComp := and (not .Values.codeExecutor.securityContext) (semverCompare ">=1.33-0" .Capabilities.KubeVersion.Version) -}} apiVersion: apps/v1