-
Notifications
You must be signed in to change notification settings - Fork 279
Description
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:azd app run -e dev -
In the child process, BOTH
MY_STAGING_VARandMY_DEV_VARare 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
-ebefore dispatching to extensions: Extract the-e/--environmentflag value from args beforeDisableFlagParsingtakes 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 viam.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)