Supply Chain

Secure Helm Charts with cnspec

Scan Helm charts for security misconfigurations before installing them in your Kubernetes clusters.

Catch insecure Kubernetes configurations before installing a chart. cnspec parses Chart.yaml, values, templates, and the rendered Kubernetes resources, exposing chart metadata, dependencies, maintainers, lifecycle hooks, Custom Resource Definitions, and resources as queryable MQL resources. cnspec renders the chart with Helm's own value-merge semantics, so you can supply the same --values files and --set overrides you would use at install time and audit the resources that actually result. It scans charts on disk, packaged .tgz archives, and remote charts pulled from OCI registries or chart repositories. The Kubernetes Security policy applies to the rendered resources, and you can use the Helm provider inside your own policies to enforce chart-level standards.

Prerequisites

To scan Helm charts with cnspec, you must have:

Scan Helm charts

Scan a Helm chart directory:

cnspec scan helm ./my-chart

Scan a packaged chart archive:

cnspec scan helm my-chart-1.2.3.tgz

If your chart pulls in remote dependencies, run helm dependency build first so the subcharts are vendored into the chart's charts/ directory before you scan.

Scan a remote chart

cnspec can fetch and scan a chart without you downloading it first.

Pull a chart from an OCI registry:

cnspec scan helm oci://registry-1.docker.io/bitnamicharts/nginx --version 18.2.5

Scan a packaged chart served over HTTPS:

cnspec scan helm https://example.com/charts/my-chart-1.2.3.tgz

Resolve a chart by name from a repository index:

cnspec scan helm nginx --repo https://charts.bitnami.com/bitnami --version 18.2.5

Use these flags to control how a remote chart is located and authenticated:

OptionDescription
--repoChart repository URL used to locate a chart referenced by name
--versionChart version to pull when fetching a remote chart
--usernameUsername for the chart repository or OCI registry
--passwordPassword for the chart repository or OCI registry
--repository-configPath to the Helm repositories.yaml configuration
--repository-cachePath to the Helm repository cache directory

Render with custom values

By default cnspec renders a chart with its bundled values.yaml. To audit the resources your real deployment produces, supply the same value overrides you use at install time. cnspec merges them using Helm's own precedence rules.

Render with a values file and a command-line override:

cnspec scan helm ./my-chart --values prod-values.yaml --set replicaCount=3

Render as a specific release into a target namespace and Kubernetes version:

cnspec scan helm ./my-chart --release-name web --namespace production --kube-version 1.30

Use these flags to control rendering:

OptionDescription
--valuesValues file to merge into the chart before rendering. Repeatable. -f is reserved on cnspec scan for --policy-bundle, so use the long form here.
--setOverride values on the command line as key=value. Repeatable.
--set-stringOverride values on the command line, forcing the value to a string. Repeatable.
--set-jsonOverride values with a JSON key=json pair. Repeatable.
--set-fileSet a value from the contents of a file as key=path. Repeatable.
--release-nameRelease name used during rendering. Defaults to the chart name.
-n, --namespaceRelease namespace used during rendering. Defaults to default.
--kube-versionTarget Kubernetes version for .Capabilities.KubeVersion during rendering.
-a, --api-versionsKubernetes API versions for .Capabilities.APIVersions.Has. Repeatable.
--is-upgradeRender with .Release.IsUpgrade set instead of .Release.IsInstall.

Scan options

OptionDescription
--asset-nameOverride the asset name
--annotationAdd an annotation to the asset (key=value)
--incognitoRun in incognito mode (do not report results to Mondoo Platform)
-o, --outputSet the output format (compact, full, json, junit, summary, yaml)
-f, --policy-bundlePath to a policy file (local path, s3:// URI, or http(s):// URL)
--policySpecify policies to execute (requires --policy-bundle)
--risk-thresholdExit with status 1 if any risk meets or exceeds this value (0-100)

Example checks

Run cnspec shell helm ./my-chart to open the interactive shell. From there you can make checks like the examples below.

List chart metadata

helm.charts { name version appVersion type }

Find deprecated charts

helm.charts.where(deprecated == true) { name version }

Confirm every chart has a maintainer

helm.charts.all(maintainers.length > 0)

Compare default and rendered values

values is always the chart's bundled values.yaml. renderedValues is the coalesced result of the defaults and any --values or --set overrides, exactly what cnspec used to render the resources:

helm.charts { name renderedValues }

Audit the post-install notes

notes is the rendered templates/NOTES.txt, the message Helm prints after install. Auditing it catches secrets or instructions accidentally emitted to operators:

helm.charts { name notes }

List all rendered Kubernetes resources

helm.charts { resources { kind name namespace } }

Check that no container runs as root

helm.charts {
  resources.where(kind == "Deployment").all(
    manifest["spec"]["template"]["spec"]["securityContext"]["runAsNonRoot"] == true
  )
}

Audit lifecycle hooks

hooks lists the resources carrying a helm.sh/hook annotation. They run outside the normal apply order and are often privileged jobs:

helm.charts { hooks { name hookTypes hookWeight hookDeletePolicies } }

Require every hook to declare a deletion policy so it does not linger after running:

helm.charts { hooks.all(hookDeletePolicies.length > 0) }

Review Custom Resource Definitions

crds lists the CRDs shipped under the chart's crds/ directory. Helm installs these before templates render, so they are not part of resources:

helm.charts { crds { name kind } }

Require a values schema

Charts that ship a values.schema.json validate supplied values at install time. Require one on every chart:

helm.charts.all(valuesSchema != null)

Verify pinned dependency versions

lock exposes the chart's Chart.lock, which records the concrete dependency versions Helm resolved:

helm.charts { lock { generated digest dependencies { name resolvedVersion } } }

Require every chart to pin its dependencies with a lock file:

helm.charts.all(lock != null)

Inspect chart dependencies

version is the declared constraint such as ^1.2.0, while resolvedVersion is the exact version locked in Chart.lock:

helm.charts { dependencies { name version resolvedVersion repository } }

Traverse from a declared dependency into the vendored subchart that satisfies it:

helm.charts {
  dependencies.where(chart != null) {
    name
    chart { name templates { name } }
  }
}

Gate on chart linting

lint runs Helm's built-in lint rules so CI can fail a chart without shelling out to helm lint:

helm.charts.all(lint.passed)

List the individual lint messages and their severity:

helm.charts { lint { passed messages { severity path message } } }

Find templates that require a live cluster

requiresCluster is true when a template calls the lookup function, which queries a running cluster at render time and cannot be fully evaluated by static analysis:

helm.charts { templates.where(requiresCluster) { name } }

Find templates that use Go template directives

helm.charts { templates { name directives { name } } }

Inspect template files

size and isBinary help you spot bundled binaries such as icons among a chart's files:

helm.charts { files.where(isBinary) { path size } }

Learn more

On this page