Skip to content

SamConfig.get_all() mutates shared document, leaking one command's config values into another's #9181

Description

@Adityaj0

Description

SamConfig.get_all() in samcli/lib/config/samconfig.py mutates the in-memory self.document in place when merging [default.global.parameters] with a command-specific section. Because self.document is cached (only re-read from disk if empty — see _read()), repeated calls to get_all() on the same SamConfig instance corrupt the shared global section with a previous command's specific parameter values.

Root cause

# samcli/lib/config/samconfig.py:89-95
config_content = self.document.get(env, {})
params = config_content.get(self.to_key(cmd_names), {}).get(section, {})
if DEFAULT_GLOBAL_CMDNAME in config_content:
    global_params = config_content.get(DEFAULT_GLOBAL_CMDNAME, {}).get(section, {})
    global_params.update(params.copy())   # <-- mutates the live dict inside self.document
    params = global_params.copy()
return params

global_params is a live reference into self.document["default"]["global"]["parameters"], not a copy. .update() permanently merges the current command's parameters into it. The .copy() calls only protect the returned dict — the corruption of self.document persists for the lifetime of the object.

Steps to reproduce

samconfig.toml:

version = 0.1

[default.global.parameters]
stack_name = "global-stack"

[default.deploy.parameters]
stack_name = "deploy-only-stack"
region = "us-east-1"
from samcli.lib.config.samconfig import SamConfig
sc = SamConfig("<dir containing samconfig.toml>")
print(sc.get_all(['deploy'], 'parameters'))
# {'stack_name': 'deploy-only-stack', 'region': 'us-east-1'}   -- correct

print(sc.get_all(['build'], 'parameters'))
# {'stack_name': 'deploy-only-stack', 'region': 'us-east-1'}   -- WRONG

build has no [default.build.parameters] section, so it should resolve to just {'stack_name': 'global-stack'} (inherited from [default.global.parameters] only). Instead it silently inherits deploy's stack_name and even picks up region, which was never defined globally at all.

I verified this directly against samcli.lib.config.samconfig.SamConfig — not simulated.

Why this doesn't usually surface via the plain CLI

cli_config_file.ConfigProvider.__call__ constructs a fresh SamConfig per process invocation and calls get_all() exactly once, so a single sam <command> run normally discards the corrupted object before it matters. The bug becomes externally observable whenever a SamConfig instance is reused across more than one get_all() call for different cmd_names — a valid, documented use of the public API (it takes cmd_names per call for exactly this purpose), e.g. by embedders/toolkits (AWS Toolkit for VS Code/Cloud9) or any code path that resolves config for multiple commands against one loaded samconfig.

Suggested fix

global_params = dict(config_content.get(DEFAULT_GLOBAL_CMDNAME, {}).get(section, {}))
global_params.update(params)
params = global_params

Expected result

get_all() calls for different commands against the same SamConfig instance should not affect each other's resolved parameters.

Actual result

A command-specific parameter value from one get_all() call leaks into the resolved parameters of a subsequent get_all() call for a different command, if that command lacks its own value for the same key.

Environment

  • aws-sam-cli version: 1.165.0 (current develop branch, commit at time of testing)
  • OS: macOS

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions