fix: guard against non-string options.host in http instrumentation - #78
Draft
RaphaelManke wants to merge 1 commit into
Draft
fix: guard against non-string options.host in http instrumentation#78RaphaelManke wants to merge 1 commit into
RaphaelManke wants to merge 1 commit into
Conversation
instrumentation-http reads `options.host` and calls .indexOf() / .match() on it without checking that it is a string, outside of any safeExecuteInTheMiddle. Node resolves the target from `options.hostname` first and ignores `options.host` entirely whenever `hostname` is set, so a non-string `host` is inert for Node but throws `TypeError: host.indexOf is not a function` inside the instrumentation -- killing the caller's request and the whole invocation. Sanitize the options in the ignoreOutgoingRequestHook we already configure: drop an unused non-string `host` when `hostname` is usable, and skip instrumenting the request entirely when it is not, so Node reports its own error instead of us masking it with ours. Reported upstream as open-telemetry/opentelemetry-js#6967 -- every published version from 0.50.0 to 0.221.0 is affected, so upgrading the dependency is not an option. Tests drive the real upstream wrapper over the real Dash0 config by applying the instrumentation's own patch via getModuleDefinitions(); enable() is a no-op under jest because require-in-the-middle cannot hook jest's module registry, which would let the requests run unpatched and pass silently. Refs SUP-1457
mosheshaham-dash0
approved these changes
Aug 12, 2026
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.
Fixes the crash in SUP-1457: instrumented Lambda invocations dying with
Cause
@opentelemetry/instrumentation-httpreadsoptions.hostand calls.indexOf()/.match()on it without checking that it is a string:Node's
ClientRequestderives the target fromoptions.hostnamefirst and ignoresoptions.hostentirely wheneverhostnameis set (it overwrites it internally). So a non-stringhostsitting next to a validhostnameis completely inert for Node — the request works, and has worked for as long as the calling code existed — but it throws inside the instrumentation. BecausegetOutgoingRequestAttributes()is called outside anysafeExecuteInTheMiddle(), the error propagates synchronously out of the caller'shttp.request()and takes the whole invocation with it.Verified against the published packages: every version from 0.50.0 through 0.221.0 is affected (0.50.0, 0.54.2, 0.55.0, 0.57.0, 0.200.0, 0.203.0, 0.205.0, 0.207.0–0.210.0, 0.215.0, 0.219.0, 0.220.0, 0.221.0). Not a regression, and bumping the dependency is not a fix. Reported upstream as open-telemetry/opentelemetry-js#6967.
No off-the-shelf client produces this shape — I probed 24 stacks (graphql-request, Apollo, urql, node-fetch, cross-fetch, axios, got, superagent, needle, the AWS Smithy handler, aws4-signed AppSync requests, …) and they all pass
hostas a string or set onlyhostname. It comes from hand-rolled request-option builders.Fix
A
makeHostSafe()guard at the top of theignoreOutgoingRequestHookthis distro already configures. That hook is the right seam: upstream calls it with the sameoptionsParsedobject before both crash sites,optionsParsedis a fresh copy so mutating it never touches the caller's object, and upstream already wraps the hook insafeExecuteInTheMiddle()so a bug in our guard cannot crash an invocation either.host+ usablehostname→ drophost(behaviour-preserving; Node ignores it anyway)host, no usablehostname→ skip instrumenting, so Node raises its ownERR_INVALID_ARG_TYPEinstead of us masking it with a TypeError of our own. These options are invalid for Node with or without the layer; we get out of the way rather than inventing a different failure.The guard is a no-op once #6967 ships, and is harmless to leave in place until then.
Tests
httpInstrumentation.test.tspreviously had one assertion (module name). It now exercises the real upstream wrapper over the real Dash0 config and asserts on exported spans.Before the fix: 4 failed, 2 passed — three shapes with
TypeError: host.indexOf is not a function, plus the no-hostnamecase failing because our TypeError replaced Node's error. After: 6 passed.hostis a URL objectTypeError: host.indexOfhttp.url/net.peer.name/net.peer.porthostis a plain objectTypeError: host.indexOfhostis a numberTypeError: host.indexOfhostnon-string, nohostnameTypeError: host?.matchERR_INVALID_ARG_TYPEhostis a string (control)One thing to know if you write tests here later:
instrumentation.enable()does nothing under jest, because require-in-the-middle cannot hook jest's module registry — requests run completely unpatched and the tests pass silently without testing anything. These tests instead apply the instrumentation's own patch through the publicgetModuleDefinitions(), which runs the genuine_outgoingRequestFunction.Verification
npx jest --no-cache→ 137 passed, 12 suitesnpm run build→ succeeds; guard confirmed present in the minifieddist/init.mjshttp.url,net.peer.name), since nothing in the repo setsOTEL_SEMCONV_STABILITY_OPT_INNotes, out of scope for this PR
getOutgoingRequestAttributes()running outsidesafeExecuteInTheMiddle()means any future upstream defect in attribute building can still kill an invocation. A real safety net needs the pristinehttp.requestcaptured inbootstrap.tsbefore instrumentation registers, so an outer wrapper can fall back to it. Suggested upstream too.npm installinopt/nodefails on a fresh clone: thefile:../../build/opentelemetry-instrumentation-aws-sdk.tgzdependency is resolved before its ownpreinstallbuilds it, soscripts/build-aws-sdk-tarball.shhas to be run manually first.FileSpanExportertests writetest-spans-*.jsonintoopt/node/without cleaning up, and those paths are not gitignored.