Skip to content

fix: Phase 1 — Security, concurrency & provisioner resilience + E2E tests#623

Merged
ArangoGutierrez merged 1 commit into
NVIDIA:mainfrom
ArangoGutierrez:fix/phase1-security-concurrency
Feb 9, 2026
Merged

fix: Phase 1 — Security, concurrency & provisioner resilience + E2E tests#623
ArangoGutierrez merged 1 commit into
NVIDIA:mainfrom
ArangoGutierrez:fix/phase1-security-concurrency

Conversation

@ArangoGutierrez

Copy link
Copy Markdown
Collaborator

Summary

Phase 1 of the 360-degree codebase evaluation. Addresses security hardening, concurrency improvements, and provisioner resilience.

Security & Concurrency Fixes

  • Template input validation — Sanitize provisioner template inputs to prevent shell injection
  • Logger channel redesign — Redesign logger channels to prevent goroutine leaks and deadlocks
  • Error wrapping — Replace %v with %w across error returns for proper error chain propagation

Provisioner Resilience

  • install_packages_with_retry — Retry on all failures, refresh apt cache, fix broken dpkg state
  • NVIDIA driver — cuda-keyring 1.1-1, architecture detection, 4-stage DKMS fallback chain
  • Container toolkit — Fix duplicate Execute() bug, add retry wrappers
  • Docker/Containerd — Add retry wrappers, default containerd to latest
  • AWS delete — Increase termination waiter to 10min

E2E Test Suite (NEW)

  • Ginkgo/Gomega E2E tests: containerd + kubeadm (K8s v1.35.0) and docker + NVIDIA runtime
  • Full AWS lifecycle testing on g4dn.xlarge

Test Plan

  • go vet ./... passes
  • Unit tests pass
  • E2E containerd+kubeadm+K8s v1.35.0: PASS
  • E2E docker+NVIDIA runtime: PASS
  • Commits signed with -s -S (DCO + SSH)

Made with Cursor

Copilot AI review requested due to automatic review settings February 7, 2026 16:50

Copilot AI 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.

Pull request overview

Phase 1 hardening pass focused on safer template execution, more resilient provisioning behavior, improved logger concurrency behavior, and adding a new E2E test suite for AWS-backed environments.

Changes:

  • Added Ginkgo/Gomega E2E coverage for AWS lifecycle + provisioning (containerd/kubeadm and docker/NVIDIA runtime scenarios).
  • Added template input validation helpers + tests to reduce shell injection risk; improved retry behavior in provisioning templates.
  • Improved error wrapping (%w) broadly; updated provisioner streaming to avoid goroutine leaks / fatal exits.

Reviewed changes

Copilot reviewed 24 out of 639 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
tests/e2e_test.go Adds Ginkgo suite bootstrap + shared env initialization for E2E.
tests/aws_test.go Implements AWS E2E table tests that create, provision, and clean up environments.
tests/common/common.go Adds helper for per-test unique IDs to avoid name collisions.
tests/data/test_aws.yml Adds E2E AWS env spec for containerd + kubeadm scenario.
tests/data/test_aws_docker.yml Adds E2E AWS env spec for docker + NVIDIA toolkit scenario.
pkg/provisioner/templates/validate.go Introduces input validators (versions/arch/host/etc.) for safer template interpolation.
pkg/provisioner/templates/validate_test.go Adds unit tests to validate injection-resistant behavior.
pkg/provisioner/templates/kubernetes.go Validates Kubernetes template inputs and wraps template execution errors.
pkg/provisioner/templates/docker.go Adds retry for apt update; validates Docker template inputs; wraps errors.
pkg/provisioner/templates/crio.go Adds version validation and wraps template execution errors.
pkg/provisioner/templates/containerd.go Improves apt retry usage; supports “latest” containerd behavior; minor template hardening.
pkg/provisioner/templates/container-toolkit.go Fixes template execution flow; adds runtime validation; adds retry-install behavior.
pkg/provisioner/templates/common.go Makes package install more resilient (retries, dpkg repair, apt refresh).
pkg/provisioner/templates/nv-driver.go Improves NVIDIA driver install resilience with arch detection + fallback chain.
pkg/provisioner/provisioner.go Improves SSH session output streaming and error wrapping; avoids fatal exit in goroutine.
internal/logger/logger.go Adds context-aware loading indicator and prevents double-close panic in Exit.
internal/logger/logger_test.go Adds unit tests for logger edge cases + goroutine exit behaviors.
pkg/provider/aws/create.go Switches many provider errors to %w wrapping for better error chains.
pkg/provider/aws/dryrun.go Wraps dry-run errors with %w.
pkg/provider/aws/delete.go Increases termination waiter timeout.
cmd/cli/create/create.go Wraps returned errors with %w for better higher-level error inspection.
cmd/cli/dryrun/dryrun.go Wraps SSH key read/parse errors with %w.
cmd/action/ci/entrypoint.go Wraps errors with %w for CI action entrypoint reliability.
go.mod Updates Go version and adds Ginkgo/Gomega dependencies for E2E.
Comments suppressed due to low confidence (1)

pkg/provisioner/templates/common.go:30

  • Setting APT::Get::AllowUnauthenticated 1 globally weakens package authenticity checks for all subsequent installs, which is a significant security regression (especially in a PR focused on hardening). Prefer removing this entirely and fixing the underlying key/repo setup; if it must exist for a narrow case, scope it to a single command and immediately undo it afterwards.
echo "APT::Get::AllowUnauthenticated 1;" | sudo tee /etc/apt/apt.conf.d/99allow-unauthenticated

Comment thread tests/e2e_test.go
Comment on lines +36 to +41
func TestMain(t *testing.T) {
suiteName := "E2E Holodeck"

RegisterFailHandler(Fail)
RunSpecs(t, suiteName)
}

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

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

TestMain is a special name in Go testing when the signature is func TestMain(m *testing.M). Here it's a normal test function, which can be confusing and makes it harder to add a real TestMain later. Consider renaming this to something like TestE2E / TestE2ESuite.

Copilot uses AI. Check for mistakes.
Comment thread tests/aws_test.go
Comment on lines +115 to +126
defer func() {
if p.Client != nil {
session, err := p.Client.NewSession()
if err == nil {
session.Close()
if err := p.Client.Close(); err != nil {
Expect(err).NotTo(HaveOccurred(), "Failed to close ssh client")
}
}
p.Client = nil
}
}()

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

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

If p.Client.NewSession() fails, the SSH client is never closed, leaking the underlying connection. Also, creating a session just to close the client is unnecessary—closing the client should be unconditional when non-nil.

Copilot uses AI. Check for mistakes.
Comment on lines 214 to 222
localFile, err := os.Open(env.Spec.Kubernetes.KindConfig)
if err != nil {
return fmt.Errorf("failed to open local file %s: %v", env.Spec.Kubernetes.KindConfig, err)
return fmt.Errorf("failed to open local file %s: %w", env.Spec.Kubernetes.KindConfig, err)
}

// copy local file to remote file
if _, err := io.Copy(remoteFile, localFile); err != nil {
return fmt.Errorf("failed to copy local file %s to remote file %s: %v", env.Spec.Kubernetes.KindConfig, remoteFilePath, err)
return fmt.Errorf("failed to copy local file %s to remote file %s: %w", env.Spec.Kubernetes.KindConfig, remoteFilePath, err)
}

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

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

localFile is never closed, which can leak file descriptors. Add a defer localFile.Close() after the successful os.Open(...).

Copilot uses AI. Check for mistakes.
Comment thread pkg/provisioner/templates/validate.go Outdated
containerRuntimePattern = regexp.MustCompile(`^(docker|containerd|crio|cri-o)$`)

// filePathPattern matches safe file paths (no shell metacharacters)
filePathPattern = regexp.MustCompile(`^[a-zA-Z0-9_./$~-]+$`)

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

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

The comment says "no shell metacharacters", but the pattern allows $, which is a shell metacharacter and enables variable expansion. Either (a) remove $ from the allowed set, or (b) ensure every use of the path in templates is safely single-quoted/escaped and update the comment to match the intended threat model.

Suggested change
filePathPattern = regexp.MustCompile(`^[a-zA-Z0-9_./$~-]+$`)
filePathPattern = regexp.MustCompile(`^[a-zA-Z0-9_./~-]+$`)

Copilot uses AI. Check for mistakes.
Comment thread internal/logger/logger.go Outdated
Comment on lines +225 to +245
ticker := time.After(330 * time.Millisecond)
i := 0
spinners := []string{"|", "/", "-", "\\"}

for {
select {
case <-ctx.Done():
fmt.Print("\r\033[2K")
printMessage(green, checkmark, message)
return
case <-failCh:
fmt.Print("\r\033[2K")
printMessage(redText, redXEmoji, message)
return
case <-ticker:
i++
fmt.Printf("\r%s\t%s", spinners[i], message)
if i >= len(spinners)-1 {
i = 0
}
ticker = time.After(330 * time.Millisecond)

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

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

This re-allocates a new time.After(...) timer on every tick, which is unnecessary overhead and can increase GC pressure during long-running operations. Use a time.NewTicker(...) and defer ticker.Stop(). Also consider writing to l.Out (or routing through a writer-aware helper) instead of fmt.Print/Printf to keep logger output consistent/testable.

Suggested change
ticker := time.After(330 * time.Millisecond)
i := 0
spinners := []string{"|", "/", "-", "\\"}
for {
select {
case <-ctx.Done():
fmt.Print("\r\033[2K")
printMessage(green, checkmark, message)
return
case <-failCh:
fmt.Print("\r\033[2K")
printMessage(redText, redXEmoji, message)
return
case <-ticker:
i++
fmt.Printf("\r%s\t%s", spinners[i], message)
if i >= len(spinners)-1 {
i = 0
}
ticker = time.After(330 * time.Millisecond)
ticker := time.NewTicker(330 * time.Millisecond)
defer ticker.Stop()
i := 0
spinners := []string{"|", "/", "-", "\\"}
for {
select {
case <-ctx.Done():
fmt.Fprint(outFile, "\r\033[2K")
printMessage(green, checkmark, message)
return
case <-failCh:
fmt.Fprint(outFile, "\r\033[2K")
printMessage(redText, redXEmoji, message)
return
case <-ticker.C:
i++
fmt.Fprintf(outFile, "\r%s\t%s", spinners[i], message)
if i >= len(spinners)-1 {
i = 0
}

Copilot uses AI. Check for mistakes.
Comment thread internal/logger/logger.go Outdated
Comment on lines +240 to +244
i++
fmt.Printf("\r%s\t%s", spinners[i], message)
if i >= len(spinners)-1 {
i = 0
}

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

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

Spinner indexing skips spinners[0] on the first tick because i is incremented before indexing. Adjust the increment/mod logic so the sequence starts at index 0 and never risks out-of-range if this logic changes later.

Suggested change
i++
fmt.Printf("\r%s\t%s", spinners[i], message)
if i >= len(spinners)-1 {
i = 0
}
fmt.Printf("\r%s\t%s", spinners[i%len(spinners)], message)
i++

Copilot uses AI. Check for mistakes.
Comment thread pkg/provider/aws/delete.go Outdated
Comment on lines 68 to 71
}, 10*time.Minute, waiterOptions...); err != nil {
a.fail()
return fmt.Errorf("error waiting for instance to be terminated: %v", err)
}

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

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

This return path still uses %v instead of %w, which breaks error unwrapping and is inconsistent with the rest of the PR’s error-wrapping changes. Prefer %w here.

Copilot uses AI. Check for mistakes.
Comment thread tests/common/common.go
Comment on lines +22 to +31
"log"
)

func GenerateUID() string {
b := make([]byte, 4)
_, err := rand.Read(b)
if err != nil {
log.Fatalf("failed to generate secure random UID: %v", err)
}
return hex.EncodeToString(b)

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

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

A 4-byte random value has a non-trivial collision risk across many E2E runs (especially in parallel). Consider increasing to 16 bytes (or at least 8) and returning (string, error) instead of calling log.Fatalf, so the caller/test can decide how to fail.

Suggested change
"log"
)
func GenerateUID() string {
b := make([]byte, 4)
_, err := rand.Read(b)
if err != nil {
log.Fatalf("failed to generate secure random UID: %v", err)
}
return hex.EncodeToString(b)
"fmt"
)
func GenerateUID() (string, error) {
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
return "", fmt.Errorf("failed to generate secure random UID: %w", err)
}
return hex.EncodeToString(b), nil

Copilot uses AI. Check for mistakes.
Comment thread tests/data/test_aws.yml
privateKey: /home/runner/.cache/key
instance:
type: g4dn.xlarge
region: us-west-1

Copilot AI Feb 7, 2026

Copy link

Choose a reason for hiding this comment

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

Using 0.0.0.0/0 for ingress in E2E test configs exposes SSH (and any opened ports) to the internet during tests. Prefer restricting ingress to the runner’s public IP/CIDR (for example, injected via CI), or at least documenting that this file should only be used in tightly scoped test accounts with short-lived credentials.

Suggested change
region: us-west-1
region: us-west-1
# NOTE: 0.0.0.0/0 is used here only for E2E testing.
# This configuration must only be used in tightly scoped test accounts
# with short-lived, non-production credentials and isolated resources.
# For CI, prefer restricting ingress to the runner's public IP/CIDR
# injected via the pipeline instead of exposing ports to the internet.

Copilot uses AI. Check for mistakes.
@ArangoGutierrez
ArangoGutierrez force-pushed the fix/phase1-security-concurrency branch from 6c72936 to 8a7fefa Compare February 9, 2026 13:28
@ArangoGutierrez
ArangoGutierrez enabled auto-merge (squash) February 9, 2026 13:29
@coveralls

coveralls commented Feb 9, 2026

Copy link
Copy Markdown

Pull Request Test Coverage Report for Build 21830773231

Details

  • 42 of 85 (49.41%) changed or added relevant lines in 5 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.2%) to 46.532%

Changes Missing Coverage Covered Lines Changed/Added Lines %
pkg/provider/aws/cluster.go 0 2 0.0%
pkg/provider/aws/delete.go 0 2 0.0%
pkg/provisioner/provisioner.go 0 4 0.0%
pkg/provider/aws/nlb.go 0 9 0.0%
pkg/provisioner/templates/validate.go 42 68 61.76%
Totals Coverage Status
Change from base Build 21830579866: 0.2%
Covered Lines: 2355
Relevant Lines: 5061

💛 - Coveralls

@ArangoGutierrez
ArangoGutierrez force-pushed the fix/phase1-security-concurrency branch from 8a7fefa to 045ad1d Compare February 9, 2026 13:39
…ping

- Add template input validation to prevent command injection via shell
  metacharacters in version strings, git URLs, and user-supplied fields
- Replace fmt.Errorf %v with %w across non-vendor packages for proper
  error chain propagation enabling errors.Is/errors.As usage

Re-implemented against current upstream/main.

Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@ArangoGutierrez
ArangoGutierrez force-pushed the fix/phase1-security-concurrency branch from 045ad1d to fba9ce3 Compare February 9, 2026 15:17
@ArangoGutierrez
ArangoGutierrez merged commit a46900d into NVIDIA:main Feb 9, 2026
19 checks passed
ArangoGutierrez added a commit to ArangoGutierrez/holodeck that referenced this pull request Feb 10, 2026
…ping (NVIDIA#623)

- Add template input validation to prevent command injection via shell
  metacharacters in version strings, git URLs, and user-supplied fields
- Replace fmt.Errorf %v with %w across non-vendor packages for proper
  error chain propagation enabling errors.Is/errors.As usage

Re-implemented against current upstream/main.

Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
ArangoGutierrez added a commit to ArangoGutierrez/holodeck that referenced this pull request Feb 10, 2026
…ping (NVIDIA#623)

- Add template input validation to prevent command injection via shell
  metacharacters in version strings, git URLs, and user-supplied fields
- Replace fmt.Errorf %v with %w across non-vendor packages for proper
  error chain propagation enabling errors.Is/errors.As usage

Re-implemented against current upstream/main.

Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
ArangoGutierrez added a commit to ArangoGutierrez/holodeck that referenced this pull request Feb 10, 2026
…ping (NVIDIA#623)

- Add template input validation to prevent command injection via shell
  metacharacters in version strings, git URLs, and user-supplied fields
- Replace fmt.Errorf %v with %w across non-vendor packages for proper
  error chain propagation enabling errors.Is/errors.As usage

Re-implemented against current upstream/main.

Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
ArangoGutierrez added a commit to ArangoGutierrez/holodeck that referenced this pull request Feb 12, 2026
…ping (NVIDIA#623)

- Add template input validation to prevent command injection via shell
  metacharacters in version strings, git URLs, and user-supplied fields
- Replace fmt.Errorf %v with %w across non-vendor packages for proper
  error chain propagation enabling errors.Is/errors.As usage

Re-implemented against current upstream/main.

Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
ArangoGutierrez added a commit to ArangoGutierrez/holodeck that referenced this pull request Feb 13, 2026
…ping (NVIDIA#623)

- Add template input validation to prevent command injection via shell
  metacharacters in version strings, git URLs, and user-supplied fields
- Replace fmt.Errorf %v with %w across non-vendor packages for proper
  error chain propagation enabling errors.Is/errors.As usage

Re-implemented against current upstream/main.

Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
ArangoGutierrez added a commit to ArangoGutierrez/holodeck that referenced this pull request Feb 13, 2026
…ping (NVIDIA#623)

- Add template input validation to prevent command injection via shell
  metacharacters in version strings, git URLs, and user-supplied fields
- Replace fmt.Errorf %v with %w across non-vendor packages for proper
  error chain propagation enabling errors.Is/errors.As usage

Re-implemented against current upstream/main.

Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Co-authored-by: Cursor <cursoragent@cursor.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.

3 participants