Skip to content

Commit dcb88ae

Browse files
committed
Add an exclude test
1 parent ba8f199 commit dcb88ae

3 files changed

Lines changed: 83 additions & 2 deletions

File tree

ci/docker-compose.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ services:
2121
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.protectParameters: "${PROTECT_PARAMETERS:-false}"
2222
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.goodBots: ""
2323
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.enableGooglebotIPCheck: "true"
24-
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.protectRoutes: "/"
24+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.mode: "regex"
25+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.protectRoutes: "^/"
26+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.excludeRoutes: "\\/oai\\/request,\\/node\\/\\d+\\/(book-)?manifest"
2527
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.persistentStateFile: "/tmp/state.json"
2628
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.enableStateReconciliation: "true"
2729
healthcheck:
@@ -53,7 +55,9 @@ services:
5355
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.protectParameters: "${PROTECT_PARAMETERS:-false}"
5456
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.goodBots: ""
5557
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.enableGooglebotIPCheck: "true"
56-
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.protectRoutes: "/"
58+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.mode: "regex"
59+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.protectRoutes: "^/"
60+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.excludeRoutes: "\\/oai\\/request,\\/node\\/\\d+\\/(book-)?manifest"
5761
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.persistentStateFile: "/tmp/state.json"
5862
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.enableStateReconciliation: "true"
5963
healthcheck:

ci/test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func main() {
6565

6666
fmt.Printf("Making sure attempt #%d causes a redirect to the challenge page\n", rateLimit+1)
6767
ensureRedirect(ips, "http://localhost")
68+
testExcludeRouteRegexBypass(ips)
6869

6970
fmt.Println("\nTesting state sharing between nginx instances...")
7071
time.Sleep(cp.StateSaveInterval + cp.StateSaveJitter + (5 * time.Second))
@@ -189,6 +190,36 @@ func ensureRedirect(ips []string, url string) {
189190
}
190191
}
191192

193+
func testExcludeRouteRegexBypass(ips []string) {
194+
fmt.Println("\nTesting regex excludeRoutes bypass...")
195+
196+
testIP := ips[0]
197+
tests := []struct {
198+
url string
199+
name string
200+
}{
201+
{
202+
url: "http://localhost/node/123/manifest",
203+
name: "/node/123/manifest",
204+
},
205+
{
206+
url: "http://localhost/oai/request?foo=bar",
207+
name: "/oai/request?foo=bar",
208+
},
209+
}
210+
211+
for _, tt := range tests {
212+
fmt.Printf("Checking excluded route %s with IP %s\n", tt.name, testIP)
213+
output := httpRequest(testIP, tt.url)
214+
if output != "" {
215+
slog.Error("Excluded route was unexpectedly challenged", "ip", testIP, "route", tt.name, "output", output)
216+
os.Exit(1)
217+
}
218+
}
219+
220+
fmt.Println("✓ regex excludeRoutes bypass works for excluded paths")
221+
}
222+
192223
func testStateSharing(ips []string) {
193224
// Use first IP to test state sharing
194225
testIP := ips[0]

main_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,52 @@ func TestVerifiedCacheBypasses(t *testing.T) {
803803
}
804804
}
805805

806+
func TestShouldApplyRegexExcludeRoutesIgnoreQueryString(t *testing.T) {
807+
config := CreateConfig()
808+
config.SiteKey = "test"
809+
config.SecretKey = "test"
810+
config.Mode = "regex"
811+
config.ProtectRoutes = []string{"^/"}
812+
config.ExcludeRoutes = []string{`\/oai\/request`, `\/node\/\d+\/(book-)?manifest`}
813+
config.RateLimit = 0
814+
815+
bc, err := NewCaptchaProtect(context.Background(), nil, config, "test")
816+
if err != nil {
817+
t.Fatalf("unexpected error %v", err)
818+
}
819+
820+
tests := []struct {
821+
name string
822+
url string
823+
want bool
824+
}{
825+
{
826+
name: "query string does not prevent exclude route match",
827+
url: "http://example.com/oai/request?foo=bar",
828+
want: false,
829+
},
830+
{
831+
name: "regex exclude route matches manifest path",
832+
url: "http://example.com/node/123/manifest",
833+
want: false,
834+
},
835+
{
836+
name: "non excluded route still protected",
837+
url: "http://example.com/node/123/other",
838+
want: true,
839+
},
840+
}
841+
842+
for _, tt := range tests {
843+
t.Run(tt.name, func(t *testing.T) {
844+
req := httptest.NewRequest(http.MethodGet, tt.url, nil)
845+
if got := bc.shouldApply(req, "1.2.3.4"); got != tt.want {
846+
t.Errorf("shouldApply(%q) = %v; want %v", tt.url, got, tt.want)
847+
}
848+
})
849+
}
850+
}
851+
806852
func TestStatsPage(t *testing.T) {
807853
config := CreateConfig()
808854
config.SiteKey = "test"

0 commit comments

Comments
 (0)