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:
- cnspec installed on your workstation
- A Helm chart directory, a
.tgzarchive, or a remote chart reference to scan
Scan Helm charts
Scan a Helm chart directory:
cnspec scan helm ./my-chartScan a packaged chart archive:
cnspec scan helm my-chart-1.2.3.tgzIf 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.5Scan a packaged chart served over HTTPS:
cnspec scan helm https://example.com/charts/my-chart-1.2.3.tgzResolve a chart by name from a repository index:
cnspec scan helm nginx --repo https://charts.bitnami.com/bitnami --version 18.2.5Use these flags to control how a remote chart is located and authenticated:
| Option | Description |
|---|---|
--repo | Chart repository URL used to locate a chart referenced by name |
--version | Chart version to pull when fetching a remote chart |
--username | Username for the chart repository or OCI registry |
--password | Password for the chart repository or OCI registry |
--repository-config | Path to the Helm repositories.yaml configuration |
--repository-cache | Path 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=3Render as a specific release into a target namespace and Kubernetes version:
cnspec scan helm ./my-chart --release-name web --namespace production --kube-version 1.30Use these flags to control rendering:
| Option | Description |
|---|---|
--values | Values file to merge into the chart before rendering. Repeatable. -f is reserved on cnspec scan for --policy-bundle, so use the long form here. |
--set | Override values on the command line as key=value. Repeatable. |
--set-string | Override values on the command line, forcing the value to a string. Repeatable. |
--set-json | Override values with a JSON key=json pair. Repeatable. |
--set-file | Set a value from the contents of a file as key=path. Repeatable. |
--release-name | Release name used during rendering. Defaults to the chart name. |
-n, --namespace | Release namespace used during rendering. Defaults to default. |
--kube-version | Target Kubernetes version for .Capabilities.KubeVersion during rendering. |
-a, --api-versions | Kubernetes API versions for .Capabilities.APIVersions.Has. Repeatable. |
--is-upgrade | Render with .Release.IsUpgrade set instead of .Release.IsInstall. |
Scan options
| Option | Description |
|---|---|
--asset-name | Override the asset name |
--annotation | Add an annotation to the asset (key=value) |
--incognito | Run in incognito mode (do not report results to Mondoo Platform) |
-o, --output | Set the output format (compact, full, json, junit, summary, yaml) |
-f, --policy-bundle | Path to a policy file (local path, s3:// URI, or http(s):// URL) |
--policy | Specify policies to execute (requires --policy-bundle) |
--risk-threshold | Exit 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
-
For the full MQL resource reference, see the MQL Helm provider documentation.
-
To learn more about how the MQL query language works, read Write Effective MQL.