Merge commit from fork

Only apply repl.ReplaceAll() on values from literal variable names
(e.g. map outputs), not on values resolved from placeholder keys
(e.g. {http.request.header.*}). The placeholder path already resolves
the value via repl.Get(), so a second expansion allows user-controlled
input containing {env.*} or {file.*} to be evaluated, leaking
environment variables and file contents.

Add regression test to verify placeholder-sourced values are not
re-expanded.
This commit is contained in:
Sam.An 2026-03-05 01:08:39 +09:00 committed by GitHub
parent 2dbcdefbbe
commit 7e83775e3a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 20 additions and 2 deletions

View File

@ -967,6 +967,7 @@ func TestVarREMatcher(t *testing.T) {
desc string
match MatchVarsRE
input VarsMiddleware
headers http.Header
expect bool
expectRepl map[string]string
}{
@ -1001,6 +1002,14 @@ func TestVarREMatcher(t *testing.T) {
input: VarsMiddleware{"Var1": "var1Value"},
expect: true,
},
{
desc: "placeholder key value containing braces is not double-expanded",
match: MatchVarsRE{"{http.request.header.X-Input}": &MatchRegexp{Pattern: ".+", Name: "val"}},
input: VarsMiddleware{},
headers: http.Header{"X-Input": []string{"{env.HOME}"}},
expect: true,
expectRepl: map[string]string{"val.0": "{env.HOME}"},
},
} {
t.Run(tc.desc, func(t *testing.T) {
t.Parallel()
@ -1017,7 +1026,7 @@ func TestVarREMatcher(t *testing.T) {
}
// set up the fake request and its Replacer
req := &http.Request{URL: new(url.URL), Method: http.MethodGet}
req := &http.Request{URL: new(url.URL), Method: http.MethodGet, Header: tc.headers}
repl := caddy.NewReplacer()
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
ctx = context.WithValue(ctx, VarsCtxKey, make(map[string]any))

View File

@ -312,10 +312,12 @@ func (m MatchVarsRE) MatchWithError(r *http.Request) (bool, error) {
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
for key, val := range m {
var varValue any
var fromPlaceholder bool
if strings.HasPrefix(key, "{") &&
strings.HasSuffix(key, "}") &&
strings.Count(key, "{") == 1 {
varValue, _ = repl.Get(strings.Trim(key, "{}"))
fromPlaceholder = true
} else {
varValue = vars[key]
}
@ -334,7 +336,14 @@ func (m MatchVarsRE) MatchWithError(r *http.Request) (bool, error) {
varStr = fmt.Sprintf("%v", vv)
}
valExpanded := repl.ReplaceAll(varStr, "")
// Only expand placeholders in values from literal variable names
// (e.g. map outputs). Values resolved from placeholder keys are
// already final and must not be re-expanded, as that would allow
// user input like {env.SECRET} to be evaluated.
valExpanded := varStr
if !fromPlaceholder {
valExpanded = repl.ReplaceAll(varStr, "")
}
if match := val.Match(valExpanded, repl); match {
return match, nil
}