From 99d40f0b0f871de81192eb9e6851a3f08e62dd80 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:06:49 +0100 Subject: [PATCH 1/3] feat(supervisor): configurable security context for run pods --- apps/supervisor/src/env.ts | 1 + .../src/workloadManager/kubernetes.test.ts | 22 +++++++++++++++++++ .../src/workloadManager/kubernetes.ts | 2 ++ .../src/workloadManager/kubernetesPodSpec.ts | 19 ++++++++++++++++ 4 files changed, 44 insertions(+) diff --git a/apps/supervisor/src/env.ts b/apps/supervisor/src/env.ts index 3f2cc8b46e9..21d29179d6e 100644 --- a/apps/supervisor/src/env.ts +++ b/apps/supervisor/src/env.ts @@ -220,6 +220,7 @@ export const Env = z KUBERNETES_RUNNER_SECCOMP_PROFILE_RUNTIMES: z .enum(["none", "node-24-plus", "all"]) .default("node-24-plus"), + KUBERNETES_RUNNER_SECURITY_CONTEXT: z.enum(["off", "baseline", "restricted"]).default("off"), // Pod DNS config — override the cluster default ndots to `KUBERNETES_POD_DNS_NDOTS`. // Default k8s ndots is 5: any name with fewer than 5 dots (e.g. `api.example.com`, 2 dots) is first walked diff --git a/apps/supervisor/src/workloadManager/kubernetes.test.ts b/apps/supervisor/src/workloadManager/kubernetes.test.ts index e3023bc5d84..a860b89abc9 100644 --- a/apps/supervisor/src/workloadManager/kubernetes.test.ts +++ b/apps/supervisor/src/workloadManager/kubernetes.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest"; import { nodetypeNodeSelector, runPodTolerations, + runnerSecurityContext, withRunnerSeccompProfile, withNodeSelector, } from "./kubernetesPodSpec.js"; @@ -153,3 +154,24 @@ describe("withRunnerSeccompProfile", () => { } }); }); + +describe("runnerSecurityContext", () => { + it("sets nothing when off", () => { + expect(runnerSecurityContext("off")).toBeUndefined(); + }); + + it("drops all capabilities and blocks escalation at baseline", () => { + expect(runnerSecurityContext("baseline")).toEqual({ + allowPrivilegeEscalation: false, + capabilities: { drop: ["ALL"] }, + }); + }); + + it("additionally requires a non-root image when restricted", () => { + expect(runnerSecurityContext("restricted")).toEqual({ + allowPrivilegeEscalation: false, + capabilities: { drop: ["ALL"] }, + runAsNonRoot: true, + }); + }); +}); diff --git a/apps/supervisor/src/workloadManager/kubernetes.ts b/apps/supervisor/src/workloadManager/kubernetes.ts index 54a9428efeb..1a4e878771e 100644 --- a/apps/supervisor/src/workloadManager/kubernetes.ts +++ b/apps/supervisor/src/workloadManager/kubernetes.ts @@ -17,6 +17,7 @@ import { getRunnerId } from "../util.js"; import { nodetypeNodeSelector, runPodTolerations, + runnerSecurityContext, withRunnerSeccompProfile, withNodeSelector, } from "./kubernetesPodSpec.js"; @@ -175,6 +176,7 @@ export class KubernetesWorkloadManager implements WorkloadManager { }, ], resources: this.#getResourcesForMachine(opts.machine), + securityContext: runnerSecurityContext(env.KUBERNETES_RUNNER_SECURITY_CONTEXT), env: [ { name: "TRIGGER_DEQUEUED_AT_MS", diff --git a/apps/supervisor/src/workloadManager/kubernetesPodSpec.ts b/apps/supervisor/src/workloadManager/kubernetesPodSpec.ts index 6c6ddfc09b0..6736ba496fe 100644 --- a/apps/supervisor/src/workloadManager/kubernetesPodSpec.ts +++ b/apps/supervisor/src/workloadManager/kubernetesPodSpec.ts @@ -96,3 +96,22 @@ export function withRunnerSeccompProfile( }, }; } + +/** + * runnerSecurityContext maps a configured level onto the run container's security + * context. "baseline" drops the capability bounding set and blocks setuid + * escalation; "restricted" additionally requires a non-root image. + */ +export function runnerSecurityContext( + level: "off" | "baseline" | "restricted" +): k8s.V1SecurityContext | undefined { + if (level === "off") { + return undefined; + } + + return { + allowPrivilegeEscalation: false, + capabilities: { drop: ["ALL"] }, + ...(level === "restricted" ? { runAsNonRoot: true } : {}), + }; +} From 5e075938f5e3c056f62b64c0397fe5dbdef97139 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:13:02 +0100 Subject: [PATCH 2/3] fix(supervisor): pin the uid at the restricted level --- apps/supervisor/src/env.ts | 1 + .../supervisor/src/workloadManager/kubernetes.test.ts | 7 ++++--- apps/supervisor/src/workloadManager/kubernetes.ts | 5 ++++- .../src/workloadManager/kubernetesPodSpec.ts | 11 ++++++++--- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/supervisor/src/env.ts b/apps/supervisor/src/env.ts index 21d29179d6e..e066ae31348 100644 --- a/apps/supervisor/src/env.ts +++ b/apps/supervisor/src/env.ts @@ -221,6 +221,7 @@ export const Env = z .enum(["none", "node-24-plus", "all"]) .default("node-24-plus"), KUBERNETES_RUNNER_SECURITY_CONTEXT: z.enum(["off", "baseline", "restricted"]).default("off"), + KUBERNETES_RUNNER_RUN_AS_USER: z.coerce.number().int().min(1).default(1000), // Pod DNS config — override the cluster default ndots to `KUBERNETES_POD_DNS_NDOTS`. // Default k8s ndots is 5: any name with fewer than 5 dots (e.g. `api.example.com`, 2 dots) is first walked diff --git a/apps/supervisor/src/workloadManager/kubernetes.test.ts b/apps/supervisor/src/workloadManager/kubernetes.test.ts index a860b89abc9..99a5fe4515e 100644 --- a/apps/supervisor/src/workloadManager/kubernetes.test.ts +++ b/apps/supervisor/src/workloadManager/kubernetes.test.ts @@ -157,21 +157,22 @@ describe("withRunnerSeccompProfile", () => { describe("runnerSecurityContext", () => { it("sets nothing when off", () => { - expect(runnerSecurityContext("off")).toBeUndefined(); + expect(runnerSecurityContext("off", 1000)).toBeUndefined(); }); it("drops all capabilities and blocks escalation at baseline", () => { - expect(runnerSecurityContext("baseline")).toEqual({ + expect(runnerSecurityContext("baseline", 1000)).toEqual({ allowPrivilegeEscalation: false, capabilities: { drop: ["ALL"] }, }); }); it("additionally requires a non-root image when restricted", () => { - expect(runnerSecurityContext("restricted")).toEqual({ + expect(runnerSecurityContext("restricted", 1000)).toEqual({ allowPrivilegeEscalation: false, capabilities: { drop: ["ALL"] }, runAsNonRoot: true, + runAsUser: 1000, }); }); }); diff --git a/apps/supervisor/src/workloadManager/kubernetes.ts b/apps/supervisor/src/workloadManager/kubernetes.ts index 1a4e878771e..154c58b18d1 100644 --- a/apps/supervisor/src/workloadManager/kubernetes.ts +++ b/apps/supervisor/src/workloadManager/kubernetes.ts @@ -176,7 +176,10 @@ export class KubernetesWorkloadManager implements WorkloadManager { }, ], resources: this.#getResourcesForMachine(opts.machine), - securityContext: runnerSecurityContext(env.KUBERNETES_RUNNER_SECURITY_CONTEXT), + securityContext: runnerSecurityContext( + env.KUBERNETES_RUNNER_SECURITY_CONTEXT, + env.KUBERNETES_RUNNER_RUN_AS_USER + ), env: [ { name: "TRIGGER_DEQUEUED_AT_MS", diff --git a/apps/supervisor/src/workloadManager/kubernetesPodSpec.ts b/apps/supervisor/src/workloadManager/kubernetesPodSpec.ts index 6736ba496fe..3e026d2647c 100644 --- a/apps/supervisor/src/workloadManager/kubernetesPodSpec.ts +++ b/apps/supervisor/src/workloadManager/kubernetesPodSpec.ts @@ -100,10 +100,15 @@ export function withRunnerSeccompProfile( /** * runnerSecurityContext maps a configured level onto the run container's security * context. "baseline" drops the capability bounding set and blocks setuid - * escalation; "restricted" additionally requires a non-root image. + * escalation; "restricted" additionally pins the container to a non-root uid. + * + * The uid is set explicitly rather than relying on the image: the kubelet cannot + * verify `runAsNonRoot` when an image declares a named user, and fails the + * container instead. */ export function runnerSecurityContext( - level: "off" | "baseline" | "restricted" + level: "off" | "baseline" | "restricted", + runAsUser: number ): k8s.V1SecurityContext | undefined { if (level === "off") { return undefined; @@ -112,6 +117,6 @@ export function runnerSecurityContext( return { allowPrivilegeEscalation: false, capabilities: { drop: ["ALL"] }, - ...(level === "restricted" ? { runAsNonRoot: true } : {}), + ...(level === "restricted" ? { runAsNonRoot: true, runAsUser } : {}), }; } From 82b1deba5b21cccf39dcad7834d972a6c20e637b Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Tue, 18 Aug 2026 18:25:27 +0100 Subject: [PATCH 3/3] feat(supervisor): select the pinned uid by runtime --- .../src/workloadManager/kubernetes.test.ts | 18 ++++++++++++++---- .../src/workloadManager/kubernetes.ts | 3 ++- .../src/workloadManager/kubernetesPodSpec.ts | 16 +++++++++++----- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/apps/supervisor/src/workloadManager/kubernetes.test.ts b/apps/supervisor/src/workloadManager/kubernetes.test.ts index 99a5fe4515e..e99e292aca1 100644 --- a/apps/supervisor/src/workloadManager/kubernetes.test.ts +++ b/apps/supervisor/src/workloadManager/kubernetes.test.ts @@ -157,22 +157,32 @@ describe("withRunnerSeccompProfile", () => { describe("runnerSecurityContext", () => { it("sets nothing when off", () => { - expect(runnerSecurityContext("off", 1000)).toBeUndefined(); + expect(runnerSecurityContext("off", 1000, "node-24")).toBeUndefined(); }); it("drops all capabilities and blocks escalation at baseline", () => { - expect(runnerSecurityContext("baseline", 1000)).toEqual({ + expect(runnerSecurityContext("baseline", 1000, "node-24")).toEqual({ allowPrivilegeEscalation: false, capabilities: { drop: ["ALL"] }, }); }); - it("additionally requires a non-root image when restricted", () => { - expect(runnerSecurityContext("restricted", 1000)).toEqual({ + it("pins the configured uid when restricted", () => { + expect(runnerSecurityContext("restricted", 1000, "node-24")).toEqual({ allowPrivilegeEscalation: false, capabilities: { drop: ["ALL"] }, runAsNonRoot: true, runAsUser: 1000, }); }); + + it("pins bun's own uid, which differs from node's", () => { + expect(runnerSecurityContext("restricted", 1000, "bun")?.runAsUser).toBe(1001); + }); + + it("falls back to the configured uid when the runtime is unknown", () => { + for (const runtime of [undefined, null, "", "node", "node-22", "node-26"]) { + expect(runnerSecurityContext("restricted", 1000, runtime)?.runAsUser).toBe(1000); + } + }); }); diff --git a/apps/supervisor/src/workloadManager/kubernetes.ts b/apps/supervisor/src/workloadManager/kubernetes.ts index 154c58b18d1..b7a2f752196 100644 --- a/apps/supervisor/src/workloadManager/kubernetes.ts +++ b/apps/supervisor/src/workloadManager/kubernetes.ts @@ -178,7 +178,8 @@ export class KubernetesWorkloadManager implements WorkloadManager { resources: this.#getResourcesForMachine(opts.machine), securityContext: runnerSecurityContext( env.KUBERNETES_RUNNER_SECURITY_CONTEXT, - env.KUBERNETES_RUNNER_RUN_AS_USER + env.KUBERNETES_RUNNER_RUN_AS_USER, + opts.runtime ), env: [ { diff --git a/apps/supervisor/src/workloadManager/kubernetesPodSpec.ts b/apps/supervisor/src/workloadManager/kubernetesPodSpec.ts index 3e026d2647c..ddd336d2f2e 100644 --- a/apps/supervisor/src/workloadManager/kubernetesPodSpec.ts +++ b/apps/supervisor/src/workloadManager/kubernetesPodSpec.ts @@ -97,18 +97,22 @@ export function withRunnerSeccompProfile( }; } +const BUN_RUN_AS_USER = 1001; + /** * runnerSecurityContext maps a configured level onto the run container's security * context. "baseline" drops the capability bounding set and blocks setuid * escalation; "restricted" additionally pins the container to a non-root uid. * - * The uid is set explicitly rather than relying on the image: the kubelet cannot - * verify `runAsNonRoot` when an image declares a named user, and fails the - * container instead. + * The uid is set explicitly rather than read from the image: the kubelet cannot + * verify `runAsNonRoot` against an image that declares a named user, and fails + * the container instead. Bun images carry their user at a different uid to + * node's, so the runtime selects which uid is pinned. */ export function runnerSecurityContext( level: "off" | "baseline" | "restricted", - runAsUser: number + runAsUser: number, + runtime: string | null | undefined ): k8s.V1SecurityContext | undefined { if (level === "off") { return undefined; @@ -117,6 +121,8 @@ export function runnerSecurityContext( return { allowPrivilegeEscalation: false, capabilities: { drop: ["ALL"] }, - ...(level === "restricted" ? { runAsNonRoot: true, runAsUser } : {}), + ...(level === "restricted" + ? { runAsNonRoot: true, runAsUser: runtime === "bun" ? BUN_RUN_AS_USER : runAsUser } + : {}), }; }