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
-
Create a project with two environments:
azd env new dev
azd env new staging
azd env select staging # make staging the default
-
Set a variable ONLY in staging:
azd env set MY_STAGING_VAR "staging-value" -e staging
-
Set a different variable in dev:
azd env set MY_DEV_VAR "dev-value" -e dev
-
Run an extension with -e dev:
-
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.go — extensionAction.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:
- 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.
- 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.
- 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)
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-eenvironment) are visible to the extension and its child processes.Steps to Reproduce
Create a project with two environments:
Set a variable ONLY in staging:
Set a different variable in dev:
Run an extension with
-e dev:In the child process, BOTH
MY_STAGING_VARandMY_DEV_VARare present in the environment.Expected Behavior
Only variables from the
-e devenvironment 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
-eenvironment values overlaid on top. Any variable that exists ONLY in the default environment leaks through because the-eenvironment doesn't define it to override.Root Cause Analysis
Traced through the source code:
cli/azd/cmd/extensions.go—extensionAction.Run():The problem is that
lazyEnvresolves to the default environment rather than the-eenvironment. This is because extension commands are registered withDisableFlagParsing: true: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, buta.cmd.Flags().GetString("environment")returns"". This causes the DI-injectedlazyEnvto fall back to the default environment.Additionally, the
envNameextraction logic also hits this wall:Since
envNameis"",AZD_ENVIRONMENTis not propagated to the extension process either (therunner.goguard checksif 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:
Suggested Fix
Ensure that when
-e <env>is specified,lazyEnvresolves to that environment, not the default. Options:-ebefore dispatching to extensions: Extract the-e/--environmentflag value from args beforeDisableFlagParsingtakes effect, and use it for DI environment resolution.cli/azd/cmd/middleware/extensions.go) may handle flag parsing differently viam.options.Flags.GetString("environment"). Ensure parity between the two execution paths.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:Environment