@@ -47,10 +47,88 @@ typedef uint32_t group_id_t;
4747
4848#define GROUP_ID_PROCESSED (1UL << 31UL)
4949
50+ /* Snapshot of proxy-related environment variables taken during MINIT.
51+ * These are later applied as explicit libcurl options on the Datadog
52+ * writer handle so that libcurl does not call curl_getenv() from
53+ * background threads.
54+ *
55+ * Lifetime: allocated once per module load and freed during MSHUTDOWN
56+ * via ddtrace_coms_mshutdown_proxy_env() after all writer threads and
57+ * curl handles have been torn down.
58+ */
59+ static char * ddtrace_env_no_proxy = NULL ; /* no_proxy / NO_PROXY */
60+ static char * ddtrace_env_http_proxy = NULL ; /* http_proxy / HTTP_PROXY */
61+ static char * ddtrace_env_https_proxy = NULL ; /* https_proxy / HTTPS_PROXY */
62+ static char * ddtrace_env_all_proxy = NULL ; /* all_proxy / ALL_PROXY */
63+
5064ddtrace_coms_state_t ddtrace_coms_globals = {.stacks = NULL };
5165static struct ddog_AgentRemoteConfigWriter_ShmHandle * dd_agent_config_writer ;
5266struct ddog_ShmHandle * ddtrace_coms_agent_config_handle ;
5367
68+ static void ddtrace_store_env (char * * target , const char * value ) {
69+ if (!* target && value && value [0 ]) {
70+ * target = strdup (value );
71+ }
72+ }
73+
74+ /* Snapshot the proxy-related environment variables curl itself consults.
75+ *
76+ * Generic pattern in libcurl (see url.c):
77+ * - For scheme-specific proxies, it looks up "<scheme>_proxy" in lowercase
78+ * first and, for most schemes, falls back to the uppercase variant
79+ * (e.g., "https_proxy" then "HTTPS_PROXY"). If no scheme-specific proxy
80+ * is found, it falls back to "all_proxy"/"ALL_PROXY".
81+ * - For the no-proxy list, it consults both "no_proxy" and "NO_PROXY".
82+ *
83+ * There is a special case in libcurl for HTTP: to avoid header-based
84+ * injection in CGI/server contexts, it does *not* automatically fall back
85+ * from "http_proxy" to "HTTP_PROXY". We still snapshot both here, but when
86+ * we later configure our agent handle we only ever use the cached value
87+ * explicitly, not libcurl's own HTTP_PROXY fallback.
88+ */
89+ void ddtrace_coms_minit_proxy_env (void ) {
90+ const char * v ;
91+
92+ /* no_proxy / NO_PROXY */
93+ v = getenv ("no_proxy" );
94+ if (!v || !v [0 ]) {
95+ v = getenv ("NO_PROXY" );
96+ }
97+ ddtrace_store_env (& ddtrace_env_no_proxy , v );
98+
99+ /* http_proxy / HTTP_PROXY */
100+ v = getenv ("http_proxy" );
101+ if (!v || !v [0 ]) {
102+ v = getenv ("HTTP_PROXY" );
103+ }
104+ ddtrace_store_env (& ddtrace_env_http_proxy , v );
105+
106+ /* https_proxy / HTTPS_PROXY */
107+ v = getenv ("https_proxy" );
108+ if (!v || !v [0 ]) {
109+ v = getenv ("HTTPS_PROXY" );
110+ }
111+ ddtrace_store_env (& ddtrace_env_https_proxy , v );
112+
113+ /* all_proxy / ALL_PROXY */
114+ v = getenv ("all_proxy" );
115+ if (!v || !v [0 ]) {
116+ v = getenv ("ALL_PROXY" );
117+ }
118+ ddtrace_store_env (& ddtrace_env_all_proxy , v );
119+ }
120+
121+ void ddtrace_coms_mshutdown_proxy_env (void ) {
122+ free (ddtrace_env_no_proxy );
123+ ddtrace_env_no_proxy = NULL ;
124+ free (ddtrace_env_http_proxy );
125+ ddtrace_env_http_proxy = NULL ;
126+ free (ddtrace_env_https_proxy );
127+ ddtrace_env_https_proxy = NULL ;
128+ free (ddtrace_env_all_proxy );
129+ ddtrace_env_all_proxy = NULL ;
130+ }
131+
54132static bool _dd_is_memory_pressure_high (void ) {
55133 ddtrace_coms_stack_t * stack = atomic_load (& ddtrace_coms_globals .current_stack );
56134 if (stack ) {
@@ -746,7 +824,46 @@ void ddtrace_curl_set_connect_timeout(CURL *curl) {
746824
747825static void ddtrace_curl_set_hostname_generic (CURL * curl , const char * path ) {
748826 char * url = ddtrace_agent_url ();
827+
828+ /* Prevent libcurl from reading no_proxy/NO_PROXY in worker threads:
829+ * always set CURLOPT_NOPROXY on this handle. If the env had a value
830+ * we cached it and reuse it; otherwise we set it to the empty string,
831+ * which means "no entries", but still suppresses curl_getenv().
832+ */
833+ if (ddtrace_env_no_proxy ) {
834+ curl_easy_setopt (curl , CURLOPT_NOPROXY , ddtrace_env_no_proxy );
835+ } else {
836+ curl_easy_setopt (curl , CURLOPT_NOPROXY , "" );
837+ }
838+
749839 if (url && url [0 ]) {
840+ const char * proxy = NULL ;
841+
842+ /* Choose a proxy based on the agent URL scheme, using the cached
843+ * env values. This mirrors curl's detect_proxy() precedence:
844+ * - scheme-specific proxy if present
845+ * - otherwise all_proxy / ALL_PROXY
846+ *
847+ * This happens without calling getenv() in the worker thread.
848+ */
849+ if (strncmp (url , "http://" , 7 ) == 0 ) {
850+ if (ddtrace_env_http_proxy ) {
851+ proxy = ddtrace_env_http_proxy ;
852+ } else if (ddtrace_env_all_proxy ) {
853+ proxy = ddtrace_env_all_proxy ;
854+ }
855+ } else if (strncmp (url , "https://" , 8 ) == 0 ) {
856+ if (ddtrace_env_https_proxy ) {
857+ proxy = ddtrace_env_https_proxy ;
858+ } else if (ddtrace_env_all_proxy ) {
859+ proxy = ddtrace_env_all_proxy ;
860+ }
861+ }
862+
863+ if (proxy ) {
864+ curl_easy_setopt (curl , CURLOPT_PROXY , proxy );
865+ }
866+
750867 char * http_url = url ;
751868 if (strlen (url ) > 7 && strncmp (url , "unix://" , 7 ) == 0 ) {
752869 curl_easy_setopt (curl , CURLOPT_UNIX_SOCKET_PATH , url + 7 );
0 commit comments