From b3e176c5f8c0f2f06ccc6fa951886a74a99a5ebd Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Sat, 2 May 2026 13:01:17 +0100 Subject: [PATCH] Cap interpolated property string length to prevent amplification DoS Self-referential properties like ${x}${x}${x} can amplify exponentially across interpolation passes. Tracks net string growth within each pass and stops substituting once the output would exceed 1 MiB. --- interpolate.go | 18 ++++++++++++++++-- interpolate_test.go | 11 +++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/interpolate.go b/interpolate.go index bf6556d..f093966 100644 --- a/interpolate.go +++ b/interpolate.go @@ -5,7 +5,10 @@ import ( "strings" ) -const maxInterpolationPasses = 10 +const ( + maxInterpolationPasses = 10 + maxInterpolatedLength = 1 << 20 // 1 MiB +) var exprRE = regexp.MustCompile(`\$\{([^}]+)\}`) @@ -18,15 +21,26 @@ func interpolate(s string, props map[string]string) string { } for range maxInterpolationPasses { changed := false + capped := false + growth := 0 + baseLen := len(s) s = exprRE.ReplaceAllStringFunc(s, func(m string) string { + if capped { + return m + } name := m[2 : len(m)-1] if v, ok := lookup(props, name); ok { + growth += len(v) - len(m) + if baseLen+growth > maxInterpolatedLength { + capped = true + return m + } changed = true return v } return m }) - if !changed || !strings.Contains(s, "${") { + if capped || !changed || !strings.Contains(s, "${") { break } } diff --git a/interpolate_test.go b/interpolate_test.go index e133a42..1758523 100644 --- a/interpolate_test.go +++ b/interpolate_test.go @@ -36,6 +36,17 @@ func TestInterpolate(t *testing.T) { } } +func TestInterpolateAmplificationCapped(t *testing.T) { + props := map[string]string{ + "bomb": "${bomb}${bomb}${bomb}${bomb}${bomb}", + } + result := interpolate("${bomb}", props) + // Without the cap, 10 passes of 5x self-reference would produce ~68 MiB. + if len(result) > maxInterpolatedLength { + t.Fatalf("interpolated length %d exceeds cap %d", len(result), maxInterpolatedLength) + } +} + func TestFirstExpr(t *testing.T) { tests := []struct { in string