fix: Harden OTEL endpoint validation#12954
Merged
Merged
Conversation
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…fff:127.0.0.1`) bypass the SSRF protection in `is_blocked_ipv6`, allowing access to cloud metadata endpoints and internal services. This commit fixes the issue reported at crates/turborepo-run-summary/src/observability/otel.rs:171 ## Bug Analysis The `is_blocked_ipv6` function is designed to prevent SSRF attacks by blocking requests to internal/private IPv6 addresses. However, it does not account for IPv4-mapped IPv6 addresses (the `::ffff:0:0/96` prefix range). **How the bypass works:** 1. An attacker configures the OTel endpoint to `https://[::ffff:169.254.169.254]/latest/meta-data/` 2. The `url` crate (v2.5.7, following WHATWG spec) parses this as `Host::Ipv6(Ipv6Addr)` with segments `[0, 0, 0, 0, 0, 0xffff, 0xa9fe, 0xa9fe]` 3. `is_blocked_otel_host` dispatches to `is_blocked_ipv6` 4. In `is_blocked_ipv6`, none of the existing checks match: - `is_loopback()` → false (only matches `::1`) - `is_unspecified()` → false (only matches `::`) - ULA check `(first & 0xfe00) == 0xfc00` → `(0 & 0xfe00) == 0xfc00` → false - Link-local check `(first & 0xffc0) == 0xfe80` → false - Multicast check `(first & 0xff00) == 0xff00` → false - Documentation check → false 5. `is_blocked_ipv6` returns `false`, allowing the request through The same bypass works for any private IPv4 address wrapped in IPv4-mapped IPv6 notation: `::ffff:127.0.0.1` (loopback), `::ffff:10.0.0.1` (private), `::ffff:192.168.1.1` (private), `::ffff:100.100.100.200` (OIDC metadata), etc. **Impact:** Complete bypass of all IPv4 SSRF protections. An attacker with control over the OTel endpoint configuration can reach the AWS metadata endpoint (169.254.169.254), cloud OIDC endpoints (100.100.100.200), loopback services, and any private network address. ## Fix Added a check at the start of `is_blocked_ipv6` using `Ipv6Addr::to_ipv4_mapped()` (stable since Rust 1.75, and this project uses nightly). When an IPv4-mapped IPv6 address is detected, the inner IPv4 address is extracted and delegated to `is_blocked_ipv4`, which already has comprehensive checks for all private/reserved IPv4 ranges. Also added test cases for the five most critical bypass vectors: - `::ffff:169.254.169.254` (AWS metadata) - `::ffff:127.0.0.1` (loopback) - `::ffff:10.0.0.1` (private) - `::ffff:192.168.1.1` (private) - `::ffff:100.100.100.200` (cloud OIDC) Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com> Co-authored-by: anthonyshew <anthonyshew@gmail.com>
anthonyshew
pushed a commit
that referenced
this pull request
May 26, 2026
## Release v2.9.15 > [!CAUTION] > Versioned docs aliasing FAILED. [View logs](https://github.com/vercel/turborepo/actions/runs/26478247978) ### Changes - release(turborepo): 2.9.14 (#12805) (`da36727`) - fix: Prune package.json workspaces (#12808) (`aea4138`) - fix: Wait for process trees before task completion (#12809) (`a3b4d94`) - release(turborepo): 2.9.15-canary.1 (#12810) (`f7b9d3a`) - ci: Sign macOS release binaries (#12811) (`fe3b84f`) - release(turborepo): 2.9.15-canary.2 (#12812) (`c34a86b`) - fix: Prevent cache archive symlink reads (#12813) (`ab90c81`) - release(turborepo): 2.9.15-canary.3 (#12814) (`d92bfcb`) - fix: Avoid path-racy chmod during directory restore (#12815) (`c62c92b`) - fix: Prevent cache restore symlink race writes (#12817) (`0f167cf`) - chore: Deny Rust panic extraction by default (#12818) (`958fc4e`) - fix: Make structured log symlink defense race-safe (#12821) (`46df4de`) - fix: Preserve Bun alias child packages (#12822) (`cbfef22`) - fix: Avoid UTF-8 panics at boundaries (#12823) (`a9b43ba`) - fix: Preserve non-UTF-8 Git path boundaries (#12826) (`85ba487`) - fix: Create daemon dirs with private permissions (#12827) (`aca956e`) - fix: Return Berry lockfile errors instead of panicking (#12828) (`3fd29a3`) - fix: Isolate Corepack state in integration tests (#12831) (`f49f23b`) - ci: Use larger Windows runners for Rust tests (#12832) (`7618d6e`) - docs: Add `with-vite-module-federation` example (#12794) (`2209f61`) - test: Run Rust tests without partitioning (#12833) (`e53c512`) - chore: Remove `TaskHashTracker`-based `expect()` calls (#12836) (`a10a5fe`) - chore: Deduplicate hash canonicalization (#12837) (`795a912`) - fix: Prevent Windows process drain hangs (#12838) (`030f50b`) - fix: Refactor execsync to execfilesync for Shell command built from environment values (#12829) (`a410750`) - test: Bound vt100 random quickcheck (#12839) (`8f9eac2`) - fix: Validate daemon discovery responses (#12840) (`f3268b2`) - fix: Store `PackageGraph` root invariants (#12841) (`67d733d`) - chore: Avoid engine graph node expects (#12842) (`639c535`) - test: Make Rust tests parallel-safe (#12843) (`dd34c30`) - fix: Avoid graph utility node lookup panics (#12844) (`8beff2e`) - fix: Avoid graph walker `expect()` calls (#12845) (`0734316`) - fix: Remove fs panic extraction lints (#12846) (`d6396de`) - fix: Remove fixed map panic extraction calls (#12847) (`412dc00`) - fix: Remove devtools WebSocket panics (#12850) (`2d11941`) - fix: Remove json rewrite panic lint allow (#12848) (`88709b4`) - fix: Remove turborepo-types panic lint allows (#12849) (`9d2cda3`) - chore: Remove turborepo-hash build expect (#12851) (`c271628`) - fix: Remove napi panic lint allows (#12852) (`9d631fe`) - fix: Avoid globwatch expect calls (#12853) (`800b355`) - fix: Remove LSP expect callsites (#12854) (`5a22478`) - fix: Remove scope panic lint allows (#12855) (`98cacad`) - fix: Remove task hash panic lints (#12856) (`c727e30`) - fix: Remove frameworks panic lint allows (#12857) (`6a5e891`) - fix: Remove microfrontends proxy expect lint allow (#12859) (`787eee6`) - fix: Avoid API client expect calls (#12858) (`43d3229`) - fix: Avoid task executor expect calls (#12860) (`709ebd2`) - fix: Remove turbo-trace unwrap callsite (#12863) (`23ed3ac`) - fix: Remove Vercel API mock expect usage (#12862) (`0386df2`) - fix: Remove vt100 expect lint allow (#12861) (`db1ee55`) - fix: Remove turborepo-shim expect callsites (#12864) (`6b7c2c7`) - test: Deflake daemon existing process test (#12865) (`1c57b5b`) - fix: Avoid repository NAPI unwrap calls (#12866) (`459d1e6`) - fix: Remove pidlock panic callsites (#12867) (`aacfcc6`) - fix: Remove telemetry panic callsites (#12868) (`9968f36`) - chore: Remove Rust re-export shims (#12870) (`0c7b052`) - fix: Remove turbo-json panic lint allows (#12869) (`3eb13fd`) - fix: Remove `globwalk`'s `expect()` callsites (#12871) (`ca42137`) - fix: Remove `turbopath`'s `expect()` callsites (#12872) (`e781dbe`) - test: Deflake Corepack prepare lock on Windows (#12873) (`53c9b4b`) - fix: Remove signals panic callsites (#12874) (`b5e3b6d`) - fix: Remove turbo-trace expect allow (#12876) (`67657e5`) - fix: Remove Vercel API mock unwrap usage (#12877) (`dd99f86`) - fix: Remove task executor unwrap usage (#12878) (`f16c120`) - fix: Remove run summary expect usage (#12879) (`2670768`) - fix: Remove microfrontends proxy unwrap usage (#12880) (`80da7a6`) - fix: Remove api client unwrap usage (#12881) (`73f3c1b`) - fix: Remove globwalk unwrap usage (#12883) (`a058336`) - fix: Remove UI `expect()` usage (#12882) (`843515e`) - fix: Remove microfrontends expect usage (#12885) (`91d5ac0`) - fix: Remove `boundaries`'s `expect()` usage (#12887) (`4ae4b19`) - fix: Remove `turborepo-process`'s `unwrap()` usage (#12888) (`7badbb5`) - fix: Remove UI unwrap usage (#12889) (`8c4316e`) - fix: Remove microfrontends unwrap allow (#12890) (`26168cd`) - fix: Remove `turborepo-process`'s `expect()` usage (#12891) (`f3e8a42`) - fix: Remove scm expect usage (#12893) (`4c0a0e0`) - fix: Remove auth unwrap usage (#12886) (`a2eed47`) - fix: Remove `turbopath`'s `unwrap()` usage (#12884) (`e1f2003`) - fix: Remove `auth`'s `expect()` usage (#12895) (`d13dee7`) - fix: Remove wax unwrap usage (#12899) (`04c99fb`) - fix: Remove scm unwrap usage (#12897) (`715cd2c`) - fix: Remove `turborepo-boundaries`'s `unwrap()` usage (#12896) (`4484b36`) - fix: Remove daemon unwrap usage (#12898) (`643b982`) - fix: Include lockfile-changed packages in affected tasks (#12900) (`81cae94`) - fix: Remove `turborepo-wax`'s `expect()` usage (#12901) (`18816eb`) - fix: Remove `turborepo-filewatch`'s `expect()` usage (#12903) (`d1dff11`) - fix: Remove `turborepo-cache`'s `expect()` usage (#12902) (`ccd358d`) - fix: Remove `turborepo-daemon`'s `expect()` usage (#12904) (`a9d8836`) - fix: Remove `turborepo-engine`'s `unwrap()` usage (#12906) (`5262b40`) - fix: Remove filewatch unwrap usage (#12907) (`364c801`) - fix: Remove engine expect usage (#12908) (`92ef87c`) - fix: Remove cache unwrap usage (#12909) (`c08053c`) - fix: Remove `turborepo-lockfiles` `expect()` usage (#12910) (`756ae7c`) - chore: Set pnpm minimum release age (#12912) (`1636a8c`) - fix: Remove `turborepo-lockfiles`'s `unwrap()` usage (#12911) (`40f8d8f`) - fix: Remove `turborepo-vt100`'s `unwrap()` usage (#12913) (`c7482f9`) - release(turborepo): 2.9.15-canary.4 (#12905) (`9f289d9`) - fix: Remove `turborepo-lib`'s `unwrap()` usage (#12915) (`a8ce590`) - fix: Remove `turborepo-lib`'s `expect()` usage (#12914) (`d1745a6`) - fix: Remove shim test unwrap usage (#12917) (`0d98ca3`) - fix: Remove turbo json test unwrap allowance (#12918) (`01367e9`) - fix: Remove run summary test unwrap usage (#12916) (`88745d1`) - release(turborepo): 2.9.15-canary.5 (#12919) (`b44d419`) - fix: Restore task completion semantics (#12923) (`1a71128`) - fix: Preserve nested Bun workspace dependency versions (#12924) (`a77a0e5`) - release(turborepo): 2.9.15-canary.6 (#12925) (`f675858`) - fix: Restore release PR auto-merge (#12927) (`155e672`) - perf: Index repo gitignore matchers (#12928) (`187a0fd`) - ci: Disable incremental Rust test builds (#12929) (`8c7dbc6`) - perf: Trim OpenTelemetry crate features (#12930) (`7f0afe7`) - perf: Trim microfrontends proxy HTTP features (#12931) (`ac537a8`) - fix: Accept `experimentalCI` object config (#12934) (`6f662f2`) - release(turborepo): 2.9.15-canary.7 (#12935) (`0e56cdc`) - fix: Restore a few internal invariant checks (#12933) (`767a9d4`) - fix: Improve profile tracing coverage (#12936) (`3063672`) - fix: Use build-scale OTel duration buckets (#12939) (`6ed6fb0`) - fix: Preserve pnpm injected peer package entries (#12940) (`31123f4`) - feat: Add heap allocation profiling (#12943) (`c7ad6f2`) - release(turborepo): 2.9.15-canary.8 (#12945) (`06e81ea`) - docs: Correct attribute presence claims in turborepo-otel (#12932) (`8fc94f3`) - chore(turbo-codemod): remove duplicate "in" in transforms path comment (#12948) (`5fa3039`) - chore: Switch Geist font imports to npm geist package (#12952) (`ebebf41`) - fix: Respect root gitignore during prune (#12953) (`f96ccc4`) - fix: Harden OTEL endpoint validation (#12954) (`076ff97`) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
anthonyshew
pushed a commit
that referenced
this pull request
May 27, 2026
## Release v2.9.16-canary.2 > [!CAUTION] > Versioned docs aliasing FAILED. [View logs](https://github.com/vercel/turborepo/actions/runs/26525563543) ### Changes - release(turborepo): 2.9.15-canary.7 (#12935) (`0e56cdc`) - fix: Restore a few internal invariant checks (#12933) (`767a9d4`) - fix: Improve profile tracing coverage (#12936) (`3063672`) - fix: Use build-scale OTel duration buckets (#12939) (`6ed6fb0`) - fix: Preserve pnpm injected peer package entries (#12940) (`31123f4`) - feat: Add heap allocation profiling (#12943) (`c7ad6f2`) - release(turborepo): 2.9.15-canary.8 (#12945) (`06e81ea`) - docs: Correct attribute presence claims in turborepo-otel (#12932) (`8fc94f3`) - chore(turbo-codemod): remove duplicate "in" in transforms path comment (#12948) (`5fa3039`) - chore: Switch Geist font imports to npm geist package (#12952) (`ebebf41`) - fix: Respect root gitignore during prune (#12953) (`f96ccc4`) - fix: Harden OTEL endpoint validation (#12954) (`076ff97`) - release(turborepo): 2.9.15 (#12955) (`c85d410`) - fix: Avoid hanging PTY shutdown (#12958) (`52e81bd`) - fix: Retry npm tlog publish failures (#12959) (`5317f65`) - release(turborepo): 2.9.16-canary.1 (#12960) (`2284fa9`) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.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.
Why
Experimental OTEL endpoints are user-configurable and should not allow direct targeting of internal network or cloud metadata endpoints. The existing validation already rejected insecure HTTP URLs, but HTTPS literal IPs and metadata hostnames still needed defense-in-depth coverage.
Fixes #12941.
What
Reject OTEL endpoints that use unsafe literal IP ranges or known metadata-service hostnames while preserving supported local collector workflows through the localhost hostname. Update the OTEL example and architecture docs to reflect the endpoint rules.
How
Added endpoint host validation for private, loopback, link-local, multicast, documentation, carrier-grade NAT, and metadata-service IPs, plus metadata hostnames. Added focused unit coverage and verified existing OTEL integration behavior.