Skip to content

feat(azure): add the localstack-azure-client tool and Azure lifecycle support - #79

Merged
DrisDary merged 9 commits into
mainfrom
feat/azure-client
Sep 30, 2026
Merged

DrisDary merged 9 commits into
mainfrom
feat/azure-client

Conversation

@DrisDary

@DrisDary DrisDary commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

This server covers LocalStack's AWS and Snowflake emulators. This PR adds the LocalStack for
Azure emulator:

  • A new tool, localstack-azure-client, runs Azure CLI (az) commands against the LocalStack
    for Azure emulator, never real Azure. Whatever az can do and the emulator implements works,
    with az's own validation and --help; for an operation az has no command for, rest with a
    relative URL reaches the emulator.
  • localstack-management manages the Azure emulator with service: "azure": start, stop,
    restart and status.

Why the Azure CLI: az already covers every Azure API, so new emulator coverage reaches users
without a code change here, and one tool keeps the catalogue small.

Why the user's own az, the way the Snowflake tool uses snow: the Azure emulator image ships
no client CLI (no az, Bicep or azlocal), unlike the AWS image, whose awslocal the AWS tool runs
inside the container. So the tool runs the az on the user's machine, in an isolated profile, and
says 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;
Loading

localstack-azure-client checks each command against its policy, then runs the Azure CLI on the
same host in the tool's own profile. Every connection az makes goes through the egress guard,
which lets it reach only the emulator. localstack-management starts, stops and restarts the
emulator's container through the Docker Engine API.

The Azure tool: localstack-azure-client

One input, command: an az command without the leading az.

  • Finding az (src/lib/azure/resolve-az.ts): MSI, pip, Homebrew and deb installs, 2.85 or
    newer. It spawns the CLI's own Python (-X utf8 -IBm azure.cli), never through a shell or a
    .cmd launcher. LOCALSTACK_AZ_PATH overrides the lookup.
  • An isolated CLI profile (bootstrap.ts): the tool's own AZURE_CONFIG_DIR
    (~/.localstack/azure/mcp-config-<port>) with a LocalStack cloud and a dummy login, plus a
    private home and temp folder. The user's own az login is never used or changed.
  • Containment (src/lib/azure/):
    • policy.ts refuses shell syntax, logins, cloud and config changes, extension installs,
      upgrade, and commands that open a browser, shell or tunnel or run Docker. File arguments
      must stay inside LOCALSTACK_AZ_WORKDIR, and never reach ~/.azure, ~/.ssh, ~/.kube or
      ~/.docker, checked as the OS will open them (through symlinks, junctions and 8.3 names).
    • child-env.ts gives az an allow-list environment: the user's AZURE_*, ARM_*, proxy and
      CA variables never reach it.
    • egress-proxy.ts routes every connection az or Bicep makes through a local proxy that allows
      only 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.com URLs
      given to rest are rewritten to relative ones.
  • Answers an agent can act on (output.ts): az's own error text, a failure class in the
    first line (not-implemented, extension, bicep-missing, egress-refused, …) and a hint.
  • Bicep: .bicep and .bicepparam deployments work (LOCALSTACK_AZ_BICEP_PATH, then the
    tool's own folder, then PATH).
  • A warm worker, experimental and off by default (LOCALSTACK_AZ_RUNNER=worker).

Setup, the Snowflake way

  • The init wizard is unchanged.
  • npx -y @localstack/localstack-mcp-server install-azure-addons installs the 26 pinned az
    extensions and Bicep 0.47.16 (sha256-checked) into the tool's own folders, never into
    ~/.azure (--no-extensions, --no-bicep).
  • A missing az answers with install commands for Windows, macOS and Debian/Ubuntu, as a missing
    snow does; a missing extension or Bicep names install-azure-addons.
  • README: "Setting up the Azure tool", the LOCALSTACK_AZ_* settings, and "How the Azure tool
    stays local".

Shared code (AWS and Snowflake run through it)

  • A stack check before every stack-specific tool (requireStack, src/core/preflight.ts): a
    tool 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). The
    AWS-only tools refuse on the Azure emulator.
  • localstack-management: service: "azure"; status names the container it found;
    restart refuses, before stopping anything, a container whose state folder this machine cannot
    mount; restart of an externally started container (lstk, docker run) keeps that
    container's own env flags, for AWS and Snowflake too, unless this server's env block sets
    them. A container this server started behaves as before. With the Azure emulator beside an AWS
    one (LOCALSTACK_AZURE_PORT set), status and stop act on the Azure container, and restart
    refuses before stopping anything. A start forwards the Azure emulator's own settings
    (LS_AZURE_*, MSSQL_ACCEPT_EULA, …) from the env block.
  • The container lookup knows the Azure image and lstk's names; with LOCALSTACK_PORT set it
    takes 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.
  • The AWS CLI tokenizer moved to src/lib/cli/argv.ts, shared by both clients; the AWS client
    calls it without options, so its behaviour is unchanged.
  • No new runtime dependencies (@anthropic-ai/sdk and yaml are dev dependencies).

Docker image

az 2.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.mjs gates 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 (service azure) says so when it starts the emulator on a named
volume.

CI

  • ci.yml: unit tests on Ubuntu, Windows and macOS with a 90 % line-coverage gate on the Azure
    code, type-checks of the test code, prettier on the changed files, and an az spawn smoke test.
  • docker.yml: image assertions (amd64 and arm64), the size gate, and the image against real
    emulators: AWS and Snowflake, then the Azure stage in a run of its own.
  • azure-live.yml (new; path-filtered PRs and main): a real LocalStack for Azure emulator per
    job, 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.
  • Every job uses the one LOCALSTACK_AUTH_TOKEN secret, so its licence must cover AWS, Snowflake
    and 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 official
    samples, drift gates, and az 2.85 and the latest. No job calls a model API: the model evals
    run locally only.
  • A failed weekly run, or a failed azure-live.yml on main, opens or updates a tracking issue
    that 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, .cmd files CRLF.

After the first review

  • The file rule checks paths as the OS opens them: through symlinks, junctions and 8.3 names,
    for files that do not exist yet, and for a .. after a link.
  • Analytics keep only flag names, also when a value is stuck to a short option (-pSecret).
  • The egress guard and the URL rule allow only the emulator's ports.
  • The AWS client runs in the Snowflake emulator's container. With LOCALSTACK_PORT set, a
    container that publishes no port is still found.
  • Beside an AWS emulator, stop (service azure) acts on the Azure container, and restart
    refuses before stopping anything. On restart, this server's env block wins over the old
    container's flags. A start forwards the Azure emulator's LS_AZURE_* settings.
  • A venv's Python in LOCALSTACK_AZ_PATH runs by its own path. A Windows az behind any WSL mount
    root is refused.
  • The warm worker resets logging for each command and honours a cancel during its start.
  • In Docker, LOCALSTACK_HOSTNAME is tried for the emulator; the hints name LOCALSTACK_PORT.
  • install-azure-addons --no-extensions works without az; server.json lists
    LOCALSTACK_AZ_BICEP_ENV; the tool description no longer claims a default location.
  • CI: no job needs an Anthropic API key; the weekly workflow sets the auth token only on the steps
    that need it; the live path filter covers src/core, src/cli and the Azure fixtures.

Tests

  • Unit: 1,639 tests, none failing on Windows or Linux, also with the temp directory behind a
    link (as macOS's /var and Windows 8.3 paths are); line coverage of the Azure code 92.8 %
    (gate: 90 %).
  • Live, against a LocalStack for Azure emulator (tests/azure/README.md lists the layers):
    the command matrix (393 cases), the egress checks, the official samples through an az shim, the
    MCP 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.
  • On this PR's CI: every job passes: live (the matrix subset, the egress checks and the
    harness 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

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.
@DrisDary DrisDary self-assigned this Sep 29, 2026
@DrisDary DrisDary added the enhancement New feature or request label Sep 29, 2026
@paolosalvatori
paolosalvatori requested a balanced review from Copilot September 29, 2026 10:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Stack detection, Bicep-only installation, worker output limits, fixture redaction, and empty-evaluation handling have unresolved correctness or security issues.

Review effort: Balanced
Findings: 2 High severity · 2 Medium severity

Open (4)
What changed in this PR

Adds first-class LocalStack Azure support, including Azure CLI execution, emulator lifecycle management, containment controls, installation tooling, Docker packaging, and extensive test infrastructure.

Changes:

  • Adds localstack-azure-client with isolated profiles, egress controls, Bicep, extensions, and optional workers.
  • Extends lifecycle and stack detection across Azure, AWS, and Snowflake.
  • Adds comprehensive unit, live, matrix, eval, drift, sample, and Docker-image coverage.
File Description
.gitattributes Enforces script line endings.
.github/​CODEOWNERS Assigns Azure ownership.
.github/​actions/​azure-live-setup/​action.yml Sets up Azure CI dependencies.
.github/​workflows/​azure-live.yml Runs PR Azure integration tests.
.github/​workflows/​azure-weekly.yml Runs full weekly Azure tests.
.github/​workflows/​ci.yml Expands cross-platform CI.
.github/​workflows/​docker.yml Adds Azure image validation.
.gitignore Ignores generated test artifacts.
.prettierignore Excludes generated Azure fixtures.
Dockerfile Bundles Azure CLI and Bicep.
README.md Documents Azure setup and containment.
data/​evals/​gemini-azure.json Defines Azure Gemini evaluations.
data/​sample-azure/​hello.txt Adds harness sample data.
data/​sample-azure/​main.bicep Adds sample Azure deployment.
data/​sample-azure/​main.bicepparam Adds Bicep parameters.
docker/​azure-extensions.txt Pins Azure CLI extensions.
docker/​image-size.json Records image-size baseline.
docs/​DOCKER.md Documents Docker Azure behavior.
jest.azure-live.config.js Configures live Azure suites.
jest.config.js Adds test typing and exclusions.
manifest.json Registers the Azure tool.
package.json Adds scripts and test dependencies.
playwright.config.mjs Adds offline Azure MCP tests.
scripts/​ci/​azure-emulator-up.sh Starts CI Azure emulator.
scripts/​ci/​merge-op-catalogue.cjs Merges matrix results.
scripts/​ci/​scan-for-secret.mjs Scans artifacts for secrets.
scripts/​gen-az-file-args.py Generates Azure file-argument metadata.
scripts/​install-azure-extensions.mjs Installs pinned extensions.
server.json Exposes Azure configuration.
src/​cli/​azure-addons.test.ts Tests add-on installation.
src/​cli/​azure-addons.ts Implements add-on command.
src/​cli/​help.test.ts Tests updated CLI help.
src/​cli/​help.ts Documents add-on command.
src/​cli/​index.ts Registers add-on subcommand.
src/​core/​analytics.ts Adds safe Azure analytics fields.
src/​core/​config.test.ts Tests Azure configuration.
src/​core/​config.ts Parses Azure settings.
src/​core/​preflight.test.ts Tests stack detection.
src/​core/​preflight.ts Adds stack-aware preflights.
src/​lib/​aws/​aws-cli-sanitizer.ts Shares the CLI tokenizer.
src/​lib/​azure/​az-file-args.generated.json Lists generated file flags.
src/​lib/​azure/​bicep-install.test.ts Tests Bicep installation.
src/​lib/​azure/​bicep-install.ts Downloads verified Bicep binaries.
src/​lib/​azure/​bootstrap.test.ts Tests isolated Azure bootstrap.
src/​lib/​azure/​bootstrap.ts Creates the LocalStack Azure profile.
src/​lib/​azure/​child-env.test.ts Tests environment isolation.
src/​lib/​azure/​child-env.ts Builds allow-listed child environments.
src/​lib/​azure/​description.test.ts Tests tool description limits.
src/​lib/​azure/​description.ts Defines Azure tool guidance.
src/​lib/​azure/​egress-proxy.test.ts Tests egress containment.
src/​lib/​azure/​egress-proxy.ts Proxies and restricts network access.
src/​lib/​azure/​emulator.test.ts Tests emulator health checks.
src/​lib/​azure/​emulator.ts Implements emulator detection.
src/​lib/​azure/​extension-install.test.ts Tests extension installation.
src/​lib/​azure/​extension-install.ts Installs isolated extensions.
src/​lib/​azure/​extension-map.test.ts Tests extension mappings.
src/​lib/​azure/​extension-map.ts Maps commands to extensions.
src/​lib/​azure/​extension-pins.test.ts Verifies embedded pins.
src/​lib/​azure/​extension-pins.ts Embeds extension versions.
src/​lib/​azure/​local-hosts.ts Centralizes allowed local hosts.
src/​lib/​azure/​loopback-forwarder.test.ts Tests Docker forwarding.
src/​lib/​azure/​loopback-forwarder.ts Forwards Docker loopback traffic.
src/​lib/​azure/​output.test.ts Tests Azure result formatting.
src/​lib/​azure/​output.ts Classifies and formats CLI output.
src/​lib/​azure/​policy.corpus.test.ts Validates the sample corpus.
src/​lib/​azure/​policy.leak.test.ts Tests policy leak resistance.
src/​lib/​azure/​policy.test.ts Tests command policy.
src/​lib/​azure/​policy.ts Enforces Azure command containment.
src/​lib/​azure/​resolve-az.test.ts Tests Azure CLI discovery.
src/​lib/​azure/​resolve-az.ts Resolves supported Azure CLI installs.
src/​lib/​azure/​runner.test.ts Tests subprocess execution.
src/​lib/​azure/​runner.ts Runs bounded Azure CLI processes.
src/​lib/​azure/​runtime-status.test.ts Tests lifecycle status mapping.
src/​lib/​azure/​runtime-status.ts Reports Azure runtime status.
src/​lib/​azure/​services.test.ts Tests shared Azure services.
src/​lib/​azure/​services.ts Coordinates Azure dependencies.
src/​lib/​azure/​spawn.smoke.test.ts Smoke-tests real CLI spawning.
src/​lib/​azure/​testing/​tls.ts Generates test TLS certificates.
src/​lib/​azure/​types.ts Defines Azure interfaces.
src/​lib/​azure/​worker-runner.test.ts Tests warm workers.
src/​lib/​azure/​worker-runner.ts Manages warm Azure workers.
src/​lib/​azure/​worker-script.ts Implements Python worker protocol.
src/​lib/​cli/​argv.test.ts Tests shared tokenization.
src/​lib/​cli/​argv.ts Implements safe CLI tokenization.
src/​lib/​docker/​docker.client.test.ts Tests stack-aware lookup.
src/​lib/​docker/​docker.client.ts Finds Azure containers safely.
src/​lib/​localstack/​container-spec.logic.test.ts Tests Azure container specs.
src/​lib/​localstack/​container-spec.logic.ts Adds Azure runtime specifications.
src/​lib/​localstack/​localstack.utils.test.ts Tests lifecycle preservation.
src/​lib/​localstack/​localstack.utils.ts Extends lifecycle utilities.
src/​lib/​wizard/​azure-steps.test.ts Tests add-on steps.
src/​lib/​wizard/​azure-steps.ts Implements add-on installation steps.
src/​lib/​wizard/​cli-args.logic.test.ts Verifies unchanged wizard flags.
src/​lib/​wizard/​prereqs.test.ts Verifies wizard prerequisites.
src/​lib/​wizard/​setup-parity.test.ts Enforces setup parity.
src/​tools-tests/​localstack-azure-client.test.ts Tests the Azure tool.
src/​tools-tests/​localstack-management-ports.test.ts Tests safe port selection.
src/​tools-tests/​localstack-management.test.ts Tests Azure lifecycle operations.
src/​tools-tests/​stack-guards.test.ts Tests tool stack guards.
src/​tools/​localstack-app-inspector.ts Adds AWS stack guard.
src/​tools/​localstack-aws-client.ts Adds stack-aware AWS execution.
src/​tools/​localstack-aws-replicator.ts Adds AWS stack guard.
src/​tools/​localstack-azure-client.ts Adds Azure CLI tool.
src/​tools/​localstack-chaos-injector.ts Adds AWS stack guard.
src/​tools/​localstack-cloud-pods.ts Adds AWS stack guard.
src/​tools/​localstack-deployer.ts Adds AWS stack guard.
src/​tools/​localstack-extensions.ts Adds AWS stack guard.
src/​tools/​localstack-iam-policy-analyzer.ts Adds AWS stack guard.
src/​tools/​localstack-logs-analysis.ts Restricts AWS-specific analyses.
src/​tools/​localstack-management.ts Adds Azure lifecycle support.
src/​tools/​localstack-snowflake-client.ts Adds Snowflake stack guard.
src/​tools/​localstack-state-management.ts Adds AWS stack guard.
tests/​azure/​README.md Documents Azure test layers.
tests/​azure/​drift/​cli-and-extensions.live.test.ts Checks CLI and extension drift.
tests/​azure/​drift/​coverage-and-samples.live.test.ts Checks coverage and sample drift.
tests/​azure/​drift/​drift-helpers.ts Provides drift utilities.
tests/​azure/​drift/​emulator-contract.live.test.ts Checks emulator contracts.
tests/​azure/​egress-internal/​README.md Documents isolated egress tests.
tests/​azure/​egress-internal/​run.sh Runs internal-network checks.
tests/​azure/​egress.live.test.ts Tests live egress containment.
tests/​azure/​evals/​README.md Documents Azure evaluations.
tests/​azure/​evals/​agent.test.ts Tests evaluation agent behavior.
tests/​azure/​evals/​agent.ts Implements evaluation agent loop.
tests/​azure/​evals/​defects.test.ts Tests known evaluation defects.
tests/​azure/​evals/​fixtures/​recorded-verifications.json Stores verifier fixtures.
tests/​azure/​evals/​harness.test.ts Tests evaluation harness.
tests/​azure/​evals/​harness.ts Implements evaluation harness.
tests/​azure/​evals/​keys.test.ts Tests API-key handling.
tests/​azure/​evals/​keys.ts Loads and protects API keys.
tests/​azure/​evals/​record-fixtures.mjs Records verifier fixtures.
tests/​azure/​evals/​run.mjs Runs composition evaluations.
tests/​azure/​evals/​run.test.ts Tests evaluation CLI.
tests/​azure/​evals/​tasks-ta.ts Defines tier-A tasks.
tests/​azure/​evals/​tasks-tapim.ts Defines APIM tasks.
tests/​azure/​evals/​tasks-tb.ts Defines tier-B tasks.
tests/​azure/​evals/​tasks.test.ts Tests task definitions.
tests/​azure/​evals/​types.ts Defines evaluation types.
tests/​azure/​evals/​variants.test.ts Tests prompt variants.
tests/​azure/​evals/​variants.ts Builds prompt variants.
tests/​azure/​evals/​verifiers.test.ts Tests task verifiers.
tests/​azure/​evals/​verifiers.ts Implements task verification.
tests/​azure/​home-guard.live.test.ts Protects the real Azure profile.
tests/​azure/​live/​harness.ts Provides live-test harness.
tests/​azure/​live/​setup-matrix-full.js Selects full matrix.
tests/​azure/​live/​setup-matrix-pr.js Selects PR matrix.
tests/​azure/​live/​setup-samples-all.js Selects all samples.
tests/​azure/​live/​setup-samples-pr.js Selects PR samples.
tests/​azure/​live/​teardown.ts Closes Azure services.
tests/​azure/​matrix.live.test.ts Executes command matrices.
tests/​azure/​matrix/​README.md Documents matrix format.
tests/​azure/​matrix/​apimanagement.yaml Covers API Management.
tests/​azure/​matrix/​app.yaml Covers managed applications.
tests/​azure/​matrix/​appconfiguration.yaml Covers App Configuration.
tests/​azure/​matrix/​authorization.yaml Covers authorization APIs.
tests/​azure/​matrix/​cdn.yaml Covers CDN APIs.
tests/​azure/​matrix/​compute.yaml Covers compute APIs.
tests/​azure/​matrix/​containerinstance.yaml Covers container instances.
tests/​azure/​matrix/​containerregistry.yaml Covers container registry.
tests/​azure/​matrix/​containerservice.yaml Covers container services.
tests/​azure/​matrix/​dbformysql.yaml Covers MySQL APIs.
tests/​azure/​matrix/​dbforpostgresql.yaml Covers PostgreSQL APIs.
tests/​azure/​matrix/​documentdb.yaml Covers document databases.
tests/​azure/​matrix/​eventgrid-dataplane.yaml Covers Event Grid data plane.
tests/​azure/​matrix/​eventgrid.yaml Covers Event Grid management.
tests/​azure/​matrix/​eventhub.yaml Covers Event Hubs.
tests/​azure/​matrix/​insights-dataplane.yaml Covers Insights data plane.
tests/​azure/​matrix/​insights.yaml Covers monitoring APIs.
tests/​azure/​matrix/​keyvault.yaml Covers Key Vault.
tests/​azure/​matrix/​kubernetesconfiguration.yaml Covers Kubernetes configuration.
tests/​azure/​matrix/​managedidentity.yaml Covers managed identities.
tests/​azure/​matrix/​network.yaml Covers networking APIs.
tests/​azure/​matrix/​operationalinsights.yaml Covers Log Analytics.
tests/​azure/​matrix/​resourcegraph.yaml Covers Resource Graph.
tests/​azure/​matrix/​resources.yaml Covers resource operations.
tests/​azure/​matrix/​schema.test.ts Validates matrix schemas.
tests/​azure/​matrix/​servicebus-dataplane.yaml Covers Service Bus data plane.
tests/​azure/​matrix/​servicebus.yaml Covers Service Bus management.
tests/​azure/​matrix/​sql.yaml Covers SQL APIs.
tests/​azure/​matrix/​storage.yaml Covers storage APIs.
tests/​azure/​matrix/​web.yaml Covers web applications.
tests/​azure/​samples-replay.live.test.ts Replays official samples.
tests/​azure/​samples-shim/​README.md Documents sample shims.
tests/​azure/​samples-shim/​az Adds Bash Azure shim.
tests/​azure/​samples-shim/​az-shim.cjs Routes shim calls through MCP.
tests/​azure/​samples-shim/​az.cmd Adds Windows Azure shim.
tests/​azure/​samples-shim/​azd.cmd Blocks real Azure Developer CLI.
tests/​azure/​samples-shim/​powershell.cmd Blocks Windows PowerShell.
tests/​azure/​samples-shim/​pwsh.cmd Blocks PowerShell Core.
tests/​azure/​samples-shim/​quote.cjs Quotes shim arguments.
tests/​azure/​samples-shim/​quote.test.ts Tests shim fidelity.
tests/​azure/​tools/​azure-home-fingerprint.mjs Detects profile changes.
tests/​azure/​tools/​call-tool.mjs Calls MCP tools manually.
tests/​azure/​tools/​live-smoke.mjs Runs Azure smoke tests.
tests/​azure/​tools/​mcp-catalogue.mjs Measures tool catalogue size.
tests/​azure/​tools/​merge-op-catalogue.test.ts Tests catalogue merging.
tests/​azure/​tools/​stdio-client.mjs Implements test MCP client.
tests/​docker/​image-size.mjs Enforces image-size limits.
tests/​docker/​l5-image-assertions.sh Validates image contents.
tests/​docker/​validate-image.mjs Extends Docker harness for Azure.
tests/​fixtures/​azure/​bicep/​matrix-identity.bicep Adds identity fixture.
tests/​fixtures/​azure/​bicep/​matrix-network.bicep Adds network fixture.
tests/​fixtures/​azure/​bicep/​matrix-sub.bicep Adds subscription fixture.
tests/​fixtures/​azure/​bicep/​storage.bicep Adds storage fixture.
tests/​fixtures/​azure/​corpus/​README.md Documents command corpus.
tests/​fixtures/​azure/​corpus/​bash_argv.py Generates argv oracle.
tests/​fixtures/​azure/​corpus/​build-corpus.py Builds corpus fixture.
tests/​fixtures/​azure/​corpus/​extract.py Extracts sample commands.
tests/​fixtures/​azure/​corpus/​samples-az-corpus.json Stores command corpus.
tests/​fixtures/​azure/​drift/​certificate-sans.json Snapshots certificate SANs.
tests/​fixtures/​azure/​drift/​metadata-endpoints.json Snapshots Azure endpoints.
tests/​fixtures/​azure/​fake-az/​echo_argv.py Echoes Python arguments.
tests/​fixtures/​azure/​fake-az/​fake-az.mjs Simulates Azure CLI behavior.
tests/​fixtures/​azure/​fake-az/​sleeper.mjs Simulates lingering children.
tests/​fixtures/​azure/​fake-worker/​azure/​__init__.py Defines fake Azure package.
tests/​fixtures/​azure/​fake-worker/​azure/​cli/​__init__.py Defines fake CLI package.
tests/​fixtures/​azure/​fake-worker/​azure/​cli/​core/​__init__.py Implements fake worker CLI.
tests/​fixtures/​azure/​leak-commands.json Stores leak-resistance cases.
tests/​fixtures/​azure/​stderr/​index.json Stores stderr fixtures.
tests/​mcp/​azure-offline.spec.mjs Tests offline Azure errors.
tests/​mcp/​direct.spec.mjs Validates Azure tool registration.
tests/​mcp/​evals-gemini-azure.spec.mjs Runs Azure Gemini evaluations.
tsconfig.tests.json Type-checks Azure tests.
yarn.lock Locks new development dependencies.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/core/preflight.ts
Comment thread tests/azure/evals/record-fixtures.mjs Outdated
Comment thread src/cli/azure-addons.ts Outdated
Comment thread tests/mcp/evals-gemini-azure.spec.mjs Outdated

@paolosalvatori paolosalvatori left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! The containment layers are built with care: the isolated CLI profile, the allow-list child environment, the egress guard that needs no DNS, and the process-group kills. The unit suite is thorough, too. Most of what follows is about where the Azure code meets the existing tools and the configuration that the MCP server page documents. The PR is still a draft.

1. Rules applied

I read these from localstack-pro origin/main (8ae2eff37f). The local clone was 32 commits behind, so every file came from origin/main.

  • localstack-pro-azure/CLAUDE.md
  • .claude/rules/azure/common/: coding-style.md, hooks.md, patterns.md, security.md, testing.md
  • .claude/rules/azure/python/: cloud-pipeline.md, coding-style.md, hooks.md, patterns.md, security.md, testing.md
  • .claude/rules/azure/bicep/ (5 .bicep files and 1 .bicepparam changed): coding-style.md, hooks.md, patterns.md, security.md, testing.md
  • .claude/rules/azure/scripts/ (3 .sh files changed): coding-style.md, hooks.md, patterns.md, security.md, testing.md
  • Not applicable: terraform/ (no .tf files) and emulator/ (no emulator service code).

All of these are paths:-scoped to localstack-pro-azure/**, and this repo is TypeScript. So I cite a rule only where its principle carries over, and mark it "principle applied".

Extra guidance for this run: check compliance with the MCP server page. I read the live page and its source in localstack-docs (origin/main, src/content/docs/aws/developer-tools/running-localstack/mcp-server.mdx, last changed in 96a87121). The two agree.

2. Existing comments

Copilot left a Balanced review with 4 inline comments and nothing suppressed.

I disagree with its comment on src/core/preflight.ts L98. Making the AWS-only tools refuse a localstack/snowflake container would break documented workflows:

The docker.client.ts finding below goes the other way.

Its other three comments are fair, and I don't repeat them:

  • stderr redaction in record-fixtures.mjs;
  • install-azure-addons --no-extensions still requiring az;
  • an empty eval counting as a pass.

3. Findings

Against the MCP server page

Page definition With this PR
All tools require LOCALSTACK_AUTH_TOKEN, and each tool runs pre-flight checks (CLI, container, token) Met. localstack-azure-client checks the token first, then the emulator and az.
localstack-management: service is aws or snowflake (default aws), and envVars are passed on start azure is added and the default is unchanged. stop and restart ignore the Azure side-by-side port (HIGH). On restart, an external container's flags now override the env block (LOW).
LOCALSTACK_HOSTNAME and LOCALSTACK_PORT LOCALSTACK_PORT is honoured, but the Azure hints name LOCALSTACK_AZURE_PORT instead (LOW). The Azure tool ignores LOCALSTACK_HOSTNAME (MEDIUM). An explicit LOCALSTACK_PORT now stops the lookup from finding a container that publishes no ports (MEDIUM).
Any LocalStack configuration variable in the env block is forwarded when localstack-management starts the container Not met for Azure: the emulator's LS_AZURE_* settings are dropped (MEDIUM).
MAIN_CONTAINER_NAME Met: it still wins in every lookup.
MCP_ANALYTICS_DISABLED (default 0, so analytics are on) Honoured. With analytics on, though, a password passed as -p<value> reaches PostHog (HIGH).
localstack-aws-client: runs awslocal in the running container and blocks shell syntax The tokenizer move keeps the old behaviour exactly (no options). But the client now refuses the Snowflake emulator's container (HIGH).
localstack-snowflake-client, localstack-docs, localstack-ephemeral-instances Unchanged.
Troubleshooting: the start times out after 120 s, and a missing token gives "Auth Token Required" Both still hold for service: azure.

Docs page changes this PR needs (not anchorable; the PR body lists most of them)

  • A localstack-azure-client section (one command parameter), and azure in the service values and description of localstack-management.
  • The Azure CLI (az 2.85 or newer) as an optional prerequisite, and install-azure-addons.
  • The LOCALSTACK_AZ_* and LOCALSTACK_AZURE_* settings in the configuration reference. server.json also lacks LOCALSTACK_AZ_BICEP_ENV, which the README documents.
  • A note that the AWS-only tools refuse the Azure emulator, and that localstack-logs-analysis supports only analysisType: logs there.
  • public/.well-known/mcp/server-card.json in localstack-docs, which still describes an AWS-only v0.3.0 server.

CI on this head

  • build (ubuntu-latest) stops at the audit step (fast-uri advisories), which fails on every recent PR. So the unit suite never ran on Linux in CI. Locally on Linux it passes: 1,572 tests passed and 41 skipped, with 92.6 % line coverage of the Azure code, and both type-checks are clean.

  • build (macos-latest) (7 failures) and build (windows-latest) (6) fail on this PR's own tests:

    • the policy.ts realpath finding causes the policy and Bicep failures on both, and (judging from the RUNNER~1 path in the log) the Windows shim one;
    • runner.test.ts L127 and L422, and quote.test.ts L684-L685, cause the other macOS failures;
    • .gitattributes causes the Windows LF failure.

    On Linux with TMPDIR behind a symlink, this head fails 6 tests (the macOS set minus the __CF_USER_TEXT_ENCODING one). With the changes below, it fails none.

  • live, L4 samples subset and smoke fail because the repo's LOCALSTACK_AUTH_TOKEN is not entitled for LocalStack for Azure: the emulator exits with code 55, and Snowflake is refused too. So none of the live results in the PR description ran in CI. The push job in docker.yml needs smoke, so after merge no image is published until the secret is entitled or the licence-dependent scenarios stop blocking the push.

How the suggestions were checked: I applied every change below together in a scratch worktree. Both type-checks and prettier are clean, and the full unit suite passes (1,572 passed and 41 skipped, as on this head), also with TMPDIR behind a symlink. Each reproduction uses this head's own functions and fixtures.

The findings, each inline with its evidence and a tested fix:

Severity Where Finding
HIGH src/lib/azure/policy.ts L407-L421 The realpath check refuses a symlinked workdir's files, and lets .. or a symlinked home past the protected directories
HIGH src/lib/azure/policy.ts L1002-L1003 A stuck short-option value, such as -pS3cret, reaches analytics
MEDIUM src/lib/azure/policy.ts L475 The URL rule and the egress guard allow every port on localhost
HIGH src/lib/docker/docker.client.ts L249-L253 localstack-aws-client now refuses the Snowflake emulator's container
MEDIUM src/lib/docker/docker.client.ts L321-L322 With LOCALSTACK_PORT set, a container that publishes no ports is no longer found
HIGH src/tools/localstack-management.ts L315-L320 Side by side, stop and restart with service: "azure" act on the AWS emulator
LOW src/tools/localstack-management.ts L348-L352 On restart, an external container's flags override the MCP env block
MEDIUM src/lib/azure/resolve-az.ts L349-L350 A Windows az behind a non-default WSL automount root gets past the WSL guard
MEDIUM src/lib/azure/worker-script.ts L100-L101 The warm worker keeps the first command's log level for up to 200 commands
LOW src/lib/azure/worker-runner.ts L352-L353 A cancel during the worker's cold start is lost, and the command still runs
MEDIUM src/lib/localstack/container-spec.logic.ts L34-L38 The Azure emulator's own LS_AZURE_* settings in the env block never reach the container
MEDIUM src/lib/azure/loopback-forwarder.ts L265-L266 The Azure tool ignores the documented LOCALSTACK_HOSTNAME
MEDIUM .gitattributes L4-L5 The matrix YAML files are not pinned to LF, so the new Windows leg fails
MEDIUM src/lib/azure/runner.test.ts L127 The cwd check compares the child's physical path with the temp dir as spelled
MEDIUM src/lib/azure/runner.test.ts L422 The environment check does not allow for the key that macOS adds itself
MEDIUM tests/azure/samples-shim/quote.test.ts L684-L685 The same physical-cwd assumption as runner.test.ts L127
LOW src/lib/azure/emulator.ts L184-L194 The hints name LOCALSTACK_AZURE_PORT where the documented setting is LOCALSTACK_PORT
LOW src/lib/azure/description.ts L20 The tool description promises a default location that is never configured
LOW src/tools/localstack-azure-client.ts L100-L104 Comments still point at a design document that is not in the repo
LOW scripts/ci/azure-emulator-up.sh L46-L47 The health loop keeps polling a container that has already exited
LOW .github/workflows/azure-live.yml L66-L70 The ~/.azure fingerprint is taken after the emulator start, so a failed start adds a false home-guard failure
LOW .github/workflows/azure-live.yml L14-L15 The path filter misses files that the live suites read
LOW .github/workflows/azure-weekly.yml L23 The auth token is set for every job and step of the weekly workflow

4. Clean files

These changed files have no material findings. Some I read myself; others went through a helper pass, and I verified every point from it that became a finding.

  • Tools and core:
    • src/tools/localstack-aws-client.ts (its regression is filed under docker.client.ts);
    • the requireStack additions in the other ten src/tools/*.ts;
    • src/core/preflight.ts and src/core/config.ts, apart from the internal references;
    • src/core/analytics.ts, src/cli/index.ts, src/cli/help.ts;
    • src/cli/azure-addons.ts (Copilot's point stands);
    • src/lib/cli/argv.ts and src/lib/aws/aws-cli-sanitizer.ts: the AWS client keeps its exact tokenizer behaviour;
    • src/lib/localstack/localstack.utils.ts: its restart carry is filed under localstack-management.ts.
  • Azure modules:
    • child-env.ts, runner.ts, bootstrap.ts, services.ts, local-hosts.ts, runtime-status.ts;
    • egress-proxy.ts: its port gap is filed under policy.ts;
    • output.ts: the first line that analytics records is only exit <code>, <class>;
    • extension-install.ts, extension-map.ts, extension-pins.ts, types.ts, testing/tls.ts and az-file-args.generated.json;
    • bicep-install.ts: all seven sha256 pins match the v0.47.16 release;
    • src/lib/wizard/azure-steps.ts: the wizard really is unchanged, and only install-azure-addons imports this file.
  • Packaging, docs and CI:
    • Dockerfile: the entrypoint is unchanged, and the Bicep ADD --checksum and the extension pins are enforced;
    • docker/*, manifest.json, README.md, docs/DOCKER.md;
    • server.json, apart from the missing LOCALSTACK_AZ_BICEP_ENV;
    • package.json: no new runtime dependency, and start and bin are unchanged;
    • .github/CODEOWNERS, .github/actions/azure-live-setup/action.yml, .github/workflows/docker.yml;
    • scripts/ci/merge-op-catalogue.cjs, scripts/install-azure-extensions.mjs, scripts/gen-az-file-args.py, tests/docker/*;
    • .gitignore, .prettierignore, jest.config.js, jest.azure-live.config.js, tsconfig.tests.json, playwright.config.mjs.
  • Tests and fixtures:
    • no real secrets: only placeholders, all-zero ids and one key the emulator generated;
    • the Anthropic eval runs only when invoked explicitly, with a spend cap, and the Gemini one only through yarn test:mcp:evals:azure;
    • an AST scan of the 47 non-live test files found no test without a real assertion.
  • Deprioritized: the 31 matrix YAML files (covered through schema.test.ts), tests/azure/evals/tasks-*.ts, the live suites beyond URL and secret greps, and the corpus Python.

5. Verdict

Request changes. There are four HIGH findings, each with a tested suggestion:

  • The file rule's realpath check both refuses legitimate files and lets .. or a symlinked home past ~/.ssh.
  • A password passed as -p<value> is sent to PostHog.
  • The AWS client refuses the Snowflake emulator.
  • stop and restart take down the AWS emulator when Azure runs beside it.

Separately, the live suites cannot validate anything until the repo's LOCALSTACK_AUTH_TOKEN is entitled for LocalStack for Azure.

Comment thread src/lib/azure/policy.ts
Comment thread src/lib/azure/policy.ts Outdated
Comment thread src/lib/azure/policy.ts Outdated
Comment thread src/lib/docker/docker.client.ts Outdated
Comment thread src/lib/docker/docker.client.ts
Comment thread src/tools/localstack-azure-client.ts Outdated
Comment thread scripts/ci/azure-emulator-up.sh
Comment thread .github/workflows/azure-live.yml Outdated
Comment thread .github/workflows/azure-live.yml Outdated
Comment thread .github/workflows/azure-weekly.yml Outdated
…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.

@paolosalvatori paolosalvatori left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the diagram, I read the PT with attention tomorrow, thanks!

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.
@DrisDary
DrisDary marked this pull request as ready for review September 29, 2026 17:45
@DrisDary
DrisDary merged commit 8754c10 into main Sep 30, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants