Repository navigation
Conversation
Adds localstack-azure-client, a tool that runs the Azure CLI against the LocalStack for Azure emulator (never real Azure), and extends localstack-management to start, stop, restart and report the Azure emulator. - The tool runs the host's az (2.85 or newer) in an isolated CLI profile that is logged in to the emulator only. A policy refuses shell syntax, logins, profile changes, extension installs and commands that open a browser, shell or tunnel, and keeps file arguments inside the working directory and out of the user's credential folders. An egress guard rewrites absolute management.azure.com URLs and blocks every other host. Failures come back classified, with a hint. An experimental warm worker (LOCALSTACK_AZ_RUNNER=worker) keeps az imports loaded. - localstack-management: the Azure stack's container spec and port checks. A restart carries an externally started container's own settings, and refuses a container this machine cannot recreate. - Setup follows the Snowflake tool: the user installs the Azure CLI, and a missing az answers with the install commands. A new command, install-azure-addons, installs the pinned Azure CLI extensions and Bicep the tool uses. The init wizard is unchanged. The setup and the LOCALSTACK_AZ_* settings are documented in README.md and docs/DOCKER.md. - The Docker image bundles az 2.90, the 26 pinned extensions and Bicep, with image assertions and a size gate. - Tests: unit tests with an Azure coverage gate (90 % of lines), a live command matrix, the official Azure samples replayed through the tool, and model evals. New CI workflows: azure-live.yml and azure-weekly.yml. - CODEOWNERS requests @localstack/smurf and @HarshCasper on the Azure-only paths. .gitattributes keeps shell scripts LF and .cmd files CRLF on every checkout.
Comments, docs and fixtures state facts without citing internal plans, reviews, checks, test runs or benchmarks. build-corpus.py now builds the samples corpus from the in-repo extract.py and bash_argv.py output (the same 736 cases), and scripts/extract-leak-commands.mjs is removed: its input was never in the repository. tests/azure/README.md defines the test layers the CI jobs name.
The README's Azure section keeps its notes on using the emulator with the other tools. The E2 evals describe their builders, variants and seven answer-reading pitfalls on their own terms, and recorded fixtures use neutral resource names.
…he token Removes the weekly E2 job, the only one that needed an Anthropic API key. The emulator start stops as soon as the container exits and prints the licence reason. The ~/.azure fingerprint is taken before the emulator starts, the live path filter covers core, cli and the Azure fixtures, the weekly token is set per step, and the matrix YAML is pinned to LF.
File paths are checked as the OS opens them, analytics drop values stuck to short options, and the egress guard allows only the emulator's ports. The AWS client runs in the Snowflake container, stop and restart handle the Azure emulator beside an AWS one, and the MCP env block wins on restart. A venv Python in LOCALSTACK_AZ_PATH is run by its own path, and a Windows az behind any WSL mount is refused. Also fixes the warm worker's log level and cancel, LOCALSTACK_HOSTNAME and port hints, the tool description, server.json and fixture redaction, and removes internal references.
…cret The Azure jobs and steps take their token from LOCALSTACK_AUTH_TOKEN_AZURE, else LOCALSTACK_AUTH_TOKEN. The smoke test runs its AWS and Snowflake stages with LOCALSTACK_AUTH_TOKEN and the Azure stage on its own with the Azure token. Also repairs four comments that an earlier cleanup left with stray punctuation.
… work The Azure emulator shares files with the containers it starts for Function and Web Apps from /var/lib/localstack, and refuses to unless that folder is a bind mount. The CI start script and the internal-network job now bind-mount a runner folder there. Starting the Azure emulator on a named volume, as when this server runs in Docker, now says that app deployments need LOCALSTACK_VOLUME_DIR, and the Docker docs and README say so too.
One token whose licence covers AWS, Snowflake and Azure, with no fallback, so a gap in its licence fails the run instead of hiding behind LOCALSTACK_AUTH_TOKEN.
Its licence covers AWS, Snowflake and Azure, so the separate LOCALSTACK_AUTH_TOKEN_AZURE secret is gone.
|
Why #86 fails now when the same code passed on 29 Sep: Nothing in our code changed. #86 runs the same commit as #79's last green run (1f12bf3), and main after the revert is byte-for-byte what it was before #79. Two things outside the repo changed:
Everything else passes, including all the Azure live tests and the AWS and Snowflake scenarios. |
audit-ci --high now fails on this advisory, published on 30 Sep. It covers 1.14.0 to 1.14.4, which dockerode pulled in. Lockfile only.
localstack-docs calls an external search service. When that service times out, refuses, or returns a 5xx, the direct test is skipped and the image harness warns, each with the tool's answer. A 4xx or any other wrong answer still fails.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The review found a test parse failure, analytics data-exposure risk, and an unbounded warm-worker output path.
Review effort: Balanced
Findings: 2
Open (2)
What changed in this PR
Adds Azure emulator tooling, lifecycle management, containment, installation helpers, and extensive multi-layer validation.
Changes:
- Adds
localstack-azure-clientand Azure-aware management/preflight behavior. - Adds isolated Azure CLI/Bicep execution with policy and egress controls.
- Expands unit, live, drift, Docker, sample, and evaluation coverage.
| File | Description |
|---|---|
src/lib/azure/* |
Azure client runtime, policy, isolation, installation, and tests |
src/tools/localstack-*.ts |
Stack-aware preflight checks for existing tools |
src/cli/* |
Azure add-ons command and help |
src/lib/wizard/* |
Azure extension/Bicep setup helpers and tests |
src/lib/cli/argv.ts |
Shared CLI tokenizer |
src/lib/aws/aws-cli-sanitizer.ts |
Uses shared tokenizer |
src/core/analytics.ts |
Azure analytics fields |
src/tools-tests/localstack-management-ports.test.ts |
Port-safe lifecycle tests |
tests/azure/** |
Live matrix, drift, samples, evals, and utilities |
tests/fixtures/azure/** |
Azure CLI, worker, corpus, Bicep, and drift fixtures |
tests/mcp/** |
Azure MCP catalogue, offline, and Gemini tests |
tests/docker/** |
Image contents and size gates |
scripts/ci/** |
Azure CI startup, catalogue merge, and secret scanning |
data/sample-azure/** |
Azure harness sample resources |
data/evals/gemini-azure.json |
Azure tool-trigger dataset |
.github/workflows/ci.yml |
Cross-platform tests, coverage, and spawn smoke tests |
.github/actions/azure-live-setup/action.yml |
Reusable Azure live-test setup |
.github/CODEOWNERS |
Azure path ownership |
server.json |
Azure environment configuration |
manifest.json |
Azure tool and prompt registration |
package.json |
Azure test scripts and development dependencies |
playwright.config.mjs |
Isolated offline Azure MCP project |
jest.config.js |
Azure coverage and test TypeScript configuration |
jest.azure-live.config.js |
Azure live-test projects |
tsconfig.tests.json |
Test-code type checking |
docker/azure-extensions.txt |
Pinned Azure CLI extensions |
docker/image-size.json |
Recorded image-size baseline |
.gitattributes |
Cross-platform line-ending rules |
.gitignore |
Generated Azure test artifact exclusions |
.prettierignore |
Generated Azure fixture exclusions |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
command_path took every leading word-shaped token, so a positional value (az find <term>) or an unknown command could reach analytics. It now keeps a word only if az's command table has it. scripts/gen-az-file-args.py --command-words generates the list (az 2.90.0 with the 26 curated extensions: 7,048 commands, 1,592 words), and DR4 checks it on every pin move. The property test now also tries secrets passed as positional values.

Motivation
This server covers LocalStack's AWS and Snowflake emulators. This PR adds the LocalStack for
Azure emulator:
localstack-azure-client, runs Azure CLI (az) commands against the LocalStackfor Azure emulator, never real Azure. Whatever
azcan do and the emulator implements works,with
az's own validation and--help; for an operationazhas no command for,restwith arelative URL reaches the emulator.
localstack-managementmanages the Azure emulator withservice: "azure": start, stop,restart and status.
Why the Azure CLI:
azalready covers every Azure API, so new emulator coverage reaches userswithout a code change here, and one tool keeps the catalogue small.
Why the user's own
az, the way the Snowflake tool usessnow: the Azure emulator image shipsno client CLI (no
az, Bicep orazlocal), unlike the AWS image, whoseawslocalthe AWS tool runsinside the container. So the tool runs the
azon the user's machine, in an isolated profile, andsays how to install it when it is missing. The Docker image bundles
az, the extensions and Bicep.Changes
How it fits together
%%{init: {"theme":"base","flowchart":{"wrappingWidth":370},"themeVariables":{"fontFamily":"Segoe UI, Helvetica, Arial, sans-serif","fontSize":"14px","primaryColor":"#eef3fb","primaryTextColor":"#1a1a1a","primaryBorderColor":"#5d7aa8","lineColor":"#5f6b7a","secondaryColor":"#f4f6f8","tertiaryColor":"#ffffff","clusterBkg":"#f7f9fc","clusterBorder":"#9aa6b6","titleColor":"#1a1a1a","edgeLabelBackground":"#ffffff","textColor":"#1a1a1a"},"themeCSS":"background-color:#ffffff;"}}%% flowchart TB subgraph canvas[" "] direction TB client["MCP client<br/>Claude Code, Cursor, VS Code, ..."] subgraph server["LocalStack MCP server (npx, or the Docker image)"] direction TB azure["localstack-azure-client<br/>one az command per call"] mgmt["localstack-management<br/>service: azure"] policy["Command policy<br/>refuses shell syntax, logins, config changes,<br/>extension installs, and browser, shell or tunnel commands;<br/>keeps files in the workdir; makes ARM URLs relative"] runner["Runner<br/>own az profile: ~/.localstack/azure/mcp-config-{port}<br/>allow-list environment, private home and temp"] guard["Egress guard (in-process proxy)<br/>allows only the emulator's host names and ports"] output["Answer<br/>az's output, or its error with a class and a hint"] end subgraph host["Same host (bundled in the Docker image)"] az["Azure CLI (az 2.85+)<br/>26 pinned extensions and Bicep<br/>installed by install-azure-addons"] end subgraph docker["Docker"] emulator["LocalStack for Azure emulator<br/>localhost.localstack.cloud:4566"] end real["Real Azure<br/>management.azure.com"] end client -- "MCP (stdio)" --> server azure --> policy --> runner runner -- "spawns" --> az az -- "HTTPS_PROXY" --> guard guard -- "allowed" --> emulator guard -- "refused" --x real runner --> output mgmt -- "Docker Engine API" --> emulator style canvas fill:#ffffff,stroke:#d0d7de,color:#1a1a1a classDef tool fill:#e7efff,stroke:#4a6fa5,color:#1a1a1a; classDef step fill:#ffffff,stroke:#8a96a8,color:#1a1a1a; classDef cli fill:#fff4dc,stroke:#b7862a,color:#1a1a1a; classDef emu fill:#e3f5e8,stroke:#3f8f5a,color:#1a1a1a; classDef blocked fill:#fde8e8,stroke:#c0504d,color:#1a1a1a; classDef muted fill:#f2f2f2,stroke:#a0a0a0,color:#1a1a1a; class azure,mgmt tool; class policy,runner,guard,output step; class az cli; class emulator emu; class real blocked; class client muted;localstack-azure-clientchecks each command against its policy, then runs the Azure CLI on thesame host in the tool's own profile. Every connection
azmakes goes through the egress guard,which lets it reach only the emulator.
localstack-managementstarts, stops and restarts theemulator's container through the Docker Engine API.
The Azure tool:
localstack-azure-clientOne input,
command: anazcommand without the leadingaz.az(src/lib/azure/resolve-az.ts): MSI, pip, Homebrew and deb installs, 2.85 ornewer. It spawns the CLI's own Python (
-X utf8 -IBm azure.cli), never through a shell or a.cmdlauncher.LOCALSTACK_AZ_PATHoverrides the lookup.bootstrap.ts): the tool's ownAZURE_CONFIG_DIR(
~/.localstack/azure/mcp-config-<port>) with aLocalStackcloud and a dummy login, plus aprivate home and temp folder. The user's own
azlogin is never used or changed.src/lib/azure/):policy.tsrefuses shell syntax, logins, cloud and config changes, extension installs,upgrade, and commands that open a browser, shell or tunnel or run Docker. File argumentsmust stay inside
LOCALSTACK_AZ_WORKDIR, and never reach~/.azure,~/.ssh,~/.kubeor~/.docker, checked as the OS will open them (through symlinks, junctions and 8.3 names).child-env.tsgivesazan allow-list environment: the user'sAZURE_*,ARM_*, proxy andCA variables never reach it.
egress-proxy.tsroutes every connectionazor Bicep makes through a local proxy that allowsonly the emulator's host names and ports (its gateway, 443 and its service range), so other
local services, such as Docker's API, stay out of reach. Absolute
management.azure.comURLsgiven to
restare rewritten to relative ones.output.ts):az's own error text, a failure class in thefirst line (
not-implemented,extension,bicep-missing,egress-refused, …) and a hint..bicepand.bicepparamdeployments work (LOCALSTACK_AZ_BICEP_PATH, then thetool's own folder, then
PATH).LOCALSTACK_AZ_RUNNER=worker).Setup, the Snowflake way
initwizard is unchanged.npx -y @localstack/localstack-mcp-server install-azure-addonsinstalls the 26 pinnedazextensions and Bicep 0.47.16 (sha256-checked) into the tool's own folders, never into
~/.azure(--no-extensions,--no-bicep).azanswers with install commands for Windows, macOS and Debian/Ubuntu, as a missingsnowdoes; a missing extension or Bicep namesinstall-azure-addons.LOCALSTACK_AZ_*settings, and "How the Azure toolstays local".
Shared code (AWS and Snowflake run through it)
requireStack,src/core/preflight.ts): atool that meets another stack's emulator refuses and names the right tool. The stack comes from
the health
edition, then the container's image and labels; unknown passes (fail-open). TheAWS-only tools refuse on the Azure emulator.
localstack-management:service: "azure";statusnames the container it found;restartrefuses, before stopping anything, a container whose state folder this machine cannotmount;
restartof an externally started container (lstk,docker run) keeps thatcontainer's own env flags, for AWS and Snowflake too, unless this server's
envblock setsthem. A container this server started behaves as before. With the Azure emulator beside an AWS
one (
LOCALSTACK_AZURE_PORTset),statusandstopact on the Azure container, andrestartrefuses before stopping anything. A start forwards the Azure emulator's own settings
(
LS_AZURE_*,MSSQL_ACCEPT_EULA, …) from theenvblock.lstk's names; withLOCALSTACK_PORTset ittakes only a container that publishes that port, or the one known container that publishes none
(host or compose networking). The AWS client also runs in the Snowflake emulator's container,
which serves the AWS APIs.
src/lib/cli/argv.ts, shared by both clients; the AWS clientcalls it without options, so its behaviour is unchanged.
@anthropic-ai/sdkandyamlare dev dependencies).Docker image
az2.90 in its own venv, the 26 pinned extensions and Bicep 0.47.16 (ADD --checksum).Compressed: 418 MB, against 245 MB without them.
tests/docker/image-size.mjsgates the growth.Run from the image, the server keeps the emulator's state in a named volume by default. The Azure
emulator runs Function and Web Apps in containers of their own and shares their files from its
state folder, which it can do only from a host folder: to deploy them, set
LOCALSTACK_VOLUME_DIR.localstack-management start(serviceazure) says so when it starts the emulator on a namedvolume.
CI
ci.yml: unit tests on Ubuntu, Windows and macOS with a 90 % line-coverage gate on the Azurecode, type-checks of the test code, prettier on the changed files, and an
azspawn smoke test.docker.yml: image assertions (amd64 and arm64), the size gate, and the image against realemulators: AWS and Snowflake, then the Azure stage in a run of its own.
azure-live.yml(new; path-filtered PRs andmain): a real LocalStack for Azure emulator perjob, with its state folder bind-mounted (Function App deployments need it); a subset of the
command matrix, the egress checks, the harness's Azure stage and three official samples.
LOCALSTACK_AUTH_TOKENsecret, so its licence must cover AWS, Snowflakeand Azure. An emulator that exits (such as on a licence failure) ends the job at once with its
reason.
azure-weekly.yml(new; Mondays): the full matrix, egress on an internal network, all officialsamples, drift gates, and
az2.85 and the latest. No job calls a model API: the model evalsrun locally only.
azure-live.ymlonmain, opens or updates a tracking issuethat mentions @localstack/smurf and @HarshCasper and is assigned to Harsh.
.github/CODEOWNERS(new): @localstack/smurf and @HarshCasper on the Azure-only paths..gitattributes(new): shell scripts and the matrix YAML stay LF,.cmdfiles CRLF.After the first review
for files that do not exist yet, and for a
..after a link.-pSecret).LOCALSTACK_PORTset, acontainer that publishes no port is still found.
stop(serviceazure) acts on the Azure container, andrestartrefuses before stopping anything. On restart, this server's
envblock wins over the oldcontainer's flags. A start forwards the Azure emulator's
LS_AZURE_*settings.LOCALSTACK_AZ_PATHruns by its own path. A Windowsazbehind any WSL mountroot is refused.
LOCALSTACK_HOSTNAMEis tried for the emulator; the hints nameLOCALSTACK_PORT.install-azure-addons --no-extensionsworks withoutaz;server.jsonlistsLOCALSTACK_AZ_BICEP_ENV; the tool description no longer claims a default location.that need it; the live path filter covers
src/core,src/cliand the Azure fixtures.Tests
link (as macOS's
/varand Windows 8.3 paths are); line coverage of the Azure code 92.8 %(gate: 90 %).
tests/azure/README.mdlists the layers):the command matrix (393 cases), the egress checks, the official samples through an
azshim, theMCP harness through npx and the image, the image assertions, and drift gates. The PR subset runs
in this PR's
azure-live.yml; the rest weekly.live(the matrix subset, the egress checks and theharness through npx) and the samples subset against the emulator,
docker.yml's smoke test(AWS, Snowflake and the Azure stage), and its L5 jobs on arm64 and against a 127.0.0.1-published
emulator.
Related
localstack/localstack-azure-samples,pinned at 5ae6984.