diff --git a/hclsyntax/expression_template.go b/hclsyntax/expression_template.go index f175fc5f..a0dc7c22 100644 --- a/hclsyntax/expression_template.go +++ b/hclsyntax/expression_template.go @@ -94,7 +94,16 @@ func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) ret = cty.UnknownVal(cty.String) if !diags.HasErrors() { // Invalid input means our partial result buffer is suspect if knownPrefix := buf.String(); knownPrefix != "" { - ret = ret.Refine().StringPrefix(knownPrefix).NewValue() + byteLen := len(knownPrefix) + // Impose a reasonable upper limit to avoid producing too long a prefix. + // The 128 B is about 10% of the safety limits in cty's msgpack decoder. + // @see https://github.com/zclconf/go-cty/blob/v1.13.2/cty/msgpack/unknown.go#L170-L175 + // + // This operation is safe because StringPrefix removes incomplete trailing grapheme clusters. + if byteLen > 128 { // arbitrarily-decided threshold + byteLen = 128 + } + ret = ret.Refine().StringPrefix(knownPrefix[:byteLen]).NewValue() } } } else { diff --git a/hclsyntax/expression_template_test.go b/hclsyntax/expression_template_test.go index 6d1b2d51..c78094f5 100644 --- a/hclsyntax/expression_template_test.go +++ b/hclsyntax/expression_template_test.go @@ -4,6 +4,7 @@ package hclsyntax import ( + "strings" "testing" "github.com/hashicorp/hcl/v2" @@ -307,6 +308,16 @@ trim`, cty.UnknownVal(cty.String).Refine().NotNull().StringPrefixFull("test_known_").NewValue(), 0, }, + { // can preserve a static prefix as a refinement, but the length is limited to 128 B + strings.Repeat("_", 130) + `${unknown}`, + &hcl.EvalContext{ + Variables: map[string]cty.Value{ + "unknown": cty.UnknownVal(cty.String), + }, + }, + cty.UnknownVal(cty.String).Refine().NotNull().StringPrefixFull(strings.Repeat("_", 128)).NewValue(), + 0, + }, { // marks from uninterpolated values are ignored `hello%{ if false } ${target}%{ endif }`, &hcl.EvalContext{