Skip to content

fix(bundler): quote recipe-supplied name and namespace in install.sh - #2853

Open
lockwobr wants to merge 2 commits into
mainfrom
fix/localformat-shell-quote-name-namespace
Open

lockwobr wants to merge 2 commits into
mainfrom
fix/localformat-shell-quote-name-namespace

Conversation

@lockwobr

Copy link
Copy Markdown
Contributor

Summary

Quote {{ .Name }} and {{ .Namespace }} where the localformat install templates interpolate them into the helm upgrade command line, using the shellSingleQuote helper the sibling apply-crds.sh template already uses.

Motivation / Context

install-upstream-helm.sh.tmpl and install-local-helm.sh.tmpl built their helm upgrade invocation with both values bare:

helm upgrade --install ${FORCE_CONFLICTS_FLAG} {{ .Name }} "${CHART_REF}" \
  --namespace {{ .Namespace }} --create-namespace \

Neither value is constrained to shell-safe characters on the way in. Component names are validated only as path components, and deployer.IsSafePathComponent rejects separators rather than shell metacharacters; the namespace is not validated on this path at all. The result is a generated script that an operator then runs, so a malformed or hostile recipe turns into command execution on the operator's workstation with their cluster credentials loaded.

This is defense in depth rather than a remotely reachable exploit: the values come from recipes/registry.yaml and recipe overlays, both of which are reviewed. But relying on a distant validator that was written for a different purpose, and that does not actually cover this character class, is the fragile part worth removing.

Fixes: #2844
Related: N/A

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • Build/CI/tooling

Component(s) Affected

  • CLI (cmd/aicr, pkg/cli)
  • API server (cmd/aicrd, pkg/server)
  • Recipe engine / data (pkg/recipe)
  • Bundlers (pkg/bundler, pkg/component/*)
  • Collectors / snapshotter (pkg/collector, pkg/snapshotter)
  • Validator (pkg/validator)
  • Core libraries (pkg/errors, pkg/k8s)
  • Docs/examples (docs/, examples/)
  • Other: ____________

Implementation Notes

No new escaping mechanism. shellSingleQuote already existed in upstream_helm.go and was already registered as shq on the apply-crds.sh template; these two templates simply never adopted it. Single quotes rather than double are what matters here, because $(), backticks, and $VAR all still expand inside double quotes.

The template.FuncMap{"shq": shellSingleQuote} literal would have appeared three times, which goconst flags, so it is hoisted into one shellFuncs var carrying the rationale in its doc comment.

16 golden install.sh fixtures and stock_render_golden.yaml are regenerated; those are the only other changes.

Testing

go test -race ./pkg/bundler/deployer/...
go test ./pkg/bundler/ -run TestStockRenderParityGolden
golangci-lint run -c .golangci.yaml ./pkg/bundler/deployer/...

All pass; linter reports 0 issues.

The regression is execution-based, and mutation-verified. A strings.Contains assertion on the rendered script would prove nothing about quoting, since it passes whether or not the value is safe to hand to bash. Instead each test renders a bundle whose name and namespace embed both a space and a $(touch <canary>) substitution, runs the script under a stub helm that records its argv, and asserts the hostile value arrived as a single argv element and that no canary file exists.

Reverting just the | shq filters and re-running shows the payload genuinely executing, not merely word-splitting:

--- FAIL: TestWrite_UpstreamHelmInstallScript_RejectsInjectedRecipeValues
    helm upgrade did not receive "nfd evil$(touch pwned-upstream-name)" as a single argv element.
    injected command executed: .../001-nfd evil$(touch pwned-upstream-name)/pwned-upstream-name exists
--- FAIL: TestWrite_LocalHelmInstallScript_RejectsInjectedRecipeValues
    helm upgrade did not receive "skyhook evil$(touch pwned-local-name)" as a single argv element.

Coverage: pkg/bundler/deployer/localformat 82.2%. No exported API added.

Risk Assessment

  • Low — Isolated change, well-tested, easy to revert
  • Medium — Touches multiple components or has broader impact
  • High — Breaking change, affects critical paths, or complex rollout

Two template lines plus a FuncMap hoist. Every currently-shipped component name and namespace is a plain DNS-1123 label, for which single-quoting is a no-op in effect, so the rendered behavior is unchanged for real recipes. The regenerated goldens show exactly that: quotes appear, nothing else moves.

Rollout notes: N/A.

Checklist

  • Tests pass locally (make test with -race)
  • Linter passes (make lint)
  • I did not skip/disable tests to make CI green
  • I added/updated tests for new functionality
  • I updated docs if user-facing behavior changed
  • Changes follow existing patterns in the codebase
  • Commits are cryptographically signed (git commit -S)

@lockwobr lockwobr added the theme/deployer Helm, ArgoCD, and deployment bundle generation label Sep 19, 2026
@lockwobr lockwobr self-assigned this Sep 19, 2026
@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: NVIDIA/aicr/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 9af686ac-f35a-4cb5-8b10-6c862291eb79

📥 Commits

Reviewing files that changed from the base of the PR and between 60502d3 and 3d94b35.

📒 Files selected for processing (1)
  • pkg/bundler/testdata/stock_render_golden.yaml

Included review availability: Your plan provides up to 12 included reviews per hour; 11 remain after this review.


📝 Walkthrough

Walkthrough

The change applies the shared shq template function to Helm release names and namespaces in local and upstream installer templates. Generated installer fixtures now contain quoted arguments. Writer tests execute generated scripts with hostile values, verify exact Helm arguments, and confirm command substitutions are not executed. Existing Helm options and --create-namespace behavior remain unchanged.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: ayuskauskas

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: shell-quoting recipe-supplied names and namespaces in install.sh.
Description check ✅ Passed The description directly explains the shell-injection risk, implementation, affected templates, regression tests, and scope. It is fully related to the changeset.
Linked Issues check ✅ Passed Issue #2844 requires shell quoting for .Name and .Namespace in both localformat Helm install templates. Both templates now use the existing shq helper. The shared shellFuncs map registers the …
Out of Scope Changes check ✅ Passed The changes remain within Issue #2844. The shared function map, parser update, golden fixtures, and execution tests support the quoting fix. The changes do not modify the already-safe CRD behavior, YA…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

@lockwobr
lockwobr force-pushed the fix/localformat-shell-quote-name-namespace branch from 566bf81 to ffa53bc Compare September 19, 2026 02:16
@lockwobr
lockwobr marked this pull request as ready for review September 19, 2026 02:33
@lockwobr
lockwobr requested a review from a team as a code owner September 19, 2026 02:33
@lockwobr
lockwobr force-pushed the fix/localformat-shell-quote-name-namespace branch from ffa53bc to fea8500 Compare September 19, 2026 02:44
@github-actions

Copy link
Copy Markdown
Contributor

Recipe evidence check

No leaf overlays affected by this PR.

This gate is warning-only and never blocks merge.

@github-actions

github-actions Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Coverage Report ✅

Metric Value
Coverage 84.9%
Threshold 83%
Status Pass
Coverage Badge
![Coverage](https://img.shields.io/badge/coverage-84.9%25-brightgreen)

Coverage unchanged by this PR.

@lockwobr
lockwobr force-pushed the fix/localformat-shell-quote-name-namespace branch 2 times, most recently from 60502d3 to 3d94b35 Compare September 20, 2026 00:53
install-upstream-helm.sh.tmpl and install-local-helm.sh.tmpl interpolated
{{ .Name }} and {{ .Namespace }} bare into the helm upgrade command line.
Component names are validated only as path components, which rejects
separators but not shell metacharacters, and the namespace is not
validated on this path at all, so a recipe could put arbitrary shell into
a script the operator then runs.

Apply the shellSingleQuote helper the sibling apply-crds.sh template
already uses, hoisted into a shared shellFuncs map.

Regression runs the rendered script with a payload embedding both a space
and a command substitution, and asserts helm received each value as one
argv element and that no canary file was created. Reverting the quoting
makes the payload execute.

Signed-off-by: Brian Lockwood <lockwobr@gmail.com>
@lockwobr
lockwobr force-pushed the fix/localformat-shell-quote-name-namespace branch from 3d94b35 to 43be8f0 Compare September 20, 2026 04:39

@mchmarny mchmarny left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approve: no findings against 43be8f0. Focused quoting tests pass; the failed NVSentinel E2E is an unrelated monitor query/timing race at the reviewed SHA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/bundler size/XL theme/deployer Helm, ArgoCD, and deployment bundle generation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(bundler): unquoted name/namespace in localformat install.sh templates

2 participants