Root cause
Validation & fail-open logic
A Claude-assisted hardening change validates and sanitizes Environment pod specs but not their standalone container fields.
How AI contributed: Flawed AI-written codePublished Jun 10, 2026 in
fission/fissionAll 3 findings in this repository
Provisional finding. Full causal and release verification remains incomplete.
Fission merges each Environment's runtime and builder container settings into the pods its executor and builder services create. A Claude-assisted hardening change validated and sanitized the security context only in the Environment's pod specs and left the separate container fields unchecked, so a tenant allowed to create or update an Environment could still request a privileged container, privilege escalation or dangerous capabilities through those fields. The unchecked container fields themselves came from earlier human code. A Claude-assisted follow-up checks the container fields too. The hardening change and its follow-up first shipped together in the 1.24.0 prerelease, and 1.25.0 is the first stable release with both, so no release carries the incomplete hardening alone; the releases through 1.23.0 that the advisory lists have no container check at all.
Root cause
A Claude-assisted hardening change validates and sanitizes Environment pod specs but not their standalone container fields.
Fix
A Claude-assisted follow-up validates and sanitizes the standalone container fields too.
Case facts
@@ -666,6 +669,11 @@ func (e *Environment) Validate() error { } } }+ // Reject podspec fields that would let a tenant escalate via the+ // executor / buildermgr service accounts. Closes GHSA-gx55-f84r-v3r7,+ // GHSA-wmgg-3p4h-48x7.+ errs = errors.Join(errs, ValidatePodSpecSafety("Environment.spec.runtime.podspec", e.Spec.Runtime.PodSpec))+ errs = errors.Join(errs, ValidatePodSpecSafety("Environment.spec.builder.podspec", e.Spec.Builder.PodSpec)) return errs } @@ -79,8 +79,29 @@ func MergePodSpec(srcPodSpec *apiv1.PodSpec, targetPodSpec *apiv1.PodSpec) (*api srcPodSpec.InitContainers = cList } - // For volumes - if duplicate exist, throw error- vols, err := mergeVolumeLists(srcPodSpec.Volumes, targetPodSpec.Volumes)+ // Sanitize per-container SecurityContext after the merge. The admission+ // webhook rejects privileged=true / allowPrivilegeEscalation=true and+ // dangerous capabilities (SYS_ADMIN, NET_ADMIN, etc.) at submit time,+ // but a webhook-bypass cluster (failurePolicy=Ignore or a stale object+ // from a pre-webhook upgrade window) could still reach this code path.+ // Strip the dangerous bits from the merged result so the resulting pod+ // cannot escape its container even if admission was bypassed. Closes+ // GHSA-gx55-f84r-v3r7 / GHSA-wmgg-3p4h-48x7 / GHSA-v455-mv2v-5g92.+ for i := range srcPodSpec.Containers {+ sanitizeContainerSecurityContext(&srcPodSpec.Containers[i])+ }+ for i := range srcPodSpec.InitContainers {+ sanitizeContainerSecurityContext(&srcPodSpec.InitContainers[i])+ }++ // For volumes - if duplicate exist, throw error. hostPath volumes are+ // stripped from the target before merge: a tenant-supplied hostPath+ // mount is a node-escape primitive (read /etc, the container runtime+ // socket, etc.). The admission webhook rejects them, and this layer+ // makes them unreachable even on webhook-bypass clusters. Closes+ // GHSA-gx55-f84r-v3r7 / GHSA-wmgg-3p4h-48x7 / GHSA-v455-mv2v-5g92.+ filteredTargetVols := stripHostPathVolumes(targetPodSpec.Volumes)+ vols, err := mergeVolumeLists(srcPodSpec.Volumes, filteredTargetVols) if err != nil { multierr = errors.Join(multierr, err) } else {@@ -662,6 +662,14 @@ func (e *Environment) Validate() error { // GHSA-wmgg-3p4h-48x7. errs = errors.Join(errs, ValidatePodSpecSafety("Environment.spec.runtime.podspec", e.Spec.Runtime.PodSpec)) errs = errors.Join(errs, ValidatePodSpecSafety("Environment.spec.builder.podspec", e.Spec.Builder.PodSpec))+ // The standalone Runtime.Container / Builder.Container fields are merged+ // into the runtime / builder pod without going through any PodSpec, so+ // ValidatePodSpecSafety above does not reach them. Validate their+ // SecurityContext directly — otherwise a tenant could set+ // spec.runtime.container.securityContext.privileged=true and bypass the+ // PodSpec hardening. Closes GHSA-m63v-2g9w-2w6v.+ errs = errors.Join(errs, ValidateContainerSafety("Environment.spec.runtime.container", e.Spec.Runtime.Container))+ errs = errors.Join(errs, ValidateContainerSafety("Environment.spec.builder.container", e.Spec.Builder.Container)) return errs } @@ -39,6 +39,17 @@ func MergeContainer(dst *apiv1.Container, src *apiv1.Container) (*apiv1.Containe checkSliceConflicts("Name", dstC.VolumeMounts), checkSliceConflicts("Name", dstC.VolumeDevices)) + // mergo.WithOverride copies src.SecurityContext onto the merged result,+ // so a tenant-supplied Environment Runtime.Container / Builder.Container+ // with privileged=true / allowPrivilegeEscalation=true / dangerous caps+ // would otherwise reach the running pod. The admission webhook+ // (ValidateContainerSafety) is the primary defence; this strips the+ // dangerous bits at the merge layer so a webhook-bypass cluster+ // (failurePolicy=Ignore or stale objects from a pre-webhook upgrade)+ // is still protected. Closes GHSA-m63v-2g9w-2w6v (the Container-field+ // sibling of GHSA-gx55 / GHSA-wmgg / GHSA-v455).+ sanitizeContainerSecurityContext(&dstC)+ return &dstC, errs } AI-assisted change 06fab5ca2d23ceb816cd69602eea4b4d36c044954207b5c22a701a1322d0b5cd · Fix 01f98f47d33c9ddd90271a4fe40200011a0669ece1cd22bbd7b87823823fd406