[BUG] HTTP lifecycle dispatch treats a NO_PROXY-only environment as corporate egress and skips the resolve-and-pinned session

Open Beginner friendly
#3,574 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@bodapatisaikrishna is already working on this.

Since Nov 12, 2026.

  • #3575 by @bodapatisaikrishna — open

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
76/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
python
Domain
cli, networking

Research direction

Start in src/apm_cli/core/script_executors.py at _environ_proxies_for() and check how requests.utils.get_environ_proxies() and requests.utils.select_proxy() determine whether a proxy applies to the URL. The report names regression coverage in tests/unit/test_lifecycle_executor_paths.py, including TestHttpExecutor dispatch tests. Done when a NO_PROXY-only environment uses the guarded session and the tests pass, while destinations with a selected proxy retain the corporate-egress path.

Written by the indexing model from the issue text.

Description

Describe the bug

_environ_proxies_for() in src/apm_cli/core/script_executors.py decides whether an HTTP lifecycle script should route through the operator's outbound proxy ("corporate egress") or use the direct, resolve-and-pinned guarded session. It currently treats any truthy return from requests.utils.get_environ_proxies() as corporate egress, but that function returns a non-empty dict in a common case where no proxy would ever be selected:

$ NO_PROXY=127.0.0.1,localhost,::1 python -c "
import requests
print(requests.utils.get_environ_proxies('https://example.com/hook', no_proxy=None))
"
{'no': '127.0.0.1,localhost,::1'}

With NO_PROXY exported but no HTTP_PROXY/HTTPS_PROXY set — a very common environment in containers, CI runners, and macOS shells — the mapping contains only the no (no-proxy) entry. requests.utils.select_proxy() picks no proxy for any URL from such a mapping, so the request goes out directly. But _dispatch_http_request() only checks the dict's truthiness, so it takes the "corporate egress" branch and sends the request on the unpinned capturing session (or bare requests.post) instead of the resolve-and-pinned guarded session. The resolve-and-pin DNS defense that _build_guarded_session() provides is therefore silently skipped on hosts that merely export NO_PROXY.

Two concrete symptoms:

  1. Test integrity. Two unit tests in tests/unit/test_lifecycle_executor_paths.py fail on any such machine, because they patch _get_guarded_session and expect the guarded session to be used:
FAILED tests/unit/test_lifecycle_executor_paths.py::TestHttpExecutor::test_public_url_dispatches_and_logs
FAILED tests/unit/test_lifecycle_executor_paths.py::TestHttpExecutor::test_batch_dispatch_drains_all_entries

CI can't see this because GitHub-hosted runners don't export NO_PROXY, so the suite is red for external contributors with a false "main is broken" signal. On my machine the failures were reproducible at the current main (0a71750c), and unsetting NO_PROXY for the pytest process made both tests pass.

  1. Consistency of egress selection. The docstring of _environ_proxies_for says a non-empty result means "the environment mandates an outbound proxy for this destination". For the {'no': ...} mapping that claim is wrong, and the code path taken (capturing session, proxy-ACL rebind story) doesn't match the actual network behavior (direct, unpinned).

To Reproduce

  1. On a machine with NO_PROXY exported (no proxy variables set), check out current main.
  2. Run:
$ pytest tests/unit/test_lifecycle_executor_paths.py -p no:cacheprovider
  1. The two TestHttpExecutor tests above fail. Run again with NO_PROXY unset for the process and all 58 tests pass.

Alternatively, in a REPL:

import requests
se._environ_proxies_for("https://example.com/hook")  # with NO_PROXY exported
# -> {'no': '127.0.0.1,localhost,::1'}  (truthy => treated as corporate egress)

Expected behavior

_environ_proxies_for should return a non-empty dict only when a proxy would actually be selected for the destination URL — matching its documented contract and keeping direct-egress dispatch on the resolve-and-pinned guarded session. The natural check is requests.utils.select_proxy(url, raw): if it returns None, no proxy applies and the function should return {}.

Environment (please complete the following information):

  • OS: macOS 26 (reproduces on any host exporting NO_PROXY without proxy variables)
  • Python Version: 3.11.7 / 3.13
  • APM Version: current main (0a71750c), also present in 0.32.x line
  • VSCode Version: n/a

Logs

Standard pytest failure output for the two tests, ending in:

E    assert <MagicMock name='mock.post' ...>.called

The mocked guarded session never sees a .post call because dispatch took the capturing-session branch.

Additional context

Proposed fix (small, in _environ_proxies_for only): keep get_environ_proxies, then confirm with select_proxy(url, raw) that a proxy would actually be used for the URL before returning the mapping; otherwise return {}. This restores the documented direct-egress contract, keeps genuinely proxied environments on the corporate-egress path unchanged, and makes the two affected tests hermetic with respect to NO_PROXY. I have a regression test + fix ready and will link the PR here.

Dominant language
Python
Stars
4.3k
Forks
415
Avg merge
3d 3h
Merged PRs (30d)
120

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from microsoft/apm

All issues in microsoft/apm

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.