diff --git a/terraform/context_validate_test.go b/terraform/context_validate_test.go index f9c22d9213..087de1a2ec 100644 --- a/terraform/context_validate_test.go +++ b/terraform/context_validate_test.go @@ -1250,6 +1250,46 @@ resource "aws_instance" "foo" { } } +func TestContext2Validate_invalidSensitiveModuleOutput(t *testing.T) { + m := testModuleInline(t, map[string]string{ + "child/main.tf": ` +variable "foo" { + default = "xyz" + sensitive = true +} + +output "out" { + value = var.foo +}`, + "main.tf": ` +module "child" { + source = "./child" +} + +resource "aws_instance" "foo" { + foo = module.child.out +}`, + }) + + p := testProvider("aws") + ctx := testContext2(t, &ContextOpts{ + Config: m, + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), + }, + }) + + diags := ctx.Validate() + if !diags.HasErrors() { + t.Fatal("succeeded; want errors") + } + // Should get this error: + // Output refers to sensitive values: Expressions used in outputs can only refer to sensitive values if the sensitive attribute is true. + if got, want := diags.Err().Error(), "Output refers to sensitive values"; strings.Index(got, want) == -1 { + t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want) + } +} + func TestContext2Validate_legacyResourceCount(t *testing.T) { m := testModuleInline(t, map[string]string{ "main.tf": ` diff --git a/terraform/evaluate.go b/terraform/evaluate.go index 3fd02772d6..4c87e55d58 100644 --- a/terraform/evaluate.go +++ b/terraform/evaluate.go @@ -260,6 +260,10 @@ func (d *evaluationStateData) GetInputVariable(addr addrs.InputVariable, rng tfd // being liberal in what it accepts because the subsequent plan walk has // more information available and so can be more conservative. if d.Operation == walkValidate { + // Ensure variable sensitivity is captured in the validate walk + if config.Sensitive { + return cty.UnknownVal(wantType).Mark("sensitive"), diags + } return cty.UnknownVal(wantType), diags }