Repository navigation
SamConfig.get_all() mutates shared document, leaking one command's config values into another's #9181
Description
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 paramsglobal_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'} -- WRONGbuild 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_paramsExpected 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-cliversion: 1.165.0 (currentdevelopbranch, commit at time of testing)- OS: macOS
Activity