Skip to content

[dmt] add yaml injection linter - #452

Open
ldmonster wants to merge 10 commits into
mainfrom
feat/add-yaml-injection-linter
Open

[dmt] add yaml injection linter#452
ldmonster wants to merge 10 commits into
mainfrom
feat/add-yaml-injection-linter

Conversation

@ldmonster

@ldmonster ldmonster commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new rule to the templates linter that guards against unquoted, unvalidated OpenAPI string values in Helm templates — a common source of broken manifests and silent type coercion (YAML injection).

A module's openapi/ schema declares the shape of .Values.<module>.*. When a type: string field has no validation keyword (pattern, enum, or format), its value is unconstrained: rendered into YAML unquoted, 123456 becomes an integer, true/no a boolean, a leading 0 is dropped, and a value containing : / # / a newline can make the document fail to parse. The rule finds every such field in the schema and reports each place a template renders it without quoting.

Default level: error (configurable per rule, with per-path exclusions).

What it detects

The rule reads openapi/values.yaml + openapi/config-values.yaml, collects the pattern/enum/format-less string paths, and scans templates/ for unquoted usages — both .Values.<module>.<path> and the root-scoped $.Values.... form. It understands a wide range of template shapes:

  • direct scalars, nullable strings (type: ["string","null"]), and strings reached through $ref / allOf / oneOf / anyOf;
  • string arrays ({{ range … }}{{ . }}), string maps (additionalProperties, {{ range $k,$v }}{{ $v }}), and arrays of arrays (range-in-range);
  • array-of-object string sub-fields at any depth ({{ range $s := …servers }}{{ $s.host }}, incl. $s.spec.name and servers[].endpoints[].url), and their string-array sub-fields via nested range;
  • with over a scalar, a single object, or an object variable ({{ with $s }}{{ .host }});
  • values copied into variables, incl. aliases and an array bound to a variable then ranged ({{ $x := …list }}{{ range $x }});
  • risky values passed to a passthrough function — string transforms (printf, upper, …) or array/map element accessors (index) — for both .Values references and variables;
  • cross-template flow: a value passed to a module-defined template that renders it unquoted ({{ include "mymod.env" .Values.mod.config }}), traced transitively across the module's own define/include chain.

Findings distinguish a standalone value (fix: add | quote) from a value embedded in a larger scalar (fix: wrap the whole value in quotes), and the message says which fix applies.

What is considered safe (not reported)

  • the field declares pattern, enum, or format (note: minLength/maxLength do not exempt — they don't restrict the character set);
  • the usage wraps the value in "…"/'…', or pipes it through a YAML-safe function (quote, squote, toJson/toYaml, b64enc, sha256sum, … and must… variants), or printf with a %q verb.

Designed to avoid false positives: external templates (helm_lib) are not inspected, values used only in conditions ({{ if … }}) and inside YAML block scalars (|/>) are skipped, and only a curated set of functions is treated as passthrough. See the rule documentation for the full list and known limitations.

Configuration

# .dmtlint.yaml
linters-settings:
  templates:
    rules:
      openapi-values-quote:
        impact: error
    exclude-rules:
      openapi-values-quote:
        - internal.someLegacyField   # a scalar path
        - extraArgs                  # a string array
        - servers[].host             # one sub-field of an array of objects

Exclusions match the value path exactly as it appears in the finding message ([] marks array elements and array-of-object sub-fields).

Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
@ldmonster ldmonster self-assigned this Aug 19, 2026
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant