OpenTelemetry OTLP disk retry temp-blob injection (GHSA-4625-4j76-fww9 / CVE-2026-42191)¶
Signal: GitHub Security Advisories published 2026-04-30. OpenTelemetry .NET OTLP exporter disk retry could fall back to shared temp directories and trust predictable retry blob paths.
What it is¶
When OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY=disk was enabled without OTEL_DOTNET_EXPERIMENTAL_OTLP_DISK_RETRY_DIRECTORY_PATH, OpenTelemetry.Exporter.OpenTelemetryProtocol used Path.GetTempPath() and fixed subdirectories such as traces, metrics, and logs for retry *.blob files.
On multi-user systems or shared temp roots, a local attacker could:
- inject crafted retry blobs that the exporter later forwards to the configured OTLP collector under the application identity;
- read queued telemetry blobs during export failures, exposing spans, metrics, logs, and possibly sensitive attributes;
- deposit many or oversized blobs to exhaust disk/CPU/IO in retry loops.
Affected package: NuGet OpenTelemetry.Exporter.OpenTelemetryProtocol versions 1.8.0 through 1.15.2 when disk retry is enabled and no dedicated retry directory is configured. Fixed version: 1.15.3.
Reference: https://github.com/advisories/GHSA-4625-4j76-fww9
Triage¶
- Search .NET services for
OpenTelemetry.Exporter.OpenTelemetryProtocoland disk retry environment variables. - Identify hosts where app temp directories are shared with other users, tenants, jobs, sidecars, or containers.
- Check for
/tmp/traces,/tmp/metrics,/tmp/logs,%TEMP%\traces,%TEMP%\metrics, or%TEMP%\logscontaining unexpected*.blobfiles. - Treat telemetry from affected windows as potentially tainted or disclosed if local attackers or co-tenants were present.
Mitigation¶
- Upgrade
OpenTelemetry.Exporter.OpenTelemetryProtocolto1.15.3or later. - Configure a dedicated retry directory with strict owner-only permissions whenever disk retry is enabled.
- Avoid disk retry in shared or multi-tenant environments unless the retry path is private and quota-bound.
- Keep telemetry exporter egress pinned to trusted collectors and protected by TLS.
Detection ideas¶
- Monitor retry directories for unexpected owners, permissions, symlinks, or anomalous blob counts/sizes.
- Alert when telemetry retry backlogs grow suddenly or exporter CPU/IO spikes during retry scans.
- Inspect collector data for malformed or impossible spans/logs that could indicate blob injection.
Durable lesson¶
Retry queues are trust boundaries. If a process will later replay files with its identity, the queue path must be private, permission-checked, quota-bound, and never silently default to a shared temp root.
October 8 follow-up: same shape in native-library loaders (GHSA-mcr4-qmvw-px4g / CVE-2026-106451, high, yawkat lz4-java < 1.11.4)¶
net.jpountz.util.Native.load() extracts the bundled native library into java.io.tmpdir when no system liblz4-java is present and System.load()s it. Only the .lck sentinel gets a random, exclusively-created name; the library path is derived by stripping .lck, is predictable before creation, and is opened without exclusive creation. A co-tenant that wins the race controls the code loaded into every JVM that later uses the library — arbitrary-code-execution-grade local persistence in one shared temp root.
Operator rules for shared-host footholds (the recon half of this class):
- Enumerate loader-written temp artifacts. On any multi-user host, list predictable shared-temp library/bundle/queue names (
lib*-java*.so, exporter retry blobs, extracted helper binaries). A file another user's process will laterload()/replay with its identity is a standing local-privilege target. - Race-shape check, bounded. The reportable primitive is "path is predictable and non-exclusive" — prove with a marker file placed at the predicted path in a lab, never by loading code on a shared system. Evidence: the lock-file/library-file naming scheme, file owner, and load-order timeline.
- Grep-side fingerprint for audits:
createTempFilefollowed by a derived (string-mangled) path used inSystem.load/dlopen/exec— the random name guards only the lock, not the payload. Same genealogy as the Oct 6 Nx world-connectable daemon UDS finding: the safely-created artifact and the actually-consumed artifact are different files.
October 14 follow-up: Metalama — sentinel-marker trust in shared temp, executed inside the compiler (CVE-2026-62847 / GHSA-m6wq-39m7-9xpp, medium/CVSS-v3 8.2, Metalama.Framework/Tool <2025.1.18, <2026.0.25, <2026.1.19)¶
Metalama (a .NET compile-time weaving tool) cached generated compile-time assemblies, extracted auxiliary executables, and unpacked embedded helper assemblies into predictable directories under Path.GetTempPath() (/tmp on Linux/macOS default) and decided "already built, trust it" from the presence of a sentinel/.completed marker file — no integrity check. An unprivileged local user pre-creates the deterministic cache tree, plants a malicious DLL plus the matching sentinel; when a higher-privileged user or CI/build service account next runs a build, the tool skips regeneration and loads/executes the planted assembly inside the compiler/IDE/MSBuild process = cross-user code execution on shared POSIX hosts (multi-user dev machines, shared CI runners). Windows default is unaffected (per-user temp ACLs).
Operator axes this adds beyond the lz4-java race leg:
- Sentinel-as-trust is a greppable pattern distinct from the derived-path race. Here the attacker doesn't even need to win a race: existence of
*.completed(or any done-marker convention) is the integrity verdict. Audit fingerprint: directory-exists + marker-file check preceding an assembly/exec load from any shared path. Enumeration on a shared runner:find /tmp -maxdepth 3 \( -name "*.completed" -o -name "done" -o -name ".sentinel" \) -newer /etc/hostname-style sweeps, then check who will later execute what sits beside the marker. - Build-tool caches are compiler-process code execution, i.e. a supply-chain position. Reading source/env and tampering build output happens inside the trusted build, so artifacts stay signed/trusted downstream. For CI foothold triage: enumerate which build plugins extract-and-load from shared temp before your first build on a runner — the plant precedes the privileged run by design.
- Deterministic-cache-name check: derive the expected cache path from the tool's own naming scheme (package version, hash-of-what inputs) in a lab, prove with a marker DLL you never let a real build execute outside your own runner. The advisory's scope note doubles as a boundary rule:
METALAMA_TEMP-style deployer overrides are config, not the defect — verify the default path on the box you're testing. - Same page lineage: exporter retry blobs (this page's original leg) → native-library loader (
lz4-java) → compiler-time assembly cache (Metalama). Three layers, one rule: anything a later, more privileged process replays or loads from a shared root is a standing target, and the "was it already made safely?" verdict must come from ownership/integrity, not existence.