feat: add inbound header validation and tests to trace package - #3
Open
daveseddon-runpod wants to merge 4 commits into
Open
daveseddon-runpod wants to merge 4 commits into
daveseddon-runpod wants to merge 4 commits into
Conversation
Validate inbound X-Trace-ID/X-Request-ID (length + charset, regenerate on failure) to prevent header/log injection; fix Python Trace.new() and save_to_headers tuple bug and bring it to five-header parity with Go; add table-driven Go + Python tests and benchmarks. Part of the end-to-end X-Trace-ID tracing effort. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… lint - py: parse/validate inbound X-Trace-Start (RFC3339, fall back to now) and drop the non-contract X-Request-Start read, closing a CWE-93 header-injection vector where a poisoned timestamp was re-emitted verbatim by save_to_headers - py: _resolve_source drops empty/invalid to "" to match Go's resolveSource; datetime.now(timezone.utc) for correct UTC; new() return annotation; Optional -> | None - py tests: add poisoned/invalid X-Trace-Start rows + valid-preserved row - go: extract X-Trace-* header names to consts (SaveToHeader/FromHeaderOrNew); close response bodies in trace tests; reuse fixture consts; fix an empty-source test description - docs: clarify request_id is minted fresh per outbound sub-request; the inbound edge honors a valid caller-supplied X-Request-ID Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
daveseddon-runpod
marked this pull request as ready for review
September 22, 2026 02:07
FromHeaderOrNew logged an slog.Warn once per request whenever an inbound X-Trace-Start was in the future. That value is caller-controlled, so an untrusted client could drive a consuming service's log volume by sending a future timestamp on every request. A malformed start already falls back to now with no log; the future case is now handled the same way, folded into the parse guard. Align the Python resolver, which previously neither clamped nor warned on a future start (it propagated it verbatim): it now clamps to now to match Go, covered by a regression test. This removes the need for each public-facing consumer to strip inbound X-Trace-Start defensively. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Harden and test the shared
tracepackage — the cross-serviceX-Trace-*header contract thathost,hapi,proxy, and the RunPod GraphQL API all speak.Part of the end-to-end
X-Trace-IDtracing effort (RunPod GraphQL → hapi/proxy → host, propagate-or-generate at every hop).Changes
Go —
trace/trace.goX-Trace-ID/X-Request-IDthat is empty, overmaxIDLen(200), or contains a byte outside[A-Za-z0-9._-](CR/LF, control, space) is rejected and regenerated — prevents header/log injection (CWE-93).X-Trace-Source/X-Request-Sourceare validated and blanked (not regenerated) when invalid.validID) — 0 allocs/op.X-Trace-Startparse so the common no-header path skipstime.Parse.orelsegeneric.Python —
py/trace.pyTrace.new()(datetime.now()AttributeError) and thesave_to_headerstuple bug (X-Request-IDstored as a 1-tuple via a trailing comma); now emits the same five headers as Go with a freshX-Request-ID._valid_id/_resolve_id/_resolve_source).Tests
trace/trace_test.go— table-driven ({description, input, expected}):validID,resolveID,resolveSource,FromHeaderOrNew(present/absent/poisoned/source-drop), trace-start (absent/past/invalid/future-clamp),SaveToHeaderround-trip,ClientMiddleware/ServerMiddleware,New()uniqueness (10k),AllocsPerRun(0-allocvalidID), plus benchmarks.py/trace_test.py—unittesttable-driven mirror incl. a regression test per fixed bug.Docs — README
## Tracingnow carries the header contract table, Go inbound/outbound/response-echo usage, and the inbound-validation behavior.Benchmarks (
-benchmem)validIDmeets the 0-alloc requirement; the 6-alloc paths are unavoidable UUID generation. Negligible against network I/O.Verification
go test -race ./...pass; benchmarks run.unittest— 8 tests pass.Follow-up
Tag a new module version (e.g.
v0.1.2) sohost/hapi/proxycan bump theirrequireand pick up the inbound sanitization. Backward compatible — the only visible change is that a hostile inbound id is regenerated.🤖 Generated with Claude Code