Skip to content

Fix trailing element separator rejecting valid dd tracestate - #12229

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 10 commits into
masterfrom
mcculls/w3c-accept-trailing-separators
Aug 21, 2026
Merged

Fix trailing element separator rejecting valid dd tracestate#12229
gh-worker-dd-mergequeue-cf854d[bot] merged 10 commits into
masterfrom
mcculls/w3c-accept-trailing-separators

Conversation

@mcculls

@mcculls mcculls commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • A trailing ; at the end of the W3C tracestate dd member value was rejected as invalid, dropping the entire dd member (and its decoded tags/priority/origin) instead of just ignoring the harmless trailing separator.
  • The parser should be more lenient and accept a trailing ; at the end, as the preceding content is still parseable.
  • Fixes W3CPTagsCodec.validateCharsUntilSeparatorOrEnd to always terminate on the element separator, regardless of position, matching the intent of the surrounding parser. The still-invalid case of a trailing : (key with no value) remains rejected via an existing downstream check.
  • Applied the same trailing-separator fix to DatadogPTagsCodec (the _dd.p.* header codec), so _dd.p.dm=-4, is accepted the same way.
  • Excluded ; (element separator) from isAllowedKeyChar in the W3C codec, and =/, (TAG_KEY_SEPARATOR/TAGS_SEPARATOR) in the Datadog codec, so a stray separator can no longer be silently swallowed into the next key.
  • Empty elements — one or more bare ; separators, anywhere in the dd value (leading, interior, or trailing), optionally padded with OWS — are ignored rather than rejected, e.g. dd=s:2;;o:some and dd=;s:2;o:some are both accepted and behave the same as dd=s:2;o:some. This mirrors how the outer W3C tracestate grammar already tolerates OWS around commas between list-members.
  • Malformed individual elements inside the dd value (invalid key/value characters, interior OWS abutting real content, missing :) are now skipped over during parsing instead of discarding the entire dd member — they never become decoded tags. The one exception: a known tag whose value fails semantic validation (t.dm/t.tid/t.dj format checks) still drops the whole dd member, since at that point we know the content is genuinely corrupt rather than just malformed framing.
  • When re-serializing the dd member for propagation, malformed/unrecognized elements are relayed byte-for-byte alongside the elements we do understand, rather than being dropped from the outgoing header. We don't extract data from them (they're excluded from the decoded tag map), but we also don't assume they're meaningless to every consumer — a different/future tracer version may understand a shape we don't. E.g. dd=s:0;t.dm:934086a686-4; t.x:y decodes only s:0;t.dm:934086a686-4 into tags, but the outgoing header still contains the trailing t.x:y unchanged. This behavior is now deterministic regardless of what else is present in the dd value (previously it depended by accident on whether another unrelated unrecognized-but-valid element happened to be present).
  • Fixed cleanUpAndAppendUnknown re-emitting a known p: element a second time as if it was unrecognized passthrough content, producing a duplicated tag in the outgoing header whenever an unrelated unknown element was also present (e.g. dd=p:b6241412414a;x:y round-tripping to dd=p:b6241412414a;x:y;p:b6241412414a).

Test plan

  • Added W3CHttpExtractorTest, W3CPropagationTagsTest, and DatadogPropagationTagsTest cases covering: trailing element separator, trailing separator + OWS (space/tab), trailing separator + OWS before the next list-member comma, empty/leading/interior element separators (ignored), interior OWS directly abutting content (dropped from decoded tags, not a full rejection), a malformed empty-value element (dropped from decoded tags, siblings retained), bare/colon-less and space-containing malformed elements before/after/between known elements (relayed verbatim in the outgoing header, excluded from decoded tags, consistently regardless of neighboring content), and o:/s:/p: elements not being duplicated when re-serialized alongside an unknown element.
  • Confirmed each new test fails before its corresponding fix and passes after.
  • ./gradlew :dd-trace-core:test --tests "datadog.trace.core.propagation.*" passes.

🤖 Generated with Claude Code

@mcculls mcculls added type: bug fix Bug fix comp: core Tracer core tag: ai generated Largely based on code generated by an AI or LLM labels Aug 18, 2026
@jordan-wong jordan-wong self-assigned this Aug 18, 2026
@datadog-datadog-us1-prod

This comment has been minimized.

@jordan-wong jordan-wong removed their assignment Aug 18, 2026
@dd-octo-sts

dd-octo-sts Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 14.05 s 13.85 s [+0.5%; +2.3%] (maybe worse)
startup:insecure-bank:tracing:Agent 12.96 s 12.99 s [-1.0%; +0.6%] (no difference)
startup:petclinic:appsec:Agent 17.47 s 17.36 s [-0.3%; +1.6%] (no difference)
startup:petclinic:iast:Agent 17.44 s 17.65 s [-1.8%; -0.6%] (maybe better)
startup:petclinic:profiling:Agent 17.55 s 17.25 s [+0.7%; +2.8%] (maybe worse)
startup:petclinic:sca:Agent 17.53 s 16.80 s [+0.0%; +8.7%] (maybe worse)
startup:petclinic:tracing:Agent 16.62 s 16.69 s [-1.4%; +0.6%] (no difference)

Commit: 2618d351 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

@mcculls
mcculls marked this pull request as ready for review August 18, 2026 14:07
@mcculls
mcculls requested review from a team as code owners August 18, 2026 14:08
@mcculls
mcculls requested review from mhlidd and removed request for a team August 18, 2026 14:08

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 18d90ea4f7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@mcculls
mcculls requested a lite review from Copilot August 18, 2026 14:12

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

Note

Copilot was unable to run its full agentic suite in this review.

This PR makes W3C propagation parsing more tolerant by accepting a trailing element separator in the dd= member (e.g., dd=...;) and adds a regression test to ensure the behavior is preserved.

Changes:

  • Add a test case covering dd= state values with a trailing ;.
  • Relax parsing/validation logic to treat a trailing separator as a valid terminator.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
dd-trace-core/src/test/java/datadog/trace/core/propagation/W3CHttpExtractorTest.java Adds coverage for dd= tracestate values that end with a trailing element separator.
dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java Updates validation to stop at the separator even when it is the final character, enabling acceptance of trailing separators.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@datadog-datadog-us1-prod datadog-datadog-us1-prod Bot 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.

Datadog Autotest: FAIL

A trailing ; is now accepted only when it is the literal final character. If valid optional whitespace follows it, the parser mistakes that whitespace for another tag key and still drops the decoded Datadog sampling, origin, and propagation tags.

Open Bits AI session

🤖 Datadog Autotest · Commit 18d90ea · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest

@dougqh

dougqh commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

🤖 Claude (via automated review)

Nice fix for the trailing-separator rejection in W3CPTagsCodec. I noticed the same bug still exists in the sibling DatadogPTagsCodecvalidateCharsUntilSeparatorOrEnd (dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/DatadogPTagsCodec.java:181) retains the pos < end - 1 && c == separator guard that this PR removed from the W3C path.

Repro: PTagsFactory.fromHeaderValue(HeaderType.DATADOG, "_dd.p.dm=-4,") returns _dd.propagation_error=decoding_error instead of extracting _dd.p.dm=-4, while the equivalent W3C tracestate case (dd=s:2;o:some;) is now correctly accepted by this PR.

So an upstream producer emitting a trailing separator on the x-datadog-tags header will still break even after this lands. Might be worth applying the same fix to DatadogPTagsCodec here, or in a quick follow-up.

@mcculls
mcculls force-pushed the mcculls/w3c-accept-trailing-separators branch from afb3f3b to 4853d64 Compare August 18, 2026 15:25

@mhlidd mhlidd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, thanks for fixes.

mcculls added a commit that referenced this pull request Aug 18, 2026
isAllowedKeyChar didn't exclude the element separator, so a key scan starting right after an empty element
(e.g. "dd=s:2;;o:some") would swallow the next ';' into the key instead of failing, silently dropping the
origin tag rather than rejecting the malformed member.

Datadog's sibling codec already excludes its element separator from key chars; align W3C's the same way.

Flagged by Copilot review on PR #12229.
@mcculls
mcculls requested a lite review from Copilot August 18, 2026 17:00
@mcculls

mcculls commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

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

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (3)

dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java:357

  • This change makes validateCharsUntilSeparatorOrEnd accept a trailing separator for all separators passed to it. If this helper is also used with key/value separators (e.g., :), it can broaden accepted inputs (e.g., key: with an empty value) beyond the intended relaxation (trailing element separator only). Consider adding an allowTrailingSeparator parameter (or a second helper) so only element/tag-list separators allow trailing separators, while key/value separators keep the stricter behavior.
      pos++;
      if (pos < end) {
        c = s.charAt(pos);
        if (c == separator) {
          break; // trailing separator allowed; caller resumes parsing from here
        }
      }

dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/DatadogPTagsCodec.java:182

  • Same concern as in W3CPTagsCodec: this helper now permits trailing separators for any separator it is invoked with. If the method is reused for validating segments split by the key/value separator, this can unintentionally allow empty values. Consider scoping the relaxation to list separators only (e.g., via a boolean flag or dedicated method) to avoid widening the accepted grammar more than required.
      pos++;
      if (pos < end) {
        c = s.charAt(pos);
        if (c == separator) {
          break; // trailing separator allowed; caller resumes parsing from here
        }
      }

dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java:359

  • The updated validation/parsing behavior (including the trailing-separator relaxation) appears duplicated across W3CPTagsCodec and DatadogPTagsCodec. To reduce the risk of the two codecs drifting over time, consider extracting the common validation routine(s) into a shared utility (or a small internal base/helper) with codec-specific configuration for separators/allowed-char predicates.
      pos++;
      if (pos < end) {
        c = s.charAt(pos);
        if (c == separator) {
          break; // trailing separator allowed; caller resumes parsing from here
        }
      }

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d11ce6a2e9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@mcculls

mcculls commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@mcculls
mcculls requested a lite review from Copilot August 18, 2026 19:12

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6fa097a94d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

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

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (2)

dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java:120

  • The new “skip malformed element” behavior means an element with a known tag key but an empty value (e.g. t.dm: / t.tid:) will be treated as a malformed element and skipped, so earlier valid elements in the dd= value will still be kept. This contradicts the PR description’s exception that known tags with semantically invalid values should drop the whole dd member, and also undermines the intent that a trailing : remains invalid.

Consider explicitly detecting the empty-value case before calling validateCharsUntilSeparatorOrEnd and, for known semantic tags (t.dm, t.tid, t.ts), reverting to dropping the entire dd member (while still skipping empty values for unknown tags). Also add a test like dd=s:0;t.dm:934086a686-4;t.tid: to lock this behavior down.

      int tagValuePos = tagKeyEndsAt + 1;
      int tagValueEndsAt =

dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java:663

  • cleanUpAndAppendUnknown finds the end of each dd= element using original.indexOf(ELEMENT_SEPARATOR, elementStart) without bounding it to ddMemberValueEnd. If the dd member has a single unknown element without any ; (e.g. dd=x:y) and a later non-dd tracestate member contains ; in its value, indexOf can jump past the dd member and cause this method to skip or append an incorrect slice that spans across the comma into the next member.

Cap elementEnd to ddMemberValueEnd when indexOf returns a position beyond it.

      int elementEnd = original.indexOf(ELEMENT_SEPARATOR, elementStart);
      if (elementEnd < 0) {
        elementEnd = w3CPTags.ddMemberValueEnd;
      }

mcculls and others added 9 commits August 20, 2026 21:43
A trailing ';' at the end of the W3C tracestate 'dd' member value was
incorrectly rejected as invalid, dropping the entire dd member and any
decoded tags, priority, or origin instead of ignoring the separator.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Skipping to the next comma when a trailing separator is followed by OWS (e.g. "dd=s:2;o:some;  ,x=y")
left the parser resuming on a space, which isAllowedKeyChar rejects and drops the whole dd member.

We add skipLeadingOWC to consume that OWS, and restyle stripTrailingOWC to the same while-loop shape.
isAllowedKeyChar didn't exclude the element separator, so a key scan starting right after an empty element
(e.g. "dd=s:2;;o:some") would swallow the next ';' into the key instead of failing, silently dropping the
origin tag rather than rejecting the malformed member.

Datadog's sibling codec already excludes its element separator from key chars; align W3C's the same way.

Flagged by Copilot review on PR #12229.
… codec's isAllowedKeyChar.

Note TAG_KEY_SEPARATOR is already caught by the separator check in validateCharsUntilSeparatorOrEnd,
so this doesn't change behavior, but keeps the predicate decoupled from how it's used by the caller.
(the similar KEY_VALUE_SEPARATOR check in the W3C codec is also a no-op given how it's used, but is
also worth keeping in case the calling code changes.)
…content

cleanUpAndAppendUnknown re-scans the raw tracestate to re-append unknown
dd submembers, assuming every element is at least 2 chars long so it can
peek a second character to detect known s/o tags. Accepting a trailing
separator followed only by OWS (e.g. "dd=x:y; ") breaks that assumption.

We skip leading OWS before each element the same way the value scanner
already does, so a trailing whitespace-only remainder is recognized as
padding and the loop exits cleanly.
(unless we're sure it's a bad tid/dm/ts value in which case it's safer to drop the whole section)

Ensure last parent tag (p:) is not duplicated when updating
@mcculls
mcculls force-pushed the mcculls/w3c-accept-trailing-separators branch from 6b1cc37 to a226c19 Compare August 20, 2026 20:43
@mcculls
mcculls force-pushed the mcculls/w3c-accept-trailing-separators branch from a226c19 to 2618d35 Compare August 20, 2026 20:55
@mcculls

mcculls commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@mcculls
mcculls requested a lite review from Copilot August 20, 2026 21:07
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 2618d3513a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

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

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (1)

dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java:134

  • When tagValueEndsAt < 0, the code now skips the malformed element unconditionally. For t.* elements this means an empty value like t.tid: / t.dm: (i.e., : followed immediately by ; or end-of-dd-member) will be silently dropped while the rest of the dd member is still accepted and propagated. This contradicts the stated intent that the trailing-: (key with no value) case remains rejected, and can mask genuinely corrupt t.* content when valid siblings are present (e.g., dd=s:2;t.tid:;o:rum would round-trip without rejecting the dd member).
      if (tagValueEndsAt < 0) {
        int nextTagPos = skipMalformedElement(value, tagValuePos, ddMemberValueEnd);
        maxUnknownSize += (nextTagPos - tagPos); // still relay malformed elements
        tagPos = nextTagPos;
        continue;

@mcculls

mcculls commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Aug 20, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-08-20 22:27:56 UTC ℹ️ Start processing command /merge


2026-08-20 22:28:01 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in master is approximately 2h (p90).


2026-08-21 00:28:43 UTCMergeQueue: The build pipeline has timeout

The merge request has been interrupted because the build 3316069053876440707 took longer than expected. The current limit for the base branch 'master' is 120 minutes.

@mcculls

mcculls commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Aug 21, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-08-21 07:31:47 UTC ℹ️ Start processing command /merge


2026-08-21 07:31:52 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in master is approximately 2h (p90).


2026-08-21 08:30:17 UTC ℹ️ MergeQueue: This merge request was merged

@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot merged commit 3f581ec into master Aug 21, 2026
590 checks passed
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot deleted the mcculls/w3c-accept-trailing-separators branch August 21, 2026 08:30
@github-actions github-actions Bot added this to the 1.66.0 milestone Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: core Tracer core tag: ai generated Largely based on code generated by an AI or LLM type: bug fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants