Skip to content

Nathan.gallet/helm deterministic rendering#214

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 9 commits into
mainfrom
nathan.gallet/helm-deterministic-rendering
Jun 29, 2026
Merged

Nathan.gallet/helm deterministic rendering#214
gh-worker-dd-mergequeue-cf854d[bot] merged 9 commits into
mainfrom
nathan.gallet/helm-deterministic-rendering

Conversation

@NathanGallet-dd

@NathanGallet-dd NathanGallet-dd commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Helm charts using non-deterministic Sprig functions (randAlphaNum, uuidv4, now, etc.) in resource names produce a different rendered name on every scan, generating a new DATADOG_FINGERPRINT each time and re-reporting the same finding as new indefinitely.

Root cause: engine.Engine (used internally by action.Install.Run) pulls its funcmap from sprig.TxtFuncMap() with no injection point — funcMap() and initFunMap() are unexported, and Engine has no FuncMap field.

Fix: Pre-process each chart template's raw bytes before rendering, replacing non-deterministic function calls with stable, line-number-seeded stubs:

Function Stub format
randAlphaNum N, randAlpha N, randAscii N "ddscan{lineNum:04d}"
randNumeric N "{lineNum:08d}"
uuidv4 "00000000-0000-0000-{lineNum:04d}-{lineNum:012d}"
now (toDate "2006-01-02" "2000-01-01") — returns time.Time, works in any pipeline

Stubs are seeded by line number so two resources in the same file using the same function produce distinct, stable names across every scan. Replacements are applied back-to-front by byte offset so earlier match positions stay valid during mutation. The now guard skips matches preceded by $ or . to avoid corrupting $now variable declarations and .Values.now field accesses.

Crypto functions (genSignedCert, genCA, etc.) are deferred — they return structs, not strings, and rarely appear in resource names.

Changes

  • pkg/resolver/helm/helm.go — adds deterministicPattern struct, deterministicPatterns table, applyDeterministicSubstitutions, and makeDeterministic (mirrors setID pattern, recurses into sub-charts). makeDeterministic is called before setID in runInstall.
  • pkg/resolver/helm/helm_deterministic_test.go — unit tests for each function type + two-lines distinctness + no-match passthrough; integration test calls Resolve twice and asserts byte-identical output.
  • test/fixtures/test_helm_nondeterministic/ — minimal fixture chart exercising all covered function types.

Testing

go test ./pkg/resolver/helm/... -v -run "TestApplyDeterministic|TestHelm_"

All existing TestHelm_Resolve cases remain green. TestHelm_DeterministicRendering calls Resolve twice on the new fixture and asserts require.Equal(first.File, second.File).

NathanGallet-dd and others added 6 commits June 26, 2026 12:52
Add test case for randAscii substitution in TestApplyDeterministicSubstitutions.
Clean up dead code in makeDeterministic by removing unnecessary reassignments
and nil-checks that depend on pointer side-effects.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…ld access

The `\bnow\b` pattern used to match the Sprig `now` function was too broad.
Word boundaries fire between non-word characters ($, .) and word characters (n),
causing false positives on `$now` variables and `.Values.now` field accesses.

After a match is found, check if it is preceded by $ or .; if so, skip it.
This ensures only actual function calls to `now` are substituted.

Add test cases covering both false-positive cases:
- `$now` variable reference should remain unchanged
- `.Values.now` field access should remain unchanged

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Moved the guard logic for the 'now' pattern from a hardcoded index check
into the deterministicPattern struct itself. This eliminates the
maintainability issue where reordering patterns could silently change
which pattern gets guarded.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@NathanGallet-dd
NathanGallet-dd requested a review from a team as a code owner June 26, 2026 11:21
@datadog-official

datadog-official Bot commented Jun 26, 2026

Copy link
Copy Markdown

🎯 Code Coverage (details)
Patch Coverage: 92.59%
Overall Coverage: 49.56% (+0.22%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 213e607 | Docs | Datadog PR Page | Give us feedback!

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0700f51dd2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread pkg/resolver/helm/helm.go
var replacements []replacement

for _, p := range deterministicPatterns {
matches := p.re.FindAllStringIndex(s, -1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict deterministic substitutions to template actions

When a chart template contains literal text that happens to mention one of these tokens, this scans the entire file and rewrites it even though it is not a Sprig call. For example, a ConfigMap script/comment containing randAlphaNum 8, or a template string such as include "uuidv4", is corrupted before Helm renders it (the latter can even produce an invalid template with nested quotes), so the scanner may analyze a manifest that the chart would never render or fail to render the chart at all. The replacement logic should be limited to actual template actions and avoid quoted strings/comments.

Useful? React with 👍 / 👎.

NathanGallet-dd and others added 2 commits June 26, 2026 13:40
…string args

Substitutions are now scoped to {{ ... }} action blocks via templateActionSpans/inAnySpan,
preventing rewrites of literal text (e.g. shell scripts in ConfigMap data). A shared guard
notPrecededByQuoteVarOrField is applied to all patterns so string arguments like
`include "uuidv4"` are not treated as function calls.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Replace the single-byte quote guard with insideQuotedStringInSpan, which
counts unescaped double-quotes from the opening {{ to the match position.
An odd count means the match is inside a string literal and should be skipped.

This prevents patterns like {{ required "set this now" .Values.foo }} and
{{ printf "prefix randAlphaNum 8" }} from being corrupted by substitution.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

@whitemerch whitemerch left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job!

Comment thread pkg/resolver/helm/helm.go
Comment thread pkg/resolver/helm/helm.go

// deterministicPatterns maps each non-deterministic sprig function regex to a replacement generator.
// The generator receives the line number of the match.
var deterministicPatterns = []deterministicPattern{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think a chart using {{ randAlphaNum .Values.len }} will be matched here

Comment thread pkg/resolver/helm/helm.go
…tic substitution

- Exclude {{/* ... */}} comment blocks from template action spans so function
  names mentioned in comments are never rewritten
- Extend argument pattern from \d+ to [\w$.]+ so calls like
  randAlphaNum .Values.suffixLen and randAlphaNum $len are replaced
- Add randBytes pattern (was missing entirely)

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot merged commit 37ad46b into main Jun 29, 2026
20 checks passed
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot deleted the nathan.gallet/helm-deterministic-rendering branch June 29, 2026 08:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants