You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When Config.proxy_mounts is set, the SDK's explicit proxy configuration is merged with the ambient environment/OS proxy settings rather than replacing them — and for some patterns the environment wins.
_build_proxy_config() returns proxy=None on the proxy_mounts branch and never passes trust_env=False:
_get_proxy_map (httpx/_client.py:239-251) only short-circuits when proxy is not None. Since the proxy_mounts branch leaves proxy=None and no transport is passed, httpx calls get_environment_proxies() and seeds the client's mount table from urllib.request.getproxies() — environment variables plus, on macOS/Windows, OS system proxy settings. The SDK's mounts are then applied on top, updating only matching keys.
The SDK's keys (http://, https://) do override correctly. Anything else the environment contributes does not — and NO_PROXY contributes host-specific patterns (all://*<host>) that sort ahead of bare scheme patterns, because URLPattern.priority orders longer hostnames first.
Result: an unrelated NO_PROXY entry in the user's shell silently disables an explicitly configured proxy, for exactly the host they are most likely to care about.
There is a second, opposite-direction inconsistency from the same code. ProxyMountsConfig.model_dump(by_alias=True) emits both keys even when only one is configured, so a partial config builds a real httpx.HTTPTransport(proxy=None) for the unset scheme:
INFRAHUB_PROXY_MOUNTS_HTTP=http://only-http:8080
built mounts: {'http://': only-http:8080, 'https://': DIRECT}
That phantom direct transport shadows an ambient HTTPS_PROXY. So a partial proxy_mounts suppresses environment proxying for the scheme the user never configured, while still honouring environment NO_PROXY host rules for the one they did. Incoherent in both directions.
Config.validate_proxy_config (infrahub_sdk/config.py:219-222) already treats proxy and proxy_mounts as mutually exclusive, which shows the intent that explicit proxy configuration is authoritative. The proxy branch only behaves correctly by accident, as a side effect of httpx's proxy is not None short-circuit — not by design in this SDK.
Expected Behavior
Explicitly configured proxy settings (INFRAHUB_PROXY / INFRAHUB_PROXY_MOUNTS_*) should be authoritative. When the SDK supplies its own proxy configuration, ambient HTTP_PROXY / HTTPS_PROXY / NO_PROXY / OS system proxy settings should not be consulted or blended in.
When no SDK proxy configuration is set, the current behaviour should be preserved: environment proxies continue to be honoured.
Steps to Reproduce
Reproduced against httpx 0.28.1 using the exact mounts dict the SDK builds — no network or Infrahub server required:
mount patterns: ['all://*infrahub.internal', 'https://', 'http://']
https://infrahub.internal/graphql -> DIRECT # ambient NO_PROXY won
https://elsewhere.com/graphql -> http://corp-proxy:8080/
The user configured a proxy for HTTPS; the ambient NO_PROXY overrode it for the internal host.
Additional Information
Affected code
infrahub_sdk/client.py:1468-1478 and :2454-2464 — both _build_proxy_config() implementations
infrahub_sdk/client.py:1493, 1574, 1634 (async) and :2479, 3716, 3803 (sync) — every httpx client construction spreads that config, passing neither trust_env nor transport
infrahub_sdk/ctl/marketplace.py:300-311 — _make_http_client independently reimplements the same pattern
infrahub_sdk/config.py:18-35 — ProxyMountsConfig.model_dump(by_alias=True) always emits both keys
Suggested fix
Add a trust_env: bool key to the ProxyConfig / ProxyConfigSync TypedDicts and set it to False in both _build_proxy_config() implementations whenever config.proxy or config.proxy_mounts is set, leaving it True in the default branch. The six call sites already spread **self._build_proxy_config(), so they need no change. While there, use exclude_none=True on the model_dump so a partial proxy_mounts stops fabricating a direct transport for the unset scheme. Mirror the change in ctl/marketplace.py.
trust_env=False is safe here. Its only other effect in httpx 0.28 is reading SSL_CERT_FILE / SSL_CERT_DIR inside create_ssl_context, and the SDK always passes an already-built ssl.SSLContext via verify=self.config.tls_context, which httpx/_config.py:57 returns unchanged. No TLS behaviour changes.
This should not flip the default to trust_env=False for users with no SDK proxy configuration — that would drop HTTPS_PROXY support and is a separate, breaking change.
Testing notes
There is currently no test coverage for proxies anywhere under tests/. A regression test can be pure unit, no network. Two gotchas:
Setting only HTTPS_PROXY will not fail on current code, because the SDK's https:// mount already overrides that same key. The failing case needs a host-specific NO_PROXY entry.
ProxyMountsConfig(**{"https://": ...}) raises extra_forbidden — validation_alias="INFRAHUB_PROXY_MOUNTS_HTTPS" takes precedence over alias="https://" (config.py:24-31), so construct with the field names or the environment variables.
Separately, the same allow_env_proxies condition makes the SDK crash under fork() on macOS (Ansible forked workers): with no proxy configured, getproxies() reaches _scproxy → SystemConfiguration → ObjC in a forked child → SIGABRT. no_proxy='*' works around it only because CPython's getproxies_environment() matches any *_proxy variable name, so no_proxy short-circuits the macOS system lookup. That is a distinct issue and needs a separate, user-facing trust_env escape hatch — filing it separately.
Component
Python SDK
Infrahub SDK version
1.23.0
Current Behavior
When
Config.proxy_mountsis set, the SDK's explicit proxy configuration is merged with the ambient environment/OS proxy settings rather than replacing them — and for some patterns the environment wins._build_proxy_config()returnsproxy=Noneon theproxy_mountsbranch and never passestrust_env=False:httpx decides whether to read the environment like this:
_get_proxy_map(httpx/_client.py:239-251) only short-circuits whenproxy is not None. Since theproxy_mountsbranch leavesproxy=Noneand notransportis passed, httpx callsget_environment_proxies()and seeds the client's mount table fromurllib.request.getproxies()— environment variables plus, on macOS/Windows, OS system proxy settings. The SDK's mounts are then applied on top, updating only matching keys.The SDK's keys (
http://,https://) do override correctly. Anything else the environment contributes does not — andNO_PROXYcontributes host-specific patterns (all://*<host>) that sort ahead of bare scheme patterns, becauseURLPattern.priorityorders longer hostnames first.Result: an unrelated
NO_PROXYentry in the user's shell silently disables an explicitly configured proxy, for exactly the host they are most likely to care about.There is a second, opposite-direction inconsistency from the same code.
ProxyMountsConfig.model_dump(by_alias=True)emits both keys even when only one is configured, so a partial config builds a realhttpx.HTTPTransport(proxy=None)for the unset scheme:That phantom direct transport shadows an ambient
HTTPS_PROXY. So a partialproxy_mountssuppresses environment proxying for the scheme the user never configured, while still honouring environmentNO_PROXYhost rules for the one they did. Incoherent in both directions.Config.validate_proxy_config(infrahub_sdk/config.py:219-222) already treatsproxyandproxy_mountsas mutually exclusive, which shows the intent that explicit proxy configuration is authoritative. Theproxybranch only behaves correctly by accident, as a side effect of httpx'sproxy is not Noneshort-circuit — not by design in this SDK.Expected Behavior
Explicitly configured proxy settings (
INFRAHUB_PROXY/INFRAHUB_PROXY_MOUNTS_*) should be authoritative. When the SDK supplies its own proxy configuration, ambientHTTP_PROXY/HTTPS_PROXY/NO_PROXY/ OS system proxy settings should not be consulted or blended in.When no SDK proxy configuration is set, the current behaviour should be preserved: environment proxies continue to be honoured.
Steps to Reproduce
Reproduced against httpx 0.28.1 using the exact mounts dict the SDK builds — no network or Infrahub server required:
Output:
The user configured a proxy for HTTPS; the ambient
NO_PROXYoverrode it for the internal host.Additional Information
Affected code
infrahub_sdk/client.py:1468-1478and:2454-2464— both_build_proxy_config()implementationsinfrahub_sdk/client.py:1493, 1574, 1634(async) and:2479, 3716, 3803(sync) — every httpx client construction spreads that config, passing neithertrust_envnortransportinfrahub_sdk/ctl/marketplace.py:300-311—_make_http_clientindependently reimplements the same patterninfrahub_sdk/config.py:18-35—ProxyMountsConfig.model_dump(by_alias=True)always emits both keysSuggested fix
Add a
trust_env: boolkey to theProxyConfig/ProxyConfigSyncTypedDicts and set it toFalsein both_build_proxy_config()implementations wheneverconfig.proxyorconfig.proxy_mountsis set, leaving itTruein the default branch. The six call sites already spread**self._build_proxy_config(), so they need no change. While there, useexclude_none=Trueon themodel_dumpso a partialproxy_mountsstops fabricating a direct transport for the unset scheme. Mirror the change inctl/marketplace.py.trust_env=Falseis safe here. Its only other effect in httpx 0.28 is readingSSL_CERT_FILE/SSL_CERT_DIRinsidecreate_ssl_context, and the SDK always passes an already-builtssl.SSLContextviaverify=self.config.tls_context, whichhttpx/_config.py:57returns unchanged. No TLS behaviour changes.This should not flip the default to
trust_env=Falsefor users with no SDK proxy configuration — that would dropHTTPS_PROXYsupport and is a separate, breaking change.Testing notes
There is currently no test coverage for proxies anywhere under
tests/. A regression test can be pure unit, no network. Two gotchas:HTTPS_PROXYwill not fail on current code, because the SDK'shttps://mount already overrides that same key. The failing case needs a host-specificNO_PROXYentry.ProxyMountsConfig(**{"https://": ...})raisesextra_forbidden—validation_alias="INFRAHUB_PROXY_MOUNTS_HTTPS"takes precedence overalias="https://"(config.py:24-31), so construct with the field names or the environment variables.Related
_build_proxy_config()code path and independently documents that_request_multipartbypasses therequesterhook.allow_env_proxiescondition makes the SDK crash underfork()on macOS (Ansible forked workers): with no proxy configured,getproxies()reaches_scproxy→ SystemConfiguration → ObjC in a forked child →SIGABRT.no_proxy='*'works around it only because CPython'sgetproxies_environment()matches any*_proxyvariable name, sono_proxyshort-circuits the macOS system lookup. That is a distinct issue and needs a separate, user-facingtrust_envescape hatch — filing it separately.