Skip to content

azd <extension> -e <env> leaks default environment variables into extension process #7034

@jongio

Description

@jongio

Description

When running an extension command with -e <env> to specify a non-default environment, environment variables from the default environment leak into the extension process. Variables that exist only in the default environment (and are not overridden by the -e environment) are visible to the extension and its child processes.

Steps to Reproduce

  1. Create a project with two environments:

    azd env new dev
    azd env new staging
    azd env select staging   # make staging the default
    
  2. Set a variable ONLY in staging:

    azd env set MY_STAGING_VAR "staging-value" -e staging
    
  3. Set a different variable in dev:

    azd env set MY_DEV_VAR "dev-value" -e dev
    
  4. Run an extension with -e dev:

    azd app run -e dev
    
  5. In the child process, BOTH MY_STAGING_VAR and MY_DEV_VAR are present in the environment.

Expected Behavior

Only variables from the -e dev environment should be present. MY_STAGING_VAR (which exists only in the staging/default environment) should NOT leak into the extension process.

Actual Behavior

The extension process receives a merged set of environment variables: default environment values as the base, with -e environment values overlaid on top. Any variable that exists ONLY in the default environment leaks through because the -e environment doesn't define it to override.

Root Cause Analysis

Traced through the source code:

cli/azd/cmd/extensions.goextensionAction.Run():

allEnv := []string{}
allEnv = append(allEnv, os.Environ()...)          // system env

env, err := a.lazyEnv.GetValue()                   // resolves to... which env?
if err == nil && env != nil {
    allEnv = append(allEnv, env.Environ()...)      // injects env values
}

The problem is that lazyEnv resolves to the default environment rather than the -e environment. This is because extension commands are registered with DisableFlagParsing: true:

cmd := &cobra.Command{
    Use:                lastPart,
    DisableFlagParsing: true,  // prevents -e from being parsed by cobra
}

With DisableFlagParsing: true, cobra does not parse persistent flags (including -e) for extension commands. The flag value is passed through to the extension as args, but a.cmd.Flags().GetString("environment") returns "". This causes the DI-injected lazyEnv to fall back to the default environment.

Additionally, the envName extraction logic also hits this wall:

envName, _ := a.cmd.Flags().GetString("environment")  // returns "" due to DisableFlagParsing
options := &extensions.InvokeOptions{
    Environment: envName,  // "" → AZD_ENVIRONMENT not set
}

Since envName is "", AZD_ENVIRONMENT is not propagated to the extension process either (the runner.go guard checks if options.Environment != "").

Impact

This is a data isolation bug. Environment-specific secrets, API endpoints, feature flags, and configuration from the default environment leak into non-default environment runs. This can cause:

  • Wrong API endpoints being used
  • Feature flags from one environment activating in another
  • Staging-specific behavior appearing in dev (or vice versa)
  • Secrets from one environment being visible in another

Suggested Fix

Ensure that when -e <env> is specified, lazyEnv resolves to that environment, not the default. Options:

  1. Parse -e before dispatching to extensions: Extract the -e/--environment flag value from args before DisableFlagParsing takes effect, and use it for DI environment resolution.
  2. Use the middleware path: The middleware-based extension execution path (cli/azd/cmd/middleware/extensions.go) may handle flag parsing differently via m.options.Flags.GetString("environment"). Ensure parity between the two execution paths.
  3. Don't inject lazyEnv.Environ() for extension commands: Let extensions query the gRPC EnvironmentService for values explicitly, rather than inheriting them via process environment.

Workaround

Run azd env select <env> before running the extension command to make the desired environment the default:

azd env select dev
azd app run    # now uses dev as default, no -e needed

Environment

  • azd version: 1.23.7
  • OS: Windows 11
  • Extension: jongio/azd-app v0.13.2 (but affects all extensions that rely on inherited env vars)

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions