Normalize control characters in X-Request-Id and X-Opaque-Id before logging - #22682
Normalize control characters in X-Request-Id and X-Opaque-Id before logging#22682DarshitChanpura wants to merge 2 commits into
Conversation
…ogging The X-Request-Id and X-Opaque-Id request headers are client-provided and are written into search slow logs and echoed into responses. This normalizes these values by stripping ASCII control characters at ingestion (RestController) and at the response-echo path (DefaultRestChannel), so downstream log records stay single-line and well-formed regardless of client input. Adds a shared RequestUtils.sanitizeHeaderValue() helper and unit tests. Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
PR Reviewer Guide 🔍(Review updated until commit caa8998)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to caa8998
Previous suggestionsSuggestions up to commit 3ac878a
|
| if (Task.X_REQUEST_ID.equals(restHeader.getName())) { | ||
| RequestUtils.validateRequestId(distinctHeaderValues.getFirst(), requestIdMaxLength); | ||
| if (Task.X_REQUEST_ID.equals(name)) { | ||
| RequestUtils.validateRequestId(headerValue, requestIdMaxLength); |
There was a problem hiding this comment.
this previously passed distinctHeaderValues.getFirst(); it now passes the joined headerValue. These are equivalent for X-Request-Id: it's registered as single-valued (new RestHeaderDefinition(Task.X_REQUEST_ID, false)), so the guard above (L433) returns 400 for more than one value before this branch is reached — meaning distinctHeaderValues always has exactly one element here, and String.join(",", …) equals getFirst(). Validating headerValue directly is intentional: it validates the exact value stored in the ThreadContext (no chance of the validated and stored values drifting) and stays correct if the header were ever made multi-valued.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #22682 +/- ##
=========================================
Coverage 71.48% 71.49%
- Complexity 76960 76981 +21
=========================================
Files 6156 6156
Lines 358444 358452 +8
Branches 52246 52247 +1
=========================================
+ Hits 256240 256273 +33
- Misses 81792 81808 +16
+ Partials 20412 20371 -41 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Cover the RestController ingestion path (stored ThreadContext value is stripped of control characters) and the DefaultRestChannel response-echo path (echoed X-Request-Id / X-Opaque-Id are stripped), which the RequestUtils unit tests alone did not exercise. Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
|
Persistent review updated to latest commit caa8998 |
|
❕ Gradle check result for caa8998: UNSTABLE Please review all flaky tests that succeeded after retry and create an issue if one does not already exist to track the flaky failure. |
|
@DarshitChanpura FYI there used to be some formatting imposed, but that was removed in #21048. I think there is definitely room for sanitization, but can there also be something on the security plugin side to escape characters like newline? |
|
@cwperks We added this recently and can be expanded to cover more: |
Description
The
X-Request-IdandX-Opaque-Idrequest headers are client-provided and are written into the search slow logs (SearchSlowLog) and echoed back into HTTP responses. Today their values are used as-is. This change strips ASCII control characters from these values before they are used in log output, so that log records stay single-line and well-formed regardless of client input.What changed
RequestUtils.sanitizeHeaderValue()helper that removes ASCII control characters (\p{Cntrl}), leavingnulland well-formed values (UUIDs, hex/alphanumeric identifiers) unchanged.RestControllerwhere correlation headers are copied into theThreadContext, so every downstream consumer that reads them viaTask.getHeader(...)(includingSearchSlowLog) receives a normalized value.DefaultRestChannel, whereX-Request-Id/X-Opaque-Idare read directly off the request to echo into the response (this path bypasses the thread context).%enc{...}{JSON}; this closes the gap for the text (PatternLayout) path and response headers.Why
Header values come directly from clients. Normalizing them keeps log files clean and reliably parseable for downstream tooling (log shippers, SIEM ingestion) and keeps a single logical request represented on a single log line.
Related component
Search/Logging
Check List
--signoff.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.