Skip to content

fix: make _dnsify honor the 63-character DNS_LABEL contract - #3466

Open
Daksha1611 wants to merge 1 commit into
flyteorg:masterfrom
Daksha1611:fix-dnsify-dns-label-contract
Open

fix: make _dnsify honor the 63-character DNS_LABEL contract#3466
Daksha1611 wants to merge 1 commit into
flyteorg:masterfrom
Daksha1611:fix-dnsify-dns-label-contract

Conversation

@Daksha1611

@Daksha1611Daksha1611 commented Sep 7, 2026

Copy link
Copy Markdown

Tracking issue

Closesflyteorg/flyte#7994

Why are the changes needed?

flytekit.core.utils._dnsify is the only place flytekit enforces the Kubernetes DNS_LABEL contract. Its docstring states the result "must only consist of alphanumeric (lower-case a-z, and 0-9) and not exceed 63 characters. It's permitted to have '-' character as long as it's not in the first or last positions." It violates both halves of that.

1. The 63-character cap was applied to the input, not the output. The hash-and-truncate step ran before the character conversion, but the conversion grows the string — every upper-case character has a - inserted before it. A 62-character camelCase name passed the input check and came out at 70 characters:

NAME="TrainImageClassifierOnLargeDatasetWithHyperparameterSweepStage"# 62 chars@workflowdefwf(x: int) ->int:
returnt(x=x).with_overrides(node_name=NAME)
len(wf.nodes[0].id) # 70wf.nodes[0].id# 'train-image-classifier-on-large-dataset-with-hyperparameter-sweepstage'

The len(res) < 62 guards did not bound anything: they only suppressed the separator, while the character itself was still appended unconditionally. They also silently corrupted the tail of the name — note sweepstage above, where the separator before Stage was dropped.

2. Consecutive separators could leave a trailing -. Each of _, -, . appended a - without checking whether the previous character was already one, and only a single trailing - was stripped, so _dnsify("test..") returned 'test-' — not a valid DNS_LABEL.

_dnsify is applied to every node ID in flytekit/tools/translator.py, to node_name overrides in flytekit/core/node.py, and to eager execution names in flytekit/core/worker_queue.py. Nothing downstream re-validates length or shape, so an over-long or trailing-- identifier is serialized into the workflow spec and registered as-is.

What changes were proposed in this pull request?

In flytekit/core/utils.py:

  1. Move the hash-and-truncate step after the character conversion, so the 63-character cap is enforced on the value actually returned. The hash is still taken over the original input, so two inputs sharing a suffix still do not collide, and the existing "t" * 64 -> "da4b348ebe-" + "t" * 52 behaviour is unchanged.
  2. Collapse runs of separators_, - and . no longer append a - when the result already ends in one.
  3. Strip every trailing - rather than just one, and drop a leading - from the truncated tail so the hashed form does not read as hash--tail.
  4. Remove the len(res) < 62 guards, which only dropped separators without bounding the length.

Only inputs whose converted form reaches 63 characters change shape, and those were already producing invalid identifiers. Names that convert to 62 characters or fewer are untouched.

How was this patch tested?

tests/flytekit/unit/core/test_utils.py:

  • Three cases added to the existing test_dnsify parametrization for consecutive separators ("test..", "test_-", "my_task-.name..").
  • test_dnsify_is_a_valid_dns_label — asserts the output is <= 63 characters and matches ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ for nine representative inputs (camelCase, all-upper, alternating case, separator-only).
  • test_dnsify_node_name_override_is_a_valid_dns_label — end-to-end regression through the public API, checking the node ID produced by with_overrides(node_name=...).

All ten pre-existing test_dnsify cases pass unchanged. Twelve of the new assertions fail on master and pass with this change.

Setup process

uv venv --python 3.12 .venv && uv pip install -e . && uv pip install -r dev-requirements.in
.venv/bin/python -m pytest tests/flytekit/unit/core/test_utils.py -q
# 26 passed

Full tests/flytekit/unit/core + tests/flytekit/unit/test_translator.py run before and after: identical failure set (33 pre-existing, environment-related failures on both sides), 1407 -> 1420 passing, no new failures.

Additionally, a fuzz sweep over 300k random inputs drawn from [A-Za-z0-9_-.$ ] (lengths 0–200) produces 431 outputs that are not valid DNS_LABELs on master and 0 with this change, with a maximum output length of 63.

Check all the applicable boxes

  • I updated the documentation accordingly. (No docs change needed — the existing _dnsify docstring already states the contract; this makes the code match it.)
  • All new and existing tests passed.
  • All commits are signed-off.

`_dnsify` documents that its result is a valid Kubernetes DNS_LABEL: at most
63 characters, lower-case alphanumerics and '-', never leading or trailing
with '-'. Two bugs let it return values that are neither.
The hash-and-truncate step ran before the character conversion, so the cap was
enforced on the input rather than on the result. The conversion grows the
string -- every upper-case character has a '-' inserted before it -- so a
62-character camelCase name passed the input check and came out at 70
characters. The `len(res) < 62` guards did not help: they only suppressed the
separator, while the character itself was still appended, so `res` kept growing
past 63. They also dropped separators from the tail of the name, turning
`...SweepStage` into `...sweepstage`.
Separately, each of '_', '-' and '.' appended a '-' without checking whether
the previous character was already one, and only a single trailing '-' was
stripped, so two adjacent separators at the end of a name produced a label
ending in '-'.
Enforce the length on the converted value instead, keeping the hash over the
original input so distinct values sharing a suffix still do not collide;
collapse runs of separators; and strip every trailing '-'.
`_dnsify` is applied to every node ID in `tools/translator.py`, to `node_name`
overrides in `core/node.py`, and to eager execution names in
`core/worker_queue.py`, and nothing downstream re-validates the result.
Fixesflyteorg/flyte#7994
Signed-off-by: Daksha1611 <mehtadaksha1611@gmail.com>
Sign up for freeto 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.

[BUG] flytekit: _dnsify can return node IDs over 63 characters or ending in '-', violating the DNS_LABEL contract

1 participant

@Daksha1611