Files
caddy/modules/caddyhttp/celmatcher_pattern_test.go
Matthew HoltandCopilot 0c7629e52b caddyhttp: verify CEL pattern provenance and satisfy lint
Follow up report dff6e149-9cd3-47fb-8e21-b4d3d4bad807: make AST handling exhaustive and preallocate map entries. Verify regex and URL-pattern factories do not expand runtime env/file placeholders while preserving their existing pattern grammar and static behavior.

Code written by GitHub Copilot (GPT-6.1 Sol, Claude Opus 5.5); reviewed by mholt.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-11 11:06:41 -07:00

89 lines
3.5 KiB
Go

package caddyhttp
import (
"context"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/caddyserver/caddy/v2"
)
func TestCELPatternPlaceholderExpansion(t *testing.T) {
t.Setenv("CADDY_CEL_PATTERN_TEST", "secret")
t.Chdir(t.TempDir())
private := "synthetic.txt"
if err := os.WriteFile(private, []byte("secret"), 0o600); err != nil {
t.Fatal(err)
}
for _, expr := range []string{
`header_regexp('Field', {input})`,
`path_regexp({input})`,
`vars_regexp('foo', {input})`,
} {
t.Run(expr, func(t *testing.T) {
for _, tc := range []struct {
name, pattern, actual string
want bool
}{
{"env not expanded", "{env.CADDY_CEL_PATTERN_TEST}", "secret", false},
{"file not expanded", "{file." + private + "}", "secret", false},
{"placeholder-shaped regex", "{env.CADDY_CEL_PATTERN_TEST}", "{env.CADDY_CEL_PATTERN_TEST}", true},
{"quantifier preserved", "se{2}cret", "seecret", true},
{"regexp grammar preserved", "s[e]+cr.t", "secret", true},
} {
t.Run(tc.name, func(t *testing.T) {
ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
defer cancel()
m := MatchExpression{Expr: expr}
if err := m.Provision(ctx); err != nil {
t.Fatal(err)
}
req := httptest.NewRequest("GET", "http://example.test/"+tc.actual, nil)
req = req.WithContext(context.WithValue(req.Context(), VarsCtxKey, map[string]any{"foo": tc.actual}))
repl := NewTestReplacer(req)
repl.Set("input", tc.pattern)
req.Header.Set("Field", tc.actual)
if matched, err := m.MatchWithError(req); err != nil || matched != tc.want {
t.Fatalf("matched=%v, error=%v; want %v", matched, err, tc.want)
}
})
}
})
}
for _, tc := range []struct {
name, expr, pattern, actual string
want bool
}{
{"static regexp quantifier", `header_regexp('Field', 'se{2}cret')`, "", "seecret", true},
{"static regexp placeholder shape", `header_regexp('Field', '\{env.CADDY_CEL_PATTERN_TEST}')`, "", "{env.CADDY_CEL_PATTERN_TEST}", true},
{"static regexp does not expand", `header_regexp('Field', '\{env.CADDY_CEL_PATTERN_TEST}')`, "", "secret", false},
{"direct CEL placeholder once", `header_regexp('Field', {env.CADDY_CEL_PATTERN_TEST})`, "", "secret", true},
{"URL env not expanded", `url_pattern({input})`, "/{env.CADDY_CEL_PATTERN_TEST}", "secret", false},
{"URL file not expanded", `url_pattern({input})`, "/{file." + filepath.ToSlash(private) + "}", "secret", false},
{"URL wildcard preserved", `url_pattern({input})`, "/sec*", "secret", true},
{"URL named group preserved", `url_pattern({input})`, "/:id", "secret", true},
{"URL static grammar", `url_pattern('/sec*')`, "", "secret", true},
{"URL static placeholder shape", `url_pattern('/\{env.CADDY_CEL_PATTERN_TEST}')`, "", "secret", false},
{"URL direct CEL placeholder once", `url_pattern('/' + {env.CADDY_CEL_PATTERN_TEST})`, "", "secret", true},
} {
t.Run(tc.name, func(t *testing.T) {
ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
defer cancel()
m := MatchExpression{Expr: tc.expr}
if err := m.Provision(ctx); err != nil {
t.Fatal(err)
}
req := httptest.NewRequest("GET", "http://example.test/"+tc.actual, nil)
repl := NewTestReplacer(req)
repl.Set("input", tc.pattern)
req.Header.Set("Field", tc.actual)
if matched, err := m.MatchWithError(req); err != nil || matched != tc.want {
t.Fatalf("matched=%v, error=%v; want %v", matched, err, tc.want)
}
})
}
}