Reject YAML tags in CI/CD input values

What does this MR do and why?

A YAML tag such as !reference passed as a CI/CD input value (in include: inputs:) is never resolved. Tag resolution runs after input interpolation in Gitlab::Ci::Config#build_config, so the unresolved Tags::Reference object is stringified during interpolation and its internals are dumped into the configuration:

echo '[#<Gitlab::Ci::Config::Yaml::Tags::Reference:0x... @data={...}>, my-specific]'

This MR rejects a YAML tag in an input value with a clear error instead:

`a` input: provided value cannot contain a !reference tag

The check covers a tag as the entire value, a tag nested in an array or hash, and a tag in an input default:. It runs before type coercion, because coercion would otherwise stringify the tag and hide it (this is what let a string input slip through an earlier revision).

Resolves #607053

Why reject instead of support

An earlier iteration implemented resolution, deferring the tag through interpolation so the resolver could resolve it against the merged configuration. It was dropped after review:

  • It added significant complexity to the inputs and interpolation code (deferred value wrappers, tag detection across both layers, key and size guards) for a need that existing input mechanics already cover.
  • Supporting a tag only as the entire input value cannot extend a shared list with additional items, because the tag has to occupy the whole value. Array input concatenation does exactly that with no new code.

Reusing a value across configuration files does not need !reference: an array input defined in an external file already works, and its items are added to a surrounding array so the list can be extended. That behavior is documented in !249486 (merged), which is merged.

Feature flag

  • ci_reject_yaml_tags_in_inputs (gitlab_com_derisk, disabled by default).
  • Rollout issue: #607488
  • The flag exists because a configuration that currently produces the dumped object would start failing, so the change needs a gradual rollout and an instant revert path.

How to set up and validate locally

# list.yml
.my-shared-list:
  - value1
  - value2

# template.yml
spec:
  inputs:
    a:
      type: array
---
job:
  script: $[[ inputs.a ]]

# .gitlab-ci.yml
include:
  - local: /list.yml
  - local: /template.yml
    inputs:
      a: !reference [.my-shared-list]

With the flag enabled, this fails with `a` input: provided value cannot contain a !reference tag. With the flag disabled, the previous behavior is unchanged.

Test coverage

Unit specs:

  • Gitlab::Ci::Config::Yaml::Tags.find_unresolved_tag across all branches, including a tag as a hash key and tags nested in arrays and hashes.
  • Rejection per input type (string, number, boolean, array) at the interpolator level, plus a tag in a hash key, a tag in default: asserting the wording, an input with options: reporting only the tag error, and the flag-disabled path.

End-to-end Gitlab::Ci::Config spec with real local includes:

  • a tag as the entire input value, for an array and a scalar input
  • a tag used in a value that is interpolated into a string
  • a tag nested in an array, and nested in a hash within an array
  • a tag in an input default:
  • flag disabled: previous behavior preserved
  • no tag: normal interpolation unaffected

MR acceptance checklist

  • Feature flag added, disabled by default; both states tested.
  • Unit and end-to-end tests.
  • RuboCop, markdownlint, and Vale passing.
Edited by Oleg Yakovenko

Merge request reports

Loading
Loading