Skip to content

[docker] Helm values.yaml updater changes unrelated image tag when multiple tags match REGEX #16304

Description

@harrider

Is there an existing issue for this?

  • I have searched the existing issues

Package ecosystem

Docker — specifically Helm values files discovered through package-ecosystem: docker.

Package manager version

N/A — Docker image updates do not invoke a native package manager.

Language version

N/A for the Docker ecosystem. Ruby 4.0.0 was used to execute the focused source-level reproduction below.

Manifest location and content before the Dependabot update

Path: /values.yaml

api:
  image:
    repository: registry.example.com/example/api
    tag: 1.2.0

worker:
  image:
    repository: registry.example.com/example/worker
    tag: 1.2.0

The two independent image repositories intentionally use the same current tag. This is the relevant shape consumed by the Docker ecosystem's Helm values parser: each parent contains an image mapping with separate repository and tag values.

dependabot.yml content

Representative configuration:

version: 2
updates:
  - package-ecosystem: docker
    directory: "/"
    schedule:
      interval: weekly

The defect is in file generation after dependency selection; it does not depend on a special allow, ignore, or grouping rule.

Updated dependency

For this reproduction, only the API image is selected for an update:

registry.example.com/example/api:1.2.0
-> registry.example.com/example/api:1.3.0

The worker image is a different dependency and is not selected for an update.

What you expected to see, versus what you actually saw

Expected:

api:
  image:
    repository: registry.example.com/example/api
    tag: 1.3.0

worker:
  image:
    repository: registry.example.com/example/worker
    tag: 1.2.0

Actual:

api:
  image:
    repository: registry.example.com/example/api
    tag: 1.3.0

worker:
  image:
    repository: registry.example.com/example/worker
    tag: 1.3.0

The unrelated worker image is changed because its old tag happens to equal the old tag of the dependency being updated.

The Docker ecosystem Helm updater constructs a regular expression using only the old tag and then applies gsub to the complete file:

def update_helm(file, content)
old_tags = old_helm_tags(file)
return if old_tags.empty?
modified_content = content
replacement = new_helm_tag(file)
old_tags.each do |old_tag|
old_tag_regex = /^\s*(?:-\s)?(?:tag|version):\s+["']?#{Regexp.escape(old_tag)}["']?(?=\s|$)/
modified_content = modified_content&.gsub(old_tag_regex) do |old_img_tag|
old_img_tag.gsub(old_tag.to_s, replacement.to_s)
end
end
modified_content

The expression is not associated with the image repository or the YAML path that produced the dependency. Consequently, every matching tag: or version: line in the file is replaced. Escaping the old tag prevents regex metacharacters from changing the match semantics, but does not scope the match to an image repository.

This can silently change an unrelated deployment image in a Dependabot-generated pull request.

Native package manager behavior

Not applicable. This behavior occurs while Dependabot generates the updated Helm values content.

Images of the diff or a link to the PR, issue, or logs

None. This report uses only the synthetic reproduction.

Smallest manifest that reproduces the issue

The following focused reproduction executes the matching and replacement logic from the linked update_helm implementation:

puts "Ruby #{RUBY_VERSION}"

values_yaml = [
"api:",
"  image:",
"    repository: registry.example.com/example/api",
"    tag: 1.2.0",
"worker:",
"  image:",
"    repository: registry.example.com/example/worker",
"    tag: 1.2.0"
].join("\n") + "\n"

old_tag = "1.2.0"
new_tag = "1.3.0"

old_tag_regex = /^\s*(?:-\s)?(?:tag|version):\s+["']?#{Regexp.escape(old_tag)}["']?(?=\s|$)/

actual = values_yaml.gsub(old_tag_regex) do |matched_line|
matched_line.gsub(old_tag, new_tag)
end

puts actual.inspect
updated_count = actual.lines.count { |line| line.include?(new_tag) }
puts "updated tag count: #{updated_count}"

raise "reproduction failed" unless updated_count == 2

Executed with Ruby 4.0.0, this produces:

Ruby 4.0.0
"api:\n  image:\n    repository: registry.example.com/example/api\n    tag: 1.3.0\nworker:\n  image:\n    repository: registry.example.com/example/worker\n    tag: 1.3.0\n"
updated tag count: 2

This is a focused source-level reproduction of the updater transformation rather than an end-to-end GitHub-native Dependabot run.

A regression test could use two different repositories with an identical old tag and assert that only the image represented by the selected dependency changes. The replacement likely needs to be scoped to the corresponding repository/YAML mapping or parser-recorded image path, rather than matching tag values across the entire file.

Activity

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

Metadata

Metadata

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions